anubis-wormhole 1.0.0

A post-quantum secure file transfer tool based on the Magic Wormhole protocol.
Documentation
use crate::kdf::{hkdf_expand_sha512, hkdf_extract_sha512};
use crate::traits::Kem;
use zeroize::Zeroize;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Role { Initiator, Responder }

/// The input to the Anubis Wormhole key derivation function.
pub struct AnubisDeriveInput<'a> {
    pub pake_secret: &'a [u8],
    pub transcript_hash: &'a [u8],
}

/// The derived keys from the Anubis Wormhole key derivation function.
#[derive(Clone, Debug, Zeroize)]
#[zeroize(drop)]
pub struct AnubisDerivedKeys {
    pub k_ctrl: Vec<u8>,
    pub k_data: Vec<u8>,
    pub k_dilate: Vec<u8>,
    pub k_verify: Vec<u8>,
}

pub fn derive_keys(pake: &[u8], kem_ss: &[u8], transcript_hash: &[u8]) -> AnubisDerivedKeys {
    let mut ikm = Vec::with_capacity(pake.len() + kem_ss.len());
    ikm.extend_from_slice(pake);
    ikm.extend_from_slice(kem_ss);
    let prk = hkdf_extract_sha512(transcript_hash, &ikm);
    let k_ctrl = hkdf_expand_sha512(&prk, b"anubis-ctrl", 32);
    let k_data = hkdf_expand_sha512(&prk, b"anubis-data", 32);
    let k_dilate = hkdf_expand_sha512(&prk, b"anubis-dilate", 32);
    let k_verify = hkdf_expand_sha512(&prk, b"anubis-verify", 32);
    AnubisDerivedKeys { k_ctrl, k_data, k_dilate, k_verify }
}

pub struct InitiatorState<K: Kem> {
    kem: K,
    sk: K::SecretKey,
    _pubkey_sent: bool,
    _derive: Vec<u8>, // serialized derive input: prk preimage marker if needed
    pake: Vec<u8>,
    th: Vec<u8>,
}

impl<K: Kem> InitiatorState<K> {
    pub fn start(kem: K, input: AnubisDeriveInput) -> (Self, K::PublicKey) {
        let (pk, sk) = kem.keypair();
        let st = InitiatorState {
            kem,
            sk,
            _pubkey_sent: true,
            _derive: Vec::new(),
            pake: input.pake_secret.to_vec(),
            th: input.transcript_hash.to_vec(),
        };
        (st, pk)
    }

    pub fn finish(self, ct_b: &K::Ciphertext) -> AnubisDerivedKeys {
        let ss = self.kem.decapsulate(&self.sk, ct_b);
        derive_keys(&self.pake, ss.as_ref(), &self.th)
    }
}

impl<K: Kem> Drop for InitiatorState<K> {
    fn drop(&mut self) {
        use zeroize::Zeroize;
        self.pake.zeroize();
        self.th.zeroize();
    }
}

pub struct ResponderOutput<K: Kem> {
    pub ct: K::Ciphertext,
    pub keys: AnubisDerivedKeys,
}

pub fn responder_process<K: Kem>(kem: &K, input: AnubisDeriveInput, pk_a: &K::PublicKey) -> ResponderOutput<K> {
    let (ct, ss) = kem.encapsulate(pk_a);
    let keys = derive_keys(input.pake_secret, ss.as_ref(), input.transcript_hash);
    ResponderOutput { ct, keys }
}