use crate::aead_policy::{AAD, Flags, RecordType, Role, sc, capabilities_hash16};
use crate::frame::Frame;
use crate::subchannel::SubchSend;
use crate::providers::aes_gcm_siv::Aes256GcmSivProvider;
use crate::handshake::{derive_keys, AnubisDerivedKeys};
use zeroize::Zeroize;
use crate::transcript::Transcript;
#[cfg(feature = "policy-l5")]
use crate::policy;
#[cfg(feature = "providers-pqclean")]
use crate::providers::mlkem::MlKem1024;
#[cfg(feature = "providers-pqclean")]
use crate::traits::Kem;
use sha2::{Digest, Sha384};
#[derive(Clone, Debug)]
pub struct Suite {
pub kdf: &'static str,
pub kem: &'static str,
pub aead: &'static str,
}
pub const SUITE_ANUBIS: Suite = Suite {
kdf: "HKDF-SHA512",
kem: "ML-KEM-1024",
aead: "AES-256-GCM-SIV",
};
#[derive(Clone, Debug)]
pub struct Hello {
pub versions: serde_json::Value,
pub capabilities: serde_json::Value,
pub suite: Suite,
}
#[derive(Clone, Debug)]
pub struct HelloAck { pub suite: Suite }
#[derive(Clone, Debug)]
pub enum ControlMsg {
Hello(Hello),
HelloAck(HelloAck),
SpakeA(Vec<u8>),
SpakeB(Vec<u8>),
KemPk(Vec<u8>),
KemCt(Vec<u8>),
Confirm(Vec<u8>),
RekeyStart { epoch: u32, kem_pk: Vec<u8> },
RekeyCt { epoch: u32, kem_ct: Vec<u8> },
RekeyDone { epoch: u32 },
}
use async_trait::async_trait;
#[async_trait]
pub trait PhaseIO {
async fn send(&mut self, bytes: &[u8]);
async fn recv(&mut self) -> Option<Vec<u8>>;
}
pub struct ControlDriver<'a, IO: PhaseIO> {
pub io: &'a mut IO,
pub role: Role,
pub session_id: [u8; 16],
pub epoch: u32,
pub suite: Suite,
}
impl<'a, IO: PhaseIO> ControlDriver<'a, IO> {
pub fn aead_seal(&self, key: &[u8;32], seq: u64, caps_hash: Option<[u8;16]>, rtype: RecordType, flags: Flags, plaintext: &[u8]) -> Vec<u8> {
let aead = Aes256GcmSivProvider;
let aad = AAD {
version: 1,
role: self.role,
record_type: rtype,
subchannel: sc::CONTROL,
seq,
flags,
session_id: self.session_id,
capabilities_hash16: caps_hash,
};
Frame::seal(&aead, key, &aad, plaintext).expect("seal")
}
pub fn aead_open_sender(&self, sender_role: Role, key: &[u8;32], seq: u64, caps_hash: Option<[u8;16]>, rtype: RecordType, flags: Flags, ciphertext: &[u8]) -> Vec<u8> {
let aead = Aes256GcmSivProvider;
let aad = AAD {
version: 1,
role: sender_role,
record_type: rtype,
subchannel: sc::CONTROL,
seq,
flags,
session_id: self.session_id,
capabilities_hash16: caps_hash,
};
Frame::open(&aead, key, &aad, ciphertext).expect("open")
}
}
pub struct InitiatorKeys { pub keys: AnubisDerivedKeys, pub cap_hash16: [u8;16] }
pub async fn initiator_handshake_over<IO: PhaseIO + Send + Sync>(io: &mut IO, code: &str, versions: serde_json::Value, capabilities: serde_json::Value) -> (InitiatorKeys, [u8;16]) {
let hello = Hello { versions: versions.clone(), capabilities: capabilities.clone(), suite: SUITE_ANUBIS };
let hello_bytes = serde_json::to_vec(&serde_json::json!({"hello":{"suite":hello.suite.aead,"versions":hello.versions,"capabilities":hello.capabilities}})).unwrap();
io.send(&hello_bytes).await;
let _ack = HelloAck { suite: SUITE_ANUBIS };
use spake2::{Ed25519Group, Identity, Password, Spake2};
let pw = Password::new(code.as_bytes());
let (state_a, msg_a) = Spake2::<Ed25519Group>::start_a(&pw, &Identity::new(&[]), &Identity::new(&[]));
io.send(&msg_a).await;
let msg_b = io.recv().await.expect("spake msg_b");
let pake_key = state_a.finish(&msg_b).expect("spake finish");
let mut tr = Transcript::new();
tr.absorb("spake.msg_a", &msg_a);
tr.absorb("spake.msg_b", &msg_b);
let th = tr.finish();
#[cfg(feature = "providers-pqclean")]
let keys = {
let kem = MlKem1024;
let (pk_a, sk_a) = kem.keypair();
io.send(&pk_a).await;
let ct_b = io.recv().await.expect("kem ct");
let ss_a = kem.decapsulate(&sk_a, &ct_b);
derive_keys(&pake_key, ss_a.as_ref(), &th)
};
#[cfg(not(feature = "providers-pqclean"))]
let keys = {
derive_keys(&pake_key, &[], &th)
};
let mut key_arr = [0u8;32]; key_arr.copy_from_slice(&keys.k_ctrl[..32]);
let cap_obj = serde_json::json!({
"suite": SUITE_ANUBIS.aead,
"versions": versions,
"capabilities": capabilities,
"sec_cat": 5,
"kem": SUITE_ANUBIS.kem,
"aead": SUITE_ANUBIS.aead,
"kdf": SUITE_ANUBIS.kdf
});
#[cfg(feature = "policy-l5")]
{ if policy::l5_enforcement_enabled() { let _ = policy::assert_level5_caps(&cap_obj); } }
let cap = serde_json::to_vec(&cap_obj).unwrap();
let cap_hash16 = capabilities_hash16(&cap);
let mut h = Sha384::new(); h.update(&keys.k_verify); let sas_raw = h.finalize(); let sas = &sas_raw[..8];
let mut sc = SubchSend::new(sc::CONTROL).with_capacity(16);
sc.enqueue(&key_arr, session_id_from_th(&th), Role::Initiator, RecordType::Control, sas, false).expect("enqueue confirm");
if let Some(ct) = sc.dequeue() { io.send(&ct).await; }
#[cfg(feature = "policy-l5")]
{ if policy::l5_enforcement_enabled() { let _ = policy::assert_level5(SUITE_ANUBIS.kem, None); } }
let out = (InitiatorKeys { keys, cap_hash16 }, session_id_from_th(&th));
{
let mut mb = msg_b.clone(); mb.zeroize();
}
out
}
pub struct ResponderKeys { pub keys: AnubisDerivedKeys, pub cap_hash16: [u8;16] }
pub async fn responder_handshake_over<IO: PhaseIO + Send + Sync>(io: &mut IO, code: &str, versions: serde_json::Value, capabilities: serde_json::Value) -> (ResponderKeys, [u8;16]) {
use spake2::{Ed25519Group, Identity, Password, Spake2};
let pw = Password::new(code.as_bytes());
let (state_b, msg_b) = Spake2::<Ed25519Group>::start_b(&pw, &Identity::new(&[]), &Identity::new(&[]));
let msg_a = io.recv().await.expect("spake msg_a");
io.send(&msg_b).await;
let pake_key = state_b.finish(&msg_a).expect("spake finish");
let mut tr = Transcript::new();
tr.absorb("spake.msg_a", &msg_a);
tr.absorb("spake.msg_b", &msg_b);
let th = tr.finish();
#[cfg(feature = "providers-pqclean")]
let keys = {
let kem = MlKem1024;
let pk_from_initiator = io.recv().await.expect("kem pk");
let (ct_b, ss_b) = kem.encapsulate(&pk_from_initiator);
io.send(&ct_b).await;
derive_keys(&pake_key, ss_b.as_ref(), &th)
};
#[cfg(not(feature = "providers-pqclean"))]
let keys = {
derive_keys(&pake_key, &[], &th)
};
let cap_obj = serde_json::json!({
"suite": SUITE_ANUBIS.aead,
"versions": versions,
"capabilities": capabilities,
"sec_cat": 5,
"kem": SUITE_ANUBIS.kem,
"aead": SUITE_ANUBIS.aead,
"kdf": SUITE_ANUBIS.kdf
});
#[cfg(feature = "policy-l5")]
{ if policy::l5_enforcement_enabled() { let _ = policy::assert_level5_caps(&cap_obj); } }
let cap = serde_json::to_vec(&cap_obj).unwrap();
let cap_hash16 = capabilities_hash16(&cap);
let sess = session_id_from_th(&th);
let mut key_arr = [0u8;32]; key_arr.copy_from_slice(&keys.k_ctrl[..32]);
let ct = io.recv().await.expect("confirm ct");
let driver = ControlDriver { io, role: Role::Responder, session_id: sess, epoch: 0, suite: SUITE_ANUBIS };
#[allow(unused_mut)]
let mut flags = Flags::empty();
#[cfg(feature = "policy-l5")]
{
if policy::l5_enforcement_enabled() {
flags |= Flags::L5_POLICY;
}
}
let _sas_plain = driver.aead_open_sender(Role::Initiator, &key_arr, 0, Some(cap_hash16), RecordType::Control, flags, &ct);
{
let mut ma = msg_a.clone(); ma.zeroize();
let mut mb = msg_b.clone(); mb.zeroize();
}
#[cfg(feature = "policy-l5")]
{ if policy::l5_enforcement_enabled() { let _ = policy::assert_level5(SUITE_ANUBIS.kem, None); } }
(ResponderKeys { keys, cap_hash16 }, sess)
}
fn session_id_from_th(th: &[u8]) -> [u8;16] {
let sid_raw = sha2::Sha512::digest([b"anubis/session_id".as_ref(), th].concat());
let mut session_id = [0u8; 16];
session_id.copy_from_slice(&sid_raw[..16]);
session_id
}
pub async fn exchange_transit_info_over<IO: PhaseIO + Send + Sync>(io: &mut IO, keys: &AnubisDerivedKeys, role: Role, session_id: [u8;16], my_info_json: serde_json::Value) -> serde_json::Value {
let mut key = [0u8;32]; key.copy_from_slice(&keys.k_ctrl[..32]);
let driver = ControlDriver { io, role, session_id, epoch: 0, suite: SUITE_ANUBIS };
match role {
Role::Initiator => {
let pt = serde_json::to_vec(&my_info_json).expect("json");
let ct = driver.aead_seal(&key, 2, None, RecordType::Control, Flags::empty(), &pt);
driver.io.send(&ct).await;
let peer_ct = driver.io.recv().await.expect("peer transit info");
let peer_pt = driver.aead_open_sender(Role::Responder, &key, 1, None, RecordType::Control, Flags::empty(), &peer_ct);
serde_json::from_slice(&peer_pt).expect("peer json")
}
Role::Responder => {
let peer_ct = driver.io.recv().await.expect("peer transit info");
let peer_pt = driver.aead_open_sender(Role::Initiator, &key, 2, None, RecordType::Control, Flags::empty(), &peer_ct);
let pt = serde_json::to_vec(&my_info_json).expect("json");
let ct = driver.aead_seal(&key, 1, None, RecordType::Control, Flags::empty(), &pt);
driver.io.send(&ct).await;
serde_json::from_slice(&peer_pt).expect("peer json")
}
}
}
pub async fn send_offer_over_control<IO: PhaseIO + Send + Sync>(io: &mut IO, keys: &AnubisDerivedKeys, role: Role, session_id: [u8;16], offer_json: serde_json::Value) -> Option<serde_json::Value> {
let mut key = [0u8;32]; key.copy_from_slice(&keys.k_ctrl[..32]);
let driver = ControlDriver { io, role, session_id, epoch: 0, suite: SUITE_ANUBIS };
match role {
Role::Initiator => {
let pt = serde_json::to_vec(&offer_json).ok()?;
let ct = driver.aead_seal(&key, 3, None, RecordType::Control, Flags::empty(), &pt);
driver.io.send(&ct).await;
None
}
Role::Responder => {
let peer_ct = driver.io.recv().await?;
let pt = driver.aead_open_sender(Role::Initiator, &key, 3, None, RecordType::Control, Flags::empty(), &peer_ct);
serde_json::from_slice(&pt).ok()
}
}
}
pub async fn initiator_rekey<IO: PhaseIO + Send + Sync>(io: &mut IO, old_keys: &AnubisDerivedKeys, epoch: u32) -> AnubisDerivedKeys {
#[cfg(feature = "providers-pqclean")]
{
let kem = MlKem1024;
let (pk, sk) = kem.keypair();
let msg = serde_json::to_vec(&serde_json::json!({"rk_start":{"epoch": epoch+1, "pk": hex::encode(&pk)}})).unwrap();
io.send(&msg).await;
let ct_msg = io.recv().await.expect("rk ct");
let val: serde_json::Value = serde_json::from_slice(&ct_msg).unwrap();
let ct_hex = val["rk_ct"]["ct"].as_str().unwrap();
let ct = hex::decode(ct_hex).unwrap();
let ss = kem.decapsulate(&sk, &ct);
let mut t = Transcript::new();
t.absorb("epoch", &(epoch+1).to_be_bytes());
t.absorb("kverify", &old_keys.k_verify);
let th = t.finish();
return derive_keys(&old_keys.k_verify, ss.as_ref(), &th);
}
#[cfg(not(feature = "providers-pqclean"))]
{
return old_keys.clone();
}
}
pub async fn responder_rekey<IO: PhaseIO + Send + Sync>(io: &mut IO, old_keys: &AnubisDerivedKeys, _epoch: u32) -> AnubisDerivedKeys {
#[cfg(feature = "providers-pqclean")]
{
let kem = MlKem1024;
let msg = io.recv().await.expect("rk start");
let v: serde_json::Value = serde_json::from_slice(&msg).unwrap();
let nxt = v["rk_start"]["epoch"].as_u64().unwrap() as u32;
let pk_hex = v["rk_start"]["pk"].as_str().unwrap();
let pk = hex::decode(pk_hex).unwrap();
let (ct, ss) = kem.encapsulate(&pk);
let ct_msg = serde_json::to_vec(&serde_json::json!({"rk_ct":{"epoch": nxt, "ct": hex::encode(&ct)}})).unwrap();
io.send(&ct_msg).await;
let mut t = Transcript::new();
t.absorb("epoch", &nxt.to_be_bytes());
t.absorb("kverify", &old_keys.k_verify);
let th = t.finish();
return derive_keys(&old_keys.k_verify, ss.as_ref(), &th);
}
#[cfg(not(feature = "providers-pqclean"))]
{
return old_keys.clone();
}
}