use hpke::aead::{Aead as HPKEAeadTrait, AesGcm256 as HPKEAesGcm256};
use hpke::kdf::{HkdfSha512, Kdf as HpkeKdfTrait, LabeledExpand, labeled_extract};
use hpke::{Deserializable, Serializable};
use hpke::{Kem as KemTrait, kem::X25519HkdfSha256};
use rand::{CryptoRng, RngCore};
use x25519_dalek::PublicKey as X25519PublicKey;
use crate::crypto::aesgcm::{Key, Nonce};
use crate::errors::Error;
type Kem = X25519HkdfSha256;
type WrappedPublicKey = <Kem as KemTrait>::PublicKey;
type WrappedPrivateKey = <Kem as KemTrait>::PrivateKey;
type DHKEMSharedSecret = hpke::kem::SharedSecret<Kem>;
pub(crate) struct DHKEMCiphertext(<Kem as KemTrait>::EncappedKey);
impl DHKEMCiphertext {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
Ok(DHKEMCiphertext(
<Kem as KemTrait>::EncappedKey::from_bytes(bytes).map_err(|_| Error::HPKEError)?,
))
}
pub(crate) fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes().into()
}
}
pub(crate) fn dhkem_encap_from_rng(
pubkey: &X25519PublicKey,
csprng: &mut (impl CryptoRng + RngCore),
) -> Result<(DHKEMSharedSecret, DHKEMCiphertext), Error> {
let wrapped = WrappedPublicKey::from_bytes(&pubkey.to_bytes()).map_err(|_| Error::HPKEError)?;
let (shared_secret, ciphertext) =
X25519HkdfSha256::encap(&wrapped, None, csprng).map_err(|_| Error::HPKEError)?;
Ok((shared_secret, DHKEMCiphertext(ciphertext)))
}
pub(crate) fn dhkem_decap(
encapped_key: &DHKEMCiphertext,
private_key: &x25519_dalek::StaticSecret,
) -> Result<DHKEMSharedSecret, Error> {
let wrapped =
WrappedPrivateKey::from_bytes(&private_key.to_bytes()).map_err(|_| Error::HPKEError)?;
X25519HkdfSha256::decap(&wrapped, None, &encapped_key.0).map_err(|_| Error::HPKEError)
}
type HpkeKdf = HkdfSha512;
type HpkeAead = HPKEAesGcm256;
const HPKE_MODE_BASE: u8 = 0;
const HYBRID_KEM_ID: u16 = 0x1020;
const HYBRID_KEM_RECIPIENT_ID: u16 = 0x1120;
fn build_suite_id(kem_id: u16) -> [u8; 10] {
let mut out = [0u8; 10];
out[0..4].copy_from_slice(b"HPKE");
out[4..6].copy_from_slice(&kem_id.to_be_bytes());
out[6..8].copy_from_slice(&HpkeKdf::KDF_ID.to_be_bytes());
out[8..10].copy_from_slice(&HpkeAead::AEAD_ID.to_be_bytes());
out
}
fn key_schedule_base(
shared_secret: &[u8],
info: &[u8],
kem_id: u16,
) -> Result<(Key, Nonce), Error> {
let suite_id = build_suite_id(kem_id);
let mut key = Key::default();
let mut base_nonce = Nonce::default();
let (psk_id_hash, _psk_kdf) = labeled_extract::<HpkeKdf>(&[], &suite_id, b"psk_id_hash", b"");
let (info_hash, _info_kdf) = labeled_extract::<HpkeKdf>(&[], &suite_id, b"info_hash", info);
let mut key_schedule_context: Vec<u8> = vec![];
key_schedule_context.push(HPKE_MODE_BASE);
key_schedule_context.extend_from_slice(&psk_id_hash);
key_schedule_context.extend_from_slice(&info_hash);
let (_prk, secret_kdf) = labeled_extract::<HpkeKdf>(shared_secret, &suite_id, b"secret", b"");
secret_kdf
.labeled_expand(&suite_id, b"key", &key_schedule_context, &mut key)
.map_err(|_| Error::HPKEError)?;
secret_kdf
.labeled_expand(
&suite_id,
b"base_nonce",
&key_schedule_context,
&mut base_nonce,
)
.map_err(|_| Error::HPKEError)?;
Ok((key, base_nonce))
}
pub(crate) fn key_schedule_base_hybrid_kem(
shared_secret: &[u8],
info: &[u8],
) -> Result<(Key, Nonce), Error> {
key_schedule_base(shared_secret, info, HYBRID_KEM_ID)
}
pub(crate) fn key_schedule_base_hybrid_kem_recipient(
shared_secret: &[u8],
info: &[u8],
) -> Result<(Key, Nonce), Error> {
key_schedule_base(shared_secret, info, HYBRID_KEM_RECIPIENT_ID)
}
pub(crate) fn compute_nonce(base_nonce: &Nonce, seq: u64) -> Nonce {
let mut nonce = *base_nonce;
let seq_be = seq.to_be_bytes();
for i in 0..seq_be.len() {
let nonce_idx = i
.checked_add(nonce.len())
.unwrap()
.checked_sub(seq_be.len())
.unwrap();
nonce[nonce_idx] ^= seq_be[i];
}
nonce
}
#[cfg(test)]
mod tests {
use crate::{MLADeserialize, MLASerialize};
use std::io;
use std::io::{BufReader, Cursor};
use crate::crypto::aesgcm::AesGcm256;
use super::*;
use hex_literal::hex;
use hpke::Serializable;
use rand::{CryptoRng, RngCore};
use x25519_dalek::StaticSecret;
struct MockRng {
buf: BufReader<Cursor<Vec<u8>>>,
}
impl MockRng {
fn new(data: &[u8]) -> Self {
MockRng {
buf: BufReader::new(Cursor::new(data.to_vec())),
}
}
}
impl RngCore for MockRng {
fn fill_bytes(&mut self, mut dest: &mut [u8]) {
io::copy(&mut self.buf, &mut dest).unwrap();
}
fn next_u32(&mut self) -> u32 {
let mut buf = [0u8; 4];
self.fill_bytes(&mut buf);
u32::from_le_bytes(buf)
}
fn next_u64(&mut self) -> u64 {
let mut buf = [0u8; 8];
self.fill_bytes(&mut buf);
u64::from_le_bytes(buf)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl CryptoRng for MockRng {}
const RFC_IKME: [u8; 32] =
hex!("7268600d403fce431561aef583ee1613527cff655c1343f29812e66706df3234");
const RFC_PKEM: [u8; 32] =
hex!("37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431");
const RFC_SKEM: [u8; 32] =
hex!("52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736");
const RFC_IKMR: [u8; 32] =
hex!("6db9df30aa07dd42ee5e8181afdb977e538f5e1fec8a06223f33f7013e525037");
const RFC_PKRM: [u8; 32] =
hex!("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d");
const RFC_SKRM: [u8; 32] =
hex!("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8");
const RFC_ENC: [u8; 32] =
hex!("37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431");
const RFC_SHARED_SECRET: [u8; 32] =
hex!("fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc");
#[test]
fn dhkem_ciphertext_serialization() {
let ciphertext = DHKEMCiphertext::from_bytes(&RFC_PKRM).unwrap();
assert_eq!(ciphertext.to_bytes(), RFC_PKRM);
let mut encoded = Vec::<u8>::new();
RFC_PKRM.as_slice().serialize(&mut encoded).unwrap();
let data: [u8; 32] = MLADeserialize::deserialize(&mut Cursor::new(encoded)).unwrap();
assert_eq!(RFC_PKRM, data);
}
#[test]
fn rfc9180_dhkem_vector_tests() {
let (sender_privkey, sender_pubkey) = X25519HkdfSha256::derive_keypair(&RFC_IKME);
assert_eq!(&sender_pubkey.to_bytes().as_ref(), &RFC_PKEM);
assert_eq!(&sender_privkey.to_bytes().as_ref(), &RFC_SKEM);
let (receiver_privkey, receiver_pubkey) = X25519HkdfSha256::derive_keypair(&RFC_IKMR);
assert_eq!(&receiver_pubkey.to_bytes().as_ref(), &RFC_PKRM);
assert_eq!(&receiver_privkey.to_bytes().as_ref(), &RFC_SKRM);
let mut rng = MockRng::new(&RFC_IKME);
let (shared_secret_encap, encapped_key) =
dhkem_encap_from_rng(&X25519PublicKey::from(RFC_PKRM), &mut rng).unwrap();
assert_eq!(&encapped_key.to_bytes().as_ref(), &RFC_ENC);
assert_eq!(&shared_secret_encap.0.to_vec(), &RFC_SHARED_SECRET);
let shared_secret_decap = dhkem_decap(
&DHKEMCiphertext::from_bytes(&RFC_ENC).unwrap(),
&StaticSecret::from(RFC_SKRM),
)
.unwrap();
assert_eq!(&shared_secret_decap.0.to_vec(), &RFC_SHARED_SECRET);
}
const RFC_A6_INFO: [u8; 20] = hex!("4f6465206f6e2061204772656369616e2055726e");
const RFC_A6_SHARED_SECRET: [u8; 64] = hex!(
"776ab421302f6eff7d7cb5cb1adaea0cd50872c71c2d63c30c4f1d5e43653336fef33b103c67e7a98add2d3b66e2fda95b5b2a667aa9dac7e59cc1d46d30e818"
);
const RFC_A6_KEM_ID: u16 = 18;
const RFC_A6_KEY: [u8; 32] =
hex!("751e346ce8f0ddb2305c8a2a85c70d5cf559c53093656be636b9406d4d7d1b70");
const RFC_A6_BASE_NONCE: [u8; 12] = hex!("55ff7a7d739c69f44b25447b");
#[test]
fn test_key_schedule_base() {
let (key, nonce) =
key_schedule_base(&RFC_A6_SHARED_SECRET, &RFC_A6_INFO, RFC_A6_KEM_ID).unwrap();
assert_eq!(key, RFC_A6_KEY);
assert_eq!(nonce, RFC_A6_BASE_NONCE);
}
const RFC_A6_SEQS: [u64; 6] = [0, 1, 2, 4, 255, 256];
const RFC_A6_NONCE: [[u8; 12]; 6] = [
hex!("55ff7a7d739c69f44b25447b"), hex!("55ff7a7d739c69f44b25447a"), hex!("55ff7a7d739c69f44b254479"), hex!("55ff7a7d739c69f44b25447f"), hex!("55ff7a7d739c69f44b254484"), hex!("55ff7a7d739c69f44b25457b"), ];
const RFC_A6_AAD: [&[u8]; 6] = [
&hex!("436f756e742d30"), &hex!("436f756e742d31"), &hex!("436f756e742d32"), &hex!("436f756e742d34"), &hex!("436f756e742d323535"), &hex!("436f756e742d323536"), ];
const RFC_A6_PT: &[u8] = &hex!("4265617574792069732074727574682c20747275746820626561757479");
const RFC_A6_CT: [&[u8]; 6] = [
&hex!(
"170f8beddfe949b75ef9c387e201baf4132fa7374593dfafa90768788b7b2b200aafcc6d80ea4c795a7c5b841a"
), &hex!(
"d9ee248e220ca24ac00bbbe7e221a832e4f7fa64c4fbab3945b6f3af0c5ecd5e16815b328be4954a05fd352256"
), &hex!(
"142cf1e02d1f58d9285f2af7dcfa44f7c3f2d15c73d460c48c6e0e506a3144bae35284e7e221105b61d24e1c7a"
), &hex!(
"3bb3a5a07100e5a12805327bf3b152df728b1c1be75a9fd2cb2bf5eac0cca1fb80addb37eb2a32938c7268e3e5"
), &hex!(
"4f268d0930f8d50b8fd9d0f26657ba25b5cb08b308c92e33382f369c768b558e113ac95a4c70dd60909ad1adc7"
), &hex!(
"dbbfc44ae037864e75f136e8b4b4123351d480e6619ae0e0ae437f036f2f8f1ef677686323977a1ccbb4b4f16a"
), ];
#[test]
fn test_compute_nonce() {
for i in 0..RFC_A6_SEQS.len() {
let computed_nonce = compute_nonce(&RFC_A6_BASE_NONCE, RFC_A6_SEQS[i]);
assert_eq!(computed_nonce, RFC_A6_NONCE[i]);
}
}
#[test]
fn test_rfc_a6_encryption() {
for i in 0..RFC_A6_SEQS.len() {
let mut aes = AesGcm256::new(
&RFC_A6_KEY,
&compute_nonce(&RFC_A6_BASE_NONCE, RFC_A6_SEQS[i]),
RFC_A6_AAD[i],
)
.unwrap();
let mut buf = Vec::from(RFC_A6_PT);
aes.encrypt(buf.as_mut_slice());
buf.extend_from_slice(&aes.into_tag());
assert_eq!(buf, RFC_A6_CT[i]);
}
}
}