use super::{
CipherSuite, Error, HpkeAead, HpkeKdf, HpkeKem, Mode, SenderContext, open as oneshot_open,
seal as oneshot_seal, setup_receiver, setup_receiver_auth, setup_receiver_auth_psk,
setup_receiver_psk, setup_sender, setup_sender_auth, setup_sender_auth_psk, setup_sender_psk,
};
use crate::rng::{HmacDrbg, RngCore};
struct ScriptRng<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> ScriptRng<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
}
impl<'a> RngCore for ScriptRng<'a> {
fn fill_bytes(&mut self, dest: &mut [u8]) {
let take = dest.len().min(self.bytes.len() - self.pos);
dest[..take].copy_from_slice(&self.bytes[self.pos..self.pos + take]);
self.pos += take;
}
}
fn hex(s: &str) -> alloc::vec::Vec<u8> {
let mut out = alloc::vec::Vec::with_capacity(s.len() / 2);
let mut byte = 0u8;
let mut hi = true;
for c in s.bytes() {
if c == b' ' || c == b'\n' || c == b'\t' {
continue;
}
let nibble = match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'f' => c - b'a' + 10,
b'A'..=b'F' => c - b'A' + 10,
_ => panic!("non-hex char {c:#x}"),
};
if hi {
byte = nibble << 4;
} else {
byte |= nibble;
out.push(byte);
}
hi = !hi;
}
assert!(hi, "odd-length hex literal");
out
}
fn drbg() -> HmacDrbg<crate::hash::Sha256> {
HmacDrbg::<crate::hash::Sha256>::new(b"hpke test seed", b"nonce", b"")
}
fn all_suites() -> alloc::vec::Vec<CipherSuite> {
let mut out = alloc::vec::Vec::new();
for kem in [
HpkeKem::DhkemX25519HkdfSha256,
HpkeKem::DhkemP256HkdfSha256,
HpkeKem::DhkemP384HkdfSha384,
HpkeKem::DhkemP521HkdfSha512,
] {
for kdf in [
HpkeKdf::HkdfSha256,
HpkeKdf::HkdfSha384,
HpkeKdf::HkdfSha512,
] {
for aead in [
HpkeAead::Aes128Gcm,
HpkeAead::Aes256Gcm,
HpkeAead::ChaCha20Poly1305,
HpkeAead::ExportOnly,
] {
out.push(CipherSuite::new(kem, kdf, aead));
}
}
}
out
}
#[test]
fn ids_match_rfc9180_table() {
assert_eq!(HpkeKem::DhkemP256HkdfSha256.id(), 0x0010);
assert_eq!(HpkeKem::DhkemP384HkdfSha384.id(), 0x0011);
assert_eq!(HpkeKem::DhkemP521HkdfSha512.id(), 0x0012);
assert_eq!(HpkeKem::DhkemX25519HkdfSha256.id(), 0x0020);
assert_eq!(HpkeKdf::HkdfSha256.id(), 0x0001);
assert_eq!(HpkeKdf::HkdfSha384.id(), 0x0002);
assert_eq!(HpkeKdf::HkdfSha512.id(), 0x0003);
assert_eq!(HpkeAead::Aes128Gcm.id(), 0x0001);
assert_eq!(HpkeAead::Aes256Gcm.id(), 0x0002);
assert_eq!(HpkeAead::ChaCha20Poly1305.id(), 0x0003);
assert_eq!(HpkeAead::ExportOnly.id(), 0xFFFF);
}
#[test]
fn base_mode_roundtrip_full_matrix() {
let info = b"hpke base info";
let aad = b"aad bytes";
let pt = b"plaintext message";
let mut rng = drbg();
for suite in all_suites() {
let (sk_r, pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let (enc, mut sender) = setup_sender(&mut rng, suite, &pk_r, info).unwrap();
let mut receiver = setup_receiver(suite, &enc, &sk_r, info).unwrap();
if suite.aead.is_export_only() {
assert_eq!(sender.seal(aad, pt), Err(Error::ExportOnly));
assert_eq!(receiver.open(aad, &[]), Err(Error::ExportOnly));
} else {
for i in 0u8..3 {
let mut pt_i = pt.to_vec();
pt_i.push(i);
let ct = sender.seal(aad, &pt_i).unwrap();
assert_eq!(receiver.open(aad, &ct).unwrap(), pt_i);
}
}
let exp_s = sender.export(b"exporter ctx", 32).unwrap();
let exp_r = receiver.export(b"exporter ctx", 32).unwrap();
assert_eq!(exp_s, exp_r);
}
}
#[test]
fn psk_mode_roundtrip() {
let info = b"info";
let aad = b"aad";
let pt = b"plaintext";
let psk = b"a pre-shared key";
let psk_id = b"psk identifier";
let mut rng = drbg();
let suite = CipherSuite::new(
HpkeKem::DhkemX25519HkdfSha256,
HpkeKdf::HkdfSha256,
HpkeAead::ChaCha20Poly1305,
);
let (sk_r, pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let (enc, mut sender) = setup_sender_psk(&mut rng, suite, &pk_r, info, psk, psk_id).unwrap();
let mut receiver = setup_receiver_psk(suite, &enc, &sk_r, info, psk, psk_id).unwrap();
let ct = sender.seal(aad, pt).unwrap();
assert_eq!(receiver.open(aad, &ct).unwrap(), pt);
}
#[test]
fn psk_input_emptiness_rejected() {
let mut rng = drbg();
let suite = CipherSuite::new(
HpkeKem::DhkemX25519HkdfSha256,
HpkeKdf::HkdfSha256,
HpkeAead::Aes128Gcm,
);
let (_sk_r, pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let err = SenderContext::new(suite, Mode::Base, &[0u8; 32], b"info", b"psk", b"id");
assert!(matches!(err, Err(Error::PskInputsInconsistent)));
let err = setup_sender_psk(&mut rng, suite, &pk_r, b"info", b"", b"");
assert!(matches!(err, Err(Error::PskInputsInconsistent)));
let err = setup_sender_psk(&mut rng, suite, &pk_r, b"info", b"psk", b"");
assert!(matches!(err, Err(Error::PskInputsInconsistent)));
}
#[test]
fn auth_mode_roundtrip() {
let mut rng = drbg();
let suite = CipherSuite::new(
HpkeKem::DhkemP256HkdfSha256,
HpkeKdf::HkdfSha256,
HpkeAead::Aes128Gcm,
);
let (sk_r, pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let (sk_s, pk_s) = suite.kem.generate_key_pair(&mut rng).unwrap();
let (enc, mut sender) = setup_sender_auth(&mut rng, suite, &pk_r, b"info", &sk_s).unwrap();
let mut receiver = setup_receiver_auth(suite, &enc, &sk_r, b"info", &pk_s).unwrap();
let ct = sender.seal(b"aad", b"hello").unwrap();
assert_eq!(receiver.open(b"aad", &ct).unwrap(), b"hello");
}
#[test]
fn auth_psk_mode_roundtrip() {
let mut rng = drbg();
let suite = CipherSuite::new(
HpkeKem::DhkemP384HkdfSha384,
HpkeKdf::HkdfSha384,
HpkeAead::Aes256Gcm,
);
let (sk_r, pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let (sk_s, pk_s) = suite.kem.generate_key_pair(&mut rng).unwrap();
let psk = b"a pre-shared symmetric key";
let psk_id = b"id";
let (enc, mut sender) =
setup_sender_auth_psk(&mut rng, suite, &pk_r, b"info", psk, psk_id, &sk_s).unwrap();
let mut receiver =
setup_receiver_auth_psk(suite, &enc, &sk_r, b"info", psk, psk_id, &pk_s).unwrap();
let ct = sender.seal(b"aad", b"hello auth-psk").unwrap();
assert_eq!(receiver.open(b"aad", &ct).unwrap(), b"hello auth-psk");
}
#[test]
fn one_shot_seal_open_roundtrip() {
let mut rng = drbg();
let suite = CipherSuite::new(
HpkeKem::DhkemX25519HkdfSha256,
HpkeKdf::HkdfSha256,
HpkeAead::ChaCha20Poly1305,
);
let (sk_r, pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let (enc, ct) = oneshot_seal(&mut rng, suite, &pk_r, b"info", b"aad", b"hello").unwrap();
let pt = oneshot_open(suite, &enc, &sk_r, b"info", b"aad", &ct).unwrap();
assert_eq!(pt, b"hello");
}
#[test]
fn tampered_ciphertext_rejected() {
let mut rng = drbg();
let suite = CipherSuite::new(
HpkeKem::DhkemX25519HkdfSha256,
HpkeKdf::HkdfSha256,
HpkeAead::Aes128Gcm,
);
let (sk_r, pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let (enc, mut ct) = oneshot_seal(&mut rng, suite, &pk_r, b"i", b"a", b"plain").unwrap();
ct[0] ^= 0x01;
assert_eq!(
oneshot_open(suite, &enc, &sk_r, b"i", b"a", &ct),
Err(Error::AeadError)
);
}
#[test]
fn derive_key_pair_is_deterministic() {
let ikm = hex("7268600d403fce431561aef583ee1613527cff655c1343f29812e6\
6706df3234");
let kem = HpkeKem::DhkemX25519HkdfSha256;
let (sk1, pk1) = kem.derive_key_pair(&ikm).unwrap();
let (sk2, pk2) = kem.derive_key_pair(&ikm).unwrap();
assert_eq!(sk1, sk2);
assert_eq!(pk1, pk2);
}
#[test]
fn enc_wrong_length_rejected() {
let mut rng = drbg();
let suite = CipherSuite::new(
HpkeKem::DhkemX25519HkdfSha256,
HpkeKdf::HkdfSha256,
HpkeAead::Aes128Gcm,
);
let (sk_r, _pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let err = setup_receiver(suite, &[0u8; 31], &sk_r, b"info");
assert_eq!(err.map(|_| ()), Err(Error::InvalidEnc));
}
#[test]
fn ks_seq_overflow_aware() {
let mut rng = drbg();
let suite = CipherSuite::new(
HpkeKem::DhkemX25519HkdfSha256,
HpkeKdf::HkdfSha256,
HpkeAead::Aes128Gcm,
);
let (_sk_r, pk_r) = suite.kem.generate_key_pair(&mut rng).unwrap();
let (_enc, mut sender) = setup_sender(&mut rng, suite, &pk_r, b"i").unwrap();
for _ in 0..5 {
sender.seal(b"a", b"p").unwrap();
}
let _ = sender.export(b"x", 16);
}
#[test]
fn rfc9180_appendix_a1_base_x25519_aes128() {
let info = hex("4f6465206f6e2061204772656369616e2055726e");
let ikm_e = hex("7268600d403fce431561aef583ee1613527cff655c1343f29812e66706df3234");
let pk_em = hex("37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431");
let sk_em = hex("52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736");
let pk_rm = hex("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d");
let sk_rm = hex("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8");
let enc_expected = pk_em.clone();
let kem = HpkeKem::DhkemX25519HkdfSha256;
let (sk_derived, pk_derived) = kem.derive_key_pair(&ikm_e).unwrap();
assert_eq!(sk_derived, sk_em, "derive_key_pair skEm");
assert_eq!(pk_derived, pk_em, "derive_key_pair pkEm");
let suite = CipherSuite::new(kem, HpkeKdf::HkdfSha256, HpkeAead::Aes128Gcm);
let mut rng = ScriptRng::new(&ikm_e);
let (enc, mut sender) = setup_sender(&mut rng, suite, &pk_rm, &info).unwrap();
assert_eq!(enc, enc_expected, "encap enc matches pkEm");
let mut receiver = setup_receiver(suite, &enc, &sk_rm, &info).unwrap();
let aad0 = hex("436f756e742d30");
let pt0 = hex("4265617574792069732074727574682c20747275746820626561757479");
let ct0_expected = hex(
"f938558b5d72f1a23810b4be2ab4f84331acc02fc97babc53a52ae8218a355a96d8770ac83d07bea87e13c512a",
);
let ct0 = sender.seal(&aad0, &pt0).unwrap();
assert_eq!(ct0, ct0_expected, "Encryption[0] ciphertext");
let pt0_back = receiver.open(&aad0, &ct0).unwrap();
assert_eq!(pt0_back, pt0);
let aad1 = hex("436f756e742d31");
let ct1 = sender.seal(&aad1, &pt0).unwrap();
let pt1_back = receiver.open(&aad1, &ct1).unwrap();
assert_eq!(pt1_back, pt0);
let exp0 = sender.export(b"", 32).unwrap();
assert_eq!(
exp0,
hex("3853fe2b4035195a573ffc53856e77058e15d9ea064de3e59f4961d0095250ee"),
"Exporter[empty,32]"
);
let exp1 = sender.export(&[0x00u8], 32).unwrap();
assert_eq!(
exp1,
hex("2e8f0b54673c7029649d4eb9d5e33bf1872cf76d623ff164ac185da9e88c21a5"),
"Exporter[00,32]"
);
let exp2 = sender.export(&hex("54657374436f6e74657874"), 32).unwrap();
assert_eq!(
exp2,
hex("e9e43065102c3836401bed8c3c3c75ae46be1639869391d62c61f1ec7af54931"),
"Exporter[TestContext,32]"
);
}