zlicenser-server 0.1.2

Server library for the zlicenser hardware-bound software licensing framework.
Documentation
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce, aead::Aead};
use ed25519_dalek::VerifyingKey;
use hkdf::Hkdf;
use rand::{RngCore, rngs::OsRng};
use sha2::Sha256;
use x25519_dalek::{EphemeralSecret, PublicKey};
use zeroize::{Zeroize, ZeroizeOnDrop};

const AT_REST_WRAP_INFO: &[u8] = b"zlicenser-server-s-issue-at-rest-v1";
const AIRGAP_WRAP_INFO: &[u8] = b"zlicenser-airgapped-s-issue-wrap-v1";

#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SIssue([u8; 32]);

impl SIssue {
    pub fn generate() -> Self {
        let mut bytes = [0u8; 32];
        OsRng.fill_bytes(&mut bytes);
        Self(bytes)
    }

    pub fn as_bytes(&self) -> &[u8; 32] {
        &self.0
    }
}

pub fn derive_at_rest_key(vendor_seed: &[u8; 32]) -> crate::Result<[u8; 32]> {
    let hk = Hkdf::<Sha256>::new(None, vendor_seed);
    let mut okm = [0u8; 32];
    hk.expand(AT_REST_WRAP_INFO, &mut okm)
        .map_err(|e| crate::Error::SecretWrappingFailed(e.to_string()))?;
    Ok(okm)
}

pub fn encrypt_s_issue_at_rest(s_issue: &SIssue, at_rest_key: &[u8; 32]) -> crate::Result<Vec<u8>> {
    let key = Key::<Aes256Gcm>::from_slice(at_rest_key);
    let cipher = Aes256Gcm::new(key);

    let mut nonce_bytes = [0u8; 12];
    OsRng.fill_bytes(&mut nonce_bytes);
    let nonce = Nonce::from_slice(&nonce_bytes);

    let ct = cipher
        .encrypt(nonce, s_issue.as_bytes().as_ref())
        .map_err(|e| crate::Error::SecretWrappingFailed(e.to_string()))?;

    let mut out = Vec::with_capacity(12 + ct.len());
    out.extend_from_slice(&nonce_bytes);
    out.extend_from_slice(&ct);
    Ok(out)
}

pub fn decrypt_s_issue_at_rest(ciphertext: &[u8], at_rest_key: &[u8; 32]) -> crate::Result<SIssue> {
    if ciphertext.len() < 12 {
        return Err(crate::Error::Corrupt("issuance secret too short".into()));
    }
    let (nonce_bytes, ct) = ciphertext.split_at(12);
    let key = Key::<Aes256Gcm>::from_slice(at_rest_key);
    let cipher = Aes256Gcm::new(key);
    let nonce = Nonce::from_slice(nonce_bytes);

    let plaintext = cipher
        .decrypt(nonce, ct)
        .map_err(|_| crate::Error::Corrupt("issuance secret decryption failed".into()))?;

    let bytes: [u8; 32] = plaintext
        .try_into()
        .map_err(|_| crate::Error::Corrupt("issuance secret wrong length".into()))?;
    Ok(SIssue(bytes))
}

#[derive(Clone)]
pub struct WrappedSecret {
    pub ephemeral_pubkey: [u8; 32],
    pub ciphertext: Vec<u8>,
}

pub fn wrap_s_issue_for_airgap(
    s_issue: &SIssue,
    customer_ed25519_pubkey: &[u8; 32],
) -> crate::Result<WrappedSecret> {
    let vk = VerifyingKey::from_bytes(customer_ed25519_pubkey)
        .map_err(|e| crate::Error::SecretWrappingFailed(e.to_string()))?;
    let customer_x25519_bytes: [u8; 32] = vk.to_montgomery().to_bytes();
    let customer_x25519 = PublicKey::from(customer_x25519_bytes);

    let ephemeral_secret = EphemeralSecret::random_from_rng(OsRng);
    let ephemeral_pubkey = PublicKey::from(&ephemeral_secret);

    let shared_secret = ephemeral_secret.diffie_hellman(&customer_x25519);

    let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
    let mut wrap_key = [0u8; 32];
    hk.expand(AIRGAP_WRAP_INFO, &mut wrap_key)
        .map_err(|e| crate::Error::SecretWrappingFailed(e.to_string()))?;

    let key = Key::<Aes256Gcm>::from_slice(&wrap_key);
    let cipher = Aes256Gcm::new(key);
    let mut nonce_bytes = [0u8; 12];
    OsRng.fill_bytes(&mut nonce_bytes);
    let nonce = Nonce::from_slice(&nonce_bytes);
    let ct = cipher
        .encrypt(nonce, s_issue.as_bytes().as_ref())
        .map_err(|e| crate::Error::SecretWrappingFailed(e.to_string()))?;

    let mut ciphertext = Vec::with_capacity(12 + ct.len());
    ciphertext.extend_from_slice(&nonce_bytes);
    ciphertext.extend_from_slice(&ct);

    wrap_key.zeroize();

    Ok(WrappedSecret {
        ephemeral_pubkey: *ephemeral_pubkey.as_bytes(),
        ciphertext,
    })
}