#![allow(dead_code)]
use alloc::vec::Vec;
use crate::cipher::{Aes128, Aes256, BlockCipher, ChaCha20, ChaCha20Poly1305, Gcm};
use crate::hash::Sha256;
use crate::kdf::hkdf_extract;
use crate::tls::Error;
use crate::tls::crypto::{HashAlg, expand_label_dyn};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum AeadAlg {
Aes128Gcm,
Aes256Gcm,
ChaCha20Poly1305,
}
impl AeadAlg {
pub(crate) const fn key_len(self) -> usize {
match self {
Self::Aes128Gcm => 16,
Self::Aes256Gcm => 32,
Self::ChaCha20Poly1305 => 32,
}
}
pub(crate) const fn hash(self) -> HashAlg {
match self {
Self::Aes128Gcm => HashAlg::Sha256,
Self::Aes256Gcm => HashAlg::Sha384,
Self::ChaCha20Poly1305 => HashAlg::Sha256,
}
}
}
#[derive(Clone)]
pub(crate) struct DirKeys {
pub(crate) alg: AeadAlg,
pub(crate) key: Vec<u8>,
pub(crate) iv: [u8; 12],
pub(crate) hp: HeaderProt,
pub(crate) secret: Vec<u8>,
}
#[derive(Clone)]
pub(crate) enum HeaderProt {
Aes128(Aes128),
Aes256(Aes256),
ChaCha20(ChaCha20),
}
impl HeaderProt {
pub(crate) fn mask(&self, sample: &[u8]) -> Result<[u8; 5], Error> {
if sample.len() != 16 {
return Err(Error::Decode);
}
let mut out = [0u8; 5];
match self {
HeaderProt::Aes128(c) => {
let mut block = [0u8; 16];
block.copy_from_slice(sample);
c.encrypt_block(&mut block);
out.copy_from_slice(&block[..5]);
}
HeaderProt::Aes256(c) => {
let mut block = [0u8; 16];
block.copy_from_slice(sample);
c.encrypt_block(&mut block);
out.copy_from_slice(&block[..5]);
}
HeaderProt::ChaCha20(c) => {
let counter = u32::from_le_bytes(sample[0..4].try_into().expect("16-byte sample"));
let mut nonce = [0u8; 12];
nonce.copy_from_slice(&sample[4..16]);
let ks = c.block(&nonce, counter);
out.copy_from_slice(&ks[..5]);
}
}
Ok(out)
}
}
pub(crate) struct LevelKeys {
pub(crate) tx: Option<DirKeys>,
pub(crate) rx: Option<DirKeys>,
pub(crate) tx_by_phase: [Option<DirKeys>; 2],
pub(crate) rx_by_phase: [Option<DirKeys>; 2],
pub(crate) prev_rx_keys: Option<DirKeys>,
pub(crate) tx_phase_pending_confirm: bool,
pub(crate) tx_hp_key_bytes: Vec<u8>,
pub(crate) rx_hp_key_bytes: Vec<u8>,
}
impl LevelKeys {
pub(crate) const fn empty() -> Self {
Self {
tx: None,
rx: None,
tx_by_phase: [None, None],
rx_by_phase: [None, None],
prev_rx_keys: None,
tx_phase_pending_confirm: false,
tx_hp_key_bytes: Vec::new(),
rx_hp_key_bytes: Vec::new(),
}
}
pub(crate) fn tx_for_phase(&self, phase: u8) -> Option<&DirKeys> {
let p = (phase & 1) as usize;
self.tx_by_phase[p].as_ref().or(self.tx.as_ref())
}
pub(crate) fn rx_for_phase(&self, phase: u8) -> Option<&DirKeys> {
let p = (phase & 1) as usize;
self.rx_by_phase[p].as_ref().or(self.rx.as_ref())
}
}
pub(crate) const INITIAL_SALT_V1: [u8; 20] = [
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad,
0xcc, 0xbb, 0x7f, 0x0a,
];
pub(crate) fn derive_initial_secrets(client_dcid: &[u8]) -> ([u8; 32], [u8; 32]) {
let initial_secret = hkdf_extract::<Sha256>(&INITIAL_SALT_V1, client_dcid);
let mut cs = [0u8; 32];
let mut ss = [0u8; 32];
expand_label_dyn(
HashAlg::Sha256,
initial_secret.as_ref(),
b"client in",
&[],
&mut cs,
);
expand_label_dyn(
HashAlg::Sha256,
initial_secret.as_ref(),
b"server in",
&[],
&mut ss,
);
(cs, ss)
}
pub(crate) fn derive_dir_keys(alg: AeadAlg, secret: &[u8]) -> DirKeys {
let hash = alg.hash();
let kl = alg.key_len();
let mut key = alloc::vec![0u8; kl];
let mut iv = [0u8; 12];
expand_label_dyn(hash, secret, b"quic key", &[], &mut key);
expand_label_dyn(hash, secret, b"quic iv", &[], &mut iv);
let mut hp_key = alloc::vec![0u8; kl];
expand_label_dyn(hash, secret, b"quic hp", &[], &mut hp_key);
let hp = match alg {
AeadAlg::Aes128Gcm => HeaderProt::Aes128(Aes128::new(hp_key[..16].try_into().expect("16"))),
AeadAlg::Aes256Gcm => HeaderProt::Aes256(Aes256::new(hp_key[..32].try_into().expect("32"))),
AeadAlg::ChaCha20Poly1305 => {
HeaderProt::ChaCha20(ChaCha20::new(hp_key[..32].try_into().expect("32")))
}
};
DirKeys {
alg,
key,
iv,
hp,
secret: secret.to_vec(),
}
}
pub(crate) fn derive_next_application_secret(alg: AeadAlg, current: &[u8]) -> Vec<u8> {
let mut next = alloc::vec![0u8; current.len()];
expand_label_dyn(alg.hash(), current, b"quic ku", &[], &mut next);
next
}
pub(crate) fn derive_dir_keys_preserve_hp(
alg: AeadAlg,
secret: &[u8],
hp_key_bytes: &[u8],
) -> DirKeys {
let hash = alg.hash();
let kl = alg.key_len();
let mut key = alloc::vec![0u8; kl];
let mut iv = [0u8; 12];
expand_label_dyn(hash, secret, b"quic key", &[], &mut key);
expand_label_dyn(hash, secret, b"quic iv", &[], &mut iv);
let hp = match alg {
AeadAlg::Aes128Gcm => {
HeaderProt::Aes128(Aes128::new(hp_key_bytes[..16].try_into().expect("16")))
}
AeadAlg::Aes256Gcm => {
HeaderProt::Aes256(Aes256::new(hp_key_bytes[..32].try_into().expect("32")))
}
AeadAlg::ChaCha20Poly1305 => {
HeaderProt::ChaCha20(ChaCha20::new(hp_key_bytes[..32].try_into().expect("32")))
}
};
DirKeys {
alg,
key,
iv,
hp,
secret: secret.to_vec(),
}
}
pub(crate) fn derive_hp_key_bytes(alg: AeadAlg, secret: &[u8]) -> Vec<u8> {
let hash = alg.hash();
let kl = alg.key_len();
let mut hp_key = alloc::vec![0u8; kl];
expand_label_dyn(hash, secret, b"quic hp", &[], &mut hp_key);
hp_key
}
pub(crate) fn nonce_for(iv: &[u8; 12], packet_number: u64) -> [u8; 12] {
let mut nonce = *iv;
let pn = packet_number.to_be_bytes();
for i in 0..8 {
nonce[4 + i] ^= pn[i];
}
nonce
}
pub(crate) fn aead_seal(
keys: &DirKeys,
packet_number: u64,
aad: &[u8],
plaintext_in_place: &mut [u8],
) -> [u8; 16] {
let nonce = nonce_for(&keys.iv, packet_number);
match keys.alg {
AeadAlg::Aes128Gcm => {
let aes = Aes128::new(keys.key[..16].try_into().expect("16"));
let g: Gcm<Aes128> = Gcm::new(aes);
g.encrypt(&nonce, aad, plaintext_in_place)
}
AeadAlg::Aes256Gcm => {
let aes = Aes256::new(keys.key[..32].try_into().expect("32"));
let g: Gcm<Aes256> = Gcm::new(aes);
g.encrypt(&nonce, aad, plaintext_in_place)
}
AeadAlg::ChaCha20Poly1305 => {
let c = ChaCha20Poly1305::new(keys.key[..32].try_into().expect("32"));
c.encrypt(&nonce, aad, plaintext_in_place)
}
}
}
pub(crate) fn aead_open(
keys: &DirKeys,
packet_number: u64,
aad: &[u8],
ciphertext_in_place: &mut [u8],
tag: &[u8; 16],
) -> Result<(), Error> {
let nonce = nonce_for(&keys.iv, packet_number);
let ok = match keys.alg {
AeadAlg::Aes128Gcm => {
let aes = Aes128::new(keys.key[..16].try_into().expect("16"));
let g: Gcm<Aes128> = Gcm::new(aes);
g.decrypt(&nonce, aad, ciphertext_in_place, tag).is_ok()
}
AeadAlg::Aes256Gcm => {
let aes = Aes256::new(keys.key[..32].try_into().expect("32"));
let g: Gcm<Aes256> = Gcm::new(aes);
g.decrypt(&nonce, aad, ciphertext_in_place, tag).is_ok()
}
AeadAlg::ChaCha20Poly1305 => {
let c = ChaCha20Poly1305::new(keys.key[..32].try_into().expect("32"));
c.decrypt(&nonce, aad, ciphertext_in_place, tag).is_ok()
}
};
if ok { Ok(()) } else { Err(Error::BadRecordMac) }
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(s: &str) -> Vec<u8> {
assert!(s.len().is_multiple_of(2), "hex length must be even");
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("hex"))
.collect()
}
const DCID: [u8; 8] = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];
#[test]
fn rfc9001_a1_initial_secrets() {
let (cs, ss) = derive_initial_secrets(&DCID);
assert_eq!(
cs.as_slice(),
hex("c00cf151ca5be075ed0ebfb5c80323c42d6b7db67881289af4008f1f6c357aea").as_slice(),
);
assert_eq!(
ss.as_slice(),
hex("3c199828fd139efd216c155ad844cc81fb82fa8d7446fa7d78be803acdda951b").as_slice(),
);
}
#[test]
fn rfc9001_a1_client_dir_keys() {
let (cs, _) = derive_initial_secrets(&DCID);
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &cs);
assert_eq!(dk.key.as_slice(), hex("1f369613dd76d5467730efcbe3b1a22d"));
assert_eq!(dk.iv.as_slice(), hex("fa044b2f42a3fd3b46fb255c"));
let mut hp_key = [0u8; 16];
expand_label_dyn(HashAlg::Sha256, &cs, b"quic hp", &[], &mut hp_key);
assert_eq!(&hp_key[..], hex("9f50449e04a0e810283a1e9933adedd2"));
}
#[test]
fn rfc9001_a1_server_dir_keys() {
let (_, ss) = derive_initial_secrets(&DCID);
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &ss);
assert_eq!(dk.key.as_slice(), hex("cf3a5331653c364c88f0f379b6067e37"));
assert_eq!(dk.iv.as_slice(), hex("0ac1493ca1905853b0bba03e"));
let mut hp_key = [0u8; 16];
expand_label_dyn(HashAlg::Sha256, &ss, b"quic hp", &[], &mut hp_key);
assert_eq!(&hp_key[..], hex("c206b8d9b9f0f37644430b490eeaa314"));
}
fn a2_plaintext() -> Vec<u8> {
let mut p = hex(
"060040f1010000ed0303ebf8fa56f12939b9584a3896472ec40bb863cfd3e868\
04fe3a47f06a2b69484c000004130113\
02010000c000000010000e00000b6578\
616d706c652e636f6dff01000100000a\
00080006001d00170018001000070005\
04616c706e000500050100000000\
003300260024001d00209370b2c9caa47fba\
baf4559fedba753de171fa71f50f1ce1\
5d43e994ec74d748002b00030203040\
00d0010000e040305030603020308040\
8050806002d00020101001c00024001\
003900320408ffffffffffffffff050480\
00ffff07048000ffff080110010480\
0075300901100f088394c8f03e515708\
06048000ffff",
);
assert_eq!(p.len(), 245, "RFC 9001 §A.2 CRYPTO frame is 245 bytes");
p.resize(1162, 0);
p
}
#[test]
fn rfc9001_a2_nonce() {
let (cs, _) = derive_initial_secrets(&DCID);
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &cs);
let nonce = nonce_for(&dk.iv, 2);
assert_eq!(nonce.as_slice(), hex("fa044b2f42a3fd3b46fb255e"));
}
#[test]
fn rfc9001_a2_client_initial_seal() {
let header = hex("c300000001088394c8f03e5157080000449e00000002");
let mut payload = a2_plaintext();
let (cs, _) = derive_initial_secrets(&DCID);
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &cs);
let tag = aead_seal(&dk, 2, &header, &mut payload);
assert_eq!(
&payload[..16],
hex("d1b1c98dd7689fb8ec11d242b123dc9b").as_slice()
);
assert_eq!(tag.as_slice(), hex("e221af44860018ab0856972e194cd934"));
}
#[test]
fn rfc9001_a2_header_protection_mask() {
let (cs, _) = derive_initial_secrets(&DCID);
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &cs);
let sample = hex("d1b1c98dd7689fb8ec11d242b123dc9b");
let mask = dk.hp.mask(&sample).expect("16-byte sample");
assert_eq!(mask.as_slice(), hex("437b9aec36"));
}
#[test]
fn rfc9001_a3_server_initial_seal() {
let payload = hex(
"02000000000600405a020000560303eefce7f7b37ba1d1632e96677825ddf73988cfc79825df566d\
c5430b9a045a1200130100002e00330024001d00209d3c940d89690b84d08a60993c144eca684d10\
81287c834d5311bcf32bb9da1a002b00020304",
);
let header = hex("c1000000010008f067a5502a4262b50040750001");
let mut buf = payload.clone();
let (_, ss) = derive_initial_secrets(&DCID);
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &ss);
let tag = aead_seal(&dk, 1, &header, &mut buf);
assert_eq!(
&buf[2..18],
hex("2cd0991cd25b0aac406a5816b6394100").as_slice()
);
assert_eq!(tag.as_slice(), hex("3d20398c276456cbc42158407dd074ee"));
}
#[test]
fn rfc9001_a3_header_protection_mask() {
let (_, ss) = derive_initial_secrets(&DCID);
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &ss);
let sample = hex("2cd0991cd25b0aac406a5816b6394100");
let mask = dk.hp.mask(&sample).expect("16-byte sample");
assert_eq!(mask.as_slice(), hex("2ec0d8356a"));
}
fn a5_secret() -> Vec<u8> {
hex("9ac312a7f877468ebe69422748ad00a15443f18203a07d6060f688f30f21632b")
}
#[test]
fn rfc9001_a5_key_derivation() {
let s = a5_secret();
let dk = derive_dir_keys(AeadAlg::ChaCha20Poly1305, &s);
assert_eq!(
dk.key.as_slice(),
hex("c6d98ff3441c3fe1b2182094f69caa2ed4b716b65488960a7a984979fb23e1c8").as_slice(),
);
assert_eq!(dk.iv.as_slice(), hex("e0459b3474bdd0e44a41c144").as_slice());
let mut hp_key = [0u8; 32];
expand_label_dyn(HashAlg::Sha256, &s, b"quic hp", &[], &mut hp_key);
assert_eq!(
&hp_key[..],
hex("25a282b9e82f06f21f488917a4fc8f1b73573685608597d0efcb076b0ab7a7a4").as_slice(),
);
let ku = derive_next_application_secret(AeadAlg::ChaCha20Poly1305, &s);
assert_eq!(
ku.as_slice(),
hex("1223504755036d556342ee9361d253421a826c9ecdf3c7148684b36b714881f9").as_slice(),
);
}
#[test]
fn rfc9001_a5_nonce_and_seal() {
let s = a5_secret();
let dk = derive_dir_keys(AeadAlg::ChaCha20Poly1305, &s);
let pn: u64 = 654360564;
let nonce = nonce_for(&dk.iv, pn);
assert_eq!(nonce.as_slice(), hex("e0459b3474bdd0e46d417eb0").as_slice());
let header = hex("4200bff4");
let mut payload = hex("01");
let tag = aead_seal(&dk, pn, &header, &mut payload);
let mut got = payload.clone();
got.extend_from_slice(&tag);
assert_eq!(
got.as_slice(),
hex("655e5cd55c41f69080575d7999c25a5bfb").as_slice(),
);
}
#[test]
fn rfc9001_a5_header_protection_mask() {
let s = a5_secret();
let dk = derive_dir_keys(AeadAlg::ChaCha20Poly1305, &s);
let sample = hex("5e5cd55c41f69080575d7999c25a5bfb");
let mask = dk.hp.mask(&sample).expect("16-byte sample");
assert_eq!(mask.as_slice(), hex("aefefe7d03").as_slice());
}
#[test]
fn nonce_for_xors_into_lower_8_bytes() {
let nonce = nonce_for(&[0x11; 12], 0x42);
let mut expected = [0x11u8; 12];
expected[11] ^= 0x42;
assert_eq!(nonce, expected);
assert_eq!(&nonce[..4], &[0x11, 0x11, 0x11, 0x11]);
}
#[test]
fn nonce_for_full_pn_overlay() {
let n = nonce_for(&[0; 12], 0x0102030405060708);
assert_eq!(
n,
[0, 0, 0, 0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
);
}
#[test]
fn header_protection_wrong_sample_length() {
let (cs, _) = derive_initial_secrets(&DCID);
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &cs);
assert!(matches!(dk.hp.mask(&[0u8; 15]), Err(Error::Decode)));
assert!(matches!(dk.hp.mask(&[0u8; 17]), Err(Error::Decode)));
assert!(matches!(dk.hp.mask(&[]), Err(Error::Decode)));
}
#[test]
fn crypto_key_update_chain() {
let s0: alloc::vec::Vec<u8> = (0..32u8).map(|i| i ^ 0x5a).collect();
let s1 = derive_next_application_secret(AeadAlg::Aes128Gcm, &s0);
let s2 = derive_next_application_secret(AeadAlg::Aes128Gcm, &s1);
assert_ne!(s0, s1, "S0 vs S1 must differ");
assert_ne!(s1, s2, "S1 vs S2 must differ");
assert_ne!(s0, s2, "S0 vs S2 must differ");
assert_eq!(s0.len(), s1.len());
assert_eq!(s1.len(), s2.len());
let dk0 = derive_dir_keys(AeadAlg::Aes128Gcm, &s0);
let dk1 = derive_dir_keys(AeadAlg::Aes128Gcm, &s1);
let dk2 = derive_dir_keys(AeadAlg::Aes128Gcm, &s2);
assert_ne!(dk0.key, dk1.key);
assert_ne!(dk1.key, dk2.key);
assert_ne!(dk0.key, dk2.key);
assert_ne!(dk0.iv, dk1.iv);
assert_ne!(dk1.iv, dk2.iv);
assert_ne!(dk0.iv, dk2.iv);
let mut hp0 = [0u8; 16];
let mut hp1 = [0u8; 16];
let mut hp2 = [0u8; 16];
expand_label_dyn(HashAlg::Sha256, &s0, b"quic hp", &[], &mut hp0);
expand_label_dyn(HashAlg::Sha256, &s1, b"quic hp", &[], &mut hp1);
expand_label_dyn(HashAlg::Sha256, &s2, b"quic hp", &[], &mut hp2);
assert_ne!(hp0, hp1);
assert_ne!(hp1, hp2);
assert_ne!(hp0, hp2);
}
#[test]
fn level_keys_phase_lookup_falls_back_to_legacy() {
let dk = derive_dir_keys(AeadAlg::Aes128Gcm, &alloc::vec![0u8; 32]);
let lk = LevelKeys {
tx: Some(DirKeys {
alg: dk.alg,
key: dk.key.clone(),
iv: dk.iv,
hp: match dk.alg {
AeadAlg::Aes128Gcm => {
HeaderProt::Aes128(Aes128::new(dk.key[..16].try_into().unwrap()))
}
_ => unreachable!(),
},
secret: dk.secret.clone(),
}),
rx: None,
tx_by_phase: [None, None],
rx_by_phase: [None, None],
prev_rx_keys: None,
tx_phase_pending_confirm: false,
tx_hp_key_bytes: Vec::new(),
rx_hp_key_bytes: Vec::new(),
};
assert!(lk.tx_for_phase(0).is_some());
assert!(lk.tx_for_phase(1).is_some());
assert!(lk.rx_for_phase(0).is_none());
}
#[test]
fn aead_open_round_trips_seal() {
for &alg in &[
AeadAlg::Aes128Gcm,
AeadAlg::Aes256Gcm,
AeadAlg::ChaCha20Poly1305,
] {
let secret =
alloc::vec![0x55u8; if matches!(alg, AeadAlg::Aes256Gcm) { 48 } else { 32 }];
let dk = derive_dir_keys(alg, &secret);
let aad = [0xc3, 0x00, 0x01];
let original: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let mut buf = original;
let tag = aead_seal(&dk, 7, &aad, &mut buf);
assert_ne!(buf, original);
aead_open(&dk, 7, &aad, &mut buf, &tag).expect("decrypt ok");
assert_eq!(buf, original);
let mut bad_tag = tag;
bad_tag[0] ^= 0x01;
let mut buf2 = original;
let _ = aead_seal(&dk, 7, &aad, &mut buf2);
assert!(aead_open(&dk, 7, &aad, &mut buf2, &bad_tag).is_err());
}
}
}