From 883ac5363d81ba0c608ed94848d809236dbfe44c Mon Sep 17 00:00:00 2001 From: Tanya Verma Date: Sun, 23 Jan 2022 22:22:36 -0800 Subject: [PATCH] Base Mode HPKE --- src/hpke.rs | 1072 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + tests/hpke_tests.rs | 178 +++++++ tests/hpke_tests.txt | 366 ++++++++++++++ 4 files changed, 1617 insertions(+) create mode 100644 src/hpke.rs create mode 100644 tests/hpke_tests.rs create mode 100644 tests/hpke_tests.txt diff --git a/src/hpke.rs b/src/hpke.rs new file mode 100644 index 0000000000..38f93feaf4 --- /dev/null +++ b/src/hpke.rs @@ -0,0 +1,1072 @@ +//! Implementation of the Hybrid Public Key Encryption (HPKE) standard specified by RFC 9810. + +use crate::hkdf::KeyType; +use crate::{aead, agreement, cpu, digest, ec, error, hkdf, hmac, rand}; +use core::convert::TryFrom; + +static VERSION: &[u8] = "HPKE-v1".as_bytes(); +const MAX_PRIVATE_KEY_LEN: usize = ec::SCALAR_MAX_BYTES; +const MAX_PUBLIC_KEY_LEN: usize = 1 + (2 * ec::ELEM_MAX_BYTES); +const MAX_AEAD_KEY_LEN: usize = 32; +const MAX_DIGEST_LEN: usize = digest::MAX_OUTPUT_LEN; + +/// Mode refers to different HPKE variants, which provide varying levels of authentication. +/// All modes require a receiver key pair. +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum Mode { + /// Base: base case of encrypting to the receiver's public key. + Base = 0x00, + /// Psk: enables sender authentication, by requiring possession of a pre-shared key. + /// (not yet implemented) + Psk = 0x01, + /// Auth: enables sender authentication, by requiring possession of sender key pair. + /// (not yet implemented) + Auth = 0x02, + /// AuthPsk: enables sender authentication, by requiring both sender key pair and a pre-shared key. + /// (not yet implemented) + AuthPsk = 0x03, +} + +/// KemId is the Key Encapsulation Mechanism (KEM) algorithm identifier. +/// It is used to derive and efficiently transport the shared symmetric key(s) over the wire. +/// It consists of the encapsulation algorithm and the key derivation function. +/// +/// The encapsulation algorithm generates a fresh ephemeral DH key pair. A shared secret is computed +/// between the ephemeral key pair and the receiver key pair. Next, it is passed through the associated +/// KDF to compute the final shared secret. +/// +/// NOTE that the KDF used in KEM can be different from the KDF used later. +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u16)] +pub enum KemId { + /// DHKEM(P-256, HKDF-SHA256) + P256Sha256 = 0x0010, + /// DHKEM(P-384, HKDF-SHA384) + P384Sha384 = 0x0011, + /// DHKEM(P-521, HKDF-SHA512) -- unimplemented + P521Sha512 = 0x0012, + /// DHKEM(X25519, HKDF-SHA256) + X25519Sha256 = 0x0020, + /// DHKEM(X448, HKDF-SHA512) -- unimplemented + X448Sha512 = 0x0021, +} + +impl TryFrom for KemId { + type Error = error::Unspecified; + + fn try_from(v: u16) -> Result { + match v { + 0x0010 if 0x0010 == KemId::P256Sha256 as u16 => Ok(KemId::P256Sha256), + 0x0011 if 0x0011 == KemId::P384Sha384 as u16 => Ok(KemId::P384Sha384), + 0x0012 if 0x0012 == KemId::P521Sha512 as u16 => Ok(KemId::P521Sha512), + 0x0020 if 0x0020 == KemId::X25519Sha256 as u16 => Ok(KemId::X25519Sha256), + 0x0021 if 0x0021 == KemId::X448Sha512 as u16 => Ok(KemId::X448Sha512), + _ => Err(error::Unspecified), + } + } +} + +/// KdfId is the Hmac Key Derivation Function (HKDF) identifier. +/// The HKDF scheme is used for deriving shared secrets for export. +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u16)] +pub enum KdfId { + /// HKDF-SHA256 + Sha256 = 0x0001, + /// HKDF-SHA384 + Sha384 = 0x0002, + /// HKDF-SHA512 -- unimplemented + Sha512 = 0x0003, +} + +impl TryFrom for KdfId { + type Error = error::Unspecified; + + fn try_from(v: u16) -> Result { + match v { + 0x0001 if 0x0001 == KdfId::Sha256 as u16 => Ok(KdfId::Sha256), + 0x0002 if 0x0002 == KdfId::Sha384 as u16 => Ok(KdfId::Sha384), + 0x0003 if 0x0003 == KdfId::Sha512 as u16 => Ok(KdfId::Sha512), + _ => Err(error::Unspecified), + } + } +} + +/// AeadId is the Authenticated Encryption with Associated Data algorithm identifier. +/// Aead is used for encrypting messages. +#[derive(Debug, Clone, Copy, PartialEq)] +#[non_exhaustive] +#[repr(u16)] +pub enum AeadId { + /// AES-128-GCM + Aes128Gcm = 0x0001, + /// AES-256-GCM + Aes256Gcm = 0x0002, + /// ChaCha20Poly1305 + ChaCha20Poly1305 = 0x0003, + // TODO: Implement ExportOnly +} + +impl TryFrom for AeadId { + type Error = error::Unspecified; + + fn try_from(v: u16) -> Result { + match v { + 0x0001 if 0x0001 == AeadId::Aes128Gcm as u16 => Ok(AeadId::Aes128Gcm), + 0x0002 if 0x0002 == AeadId::Aes256Gcm as u16 => Ok(AeadId::Aes256Gcm), + 0x0003 if 0x0003 == AeadId::ChaCha20Poly1305 as u16 => Ok(AeadId::ChaCha20Poly1305), + _ => Err(error::Unspecified), + } + } +} + +/// KeyPair +pub struct KeyPair { + secret_key: ec::Seed, + public_key: [u8; MAX_PUBLIC_KEY_LEN], + /// public key length + pub public_key_len: u16, + /// secret key length + pub secret_key_len: u16, +} + +impl KeyPair { + /// Returns the public key as a byte slice + pub fn public_key_bytes(&self) -> &[u8] { + &self.public_key[..self.public_key_len as usize] + } +} + +/// Suite +pub struct Suite { + suite_id_context: [u8; 10], + hash: &'static hkdf::Algorithm, + aead: &'static aead::Algorithm, + kem: Kem, +} + +fn get_suite_id_kem(kem_id: KemId) -> [u8; 5] { + let mut kem = [0u8; 5]; + kem[0..3].copy_from_slice("KEM".as_bytes()); + kem[3..5].copy_from_slice(&(kem_id as u16).to_be_bytes()); + kem +} + +fn get_suite_id_context(kem_id: KemId, kdf_id: KdfId, aead_id: AeadId) -> [u8; 10] { + let mut suite = [0u8; 10]; + suite[..4].copy_from_slice("HPKE".as_bytes()); + let mut i = 4; + for id in [kem_id as u16, kdf_id as u16, aead_id as u16] { + suite[i..i + 2].copy_from_slice(&id.to_be_bytes()); + i += 2; + } + suite +} + +/// Kem +pub struct Kem { + suite_id_kem: [u8; 5], + kem_id: KemId, + hash: &'static hkdf::Algorithm, + agreement: &'static agreement::Algorithm, +} + +impl Kem { + /// Instantiate a new KEM struct from a given [KemId] + pub fn new(kem_id: KemId) -> Result { + match kem_id { + KemId::P256Sha256 => Ok(Kem { + suite_id_kem: get_suite_id_kem(kem_id), + kem_id, + hash: &hkdf::HKDF_SHA256, + agreement: &agreement::ECDH_P256, + }), + KemId::P384Sha384 => Ok(Kem { + suite_id_kem: get_suite_id_kem(kem_id), + kem_id, + hash: &hkdf::HKDF_SHA384, + agreement: &agreement::ECDH_P384, + }), + KemId::P521Sha512 => { + // unimplemented suite + Err(error::Unspecified) + } + KemId::X25519Sha256 => Ok(Kem { + suite_id_kem: get_suite_id_kem(kem_id), + kem_id, + hash: &hkdf::HKDF_SHA256, + agreement: &agreement::X25519, + }), + KemId::X448Sha512 => { + // unimplemented suite + Err(error::Unspecified) + } + } + } + + /// Instantiate a new KEM from an `u16` argument `kem_id`. + /// `kem_id` should be a valid variant of [KemId]. + pub fn from_u16(kem_id: u16) -> Result { + let kem_id = KemId::try_from(kem_id)?; + Self::new(kem_id) + } + + /// Length of an encoded public key for this KEM + pub fn public_key_length(&self) -> usize { + self.agreement.curve.public_key_len + } + + /// Length of an encoded private key for this KEM + pub fn private_key_length(&self) -> usize { + self.agreement.curve.elem_scalar_seed_len + } + + /// Write the serialized public key bytes into output buffer `buf` + fn public_key_bytes(&self, secret_key: &ec::Seed, buf: &mut [u8]) -> Result<(), error::Unspecified> { + if buf.len() != self.public_key_length() { + return Err(error::Unspecified); + } + let public_key = secret_key.compute_public_key()?; + buf.copy_from_slice(public_key.as_ref()); + Ok(()) + } + + /// Randomized algorithm to generate a key pair. + /// If `rng` is None, then `rand::SystemRandom` will be used as the rng. + pub fn generate_key_pair( + &self, + rng: Option<&dyn rand::SecureRandom>, + ) -> Result { + let secret_key; + if let Some(rng) = rng { + secret_key = ec::Seed::generate(self.agreement.curve, rng, cpu::features())?; + } else { + secret_key = ec::Seed::generate(self.agreement.curve, &rand::SystemRandom::new(), cpu::features())?; + }; + let mut public_key = [0u8; MAX_PUBLIC_KEY_LEN]; + let public_key_len = self.public_key_length() as u16; + let secret_key_len = self.private_key_length() as u16; + self.public_key_bytes(&secret_key, &mut public_key[..self.public_key_length()])?; + Ok(KeyPair { + secret_key, + public_key, + public_key_len, + secret_key_len, + }) + } + + /// Deterministic algorithm to derive a key pair from an initial `seed` + pub fn derive_key_pair(&self, seed: &[u8]) -> Result { + let prk = labeled_extract(&self.suite_id_kem, &[], "dkp_prk", seed, self.hash); + let mut out = [0u8; MAX_PRIVATE_KEY_LEN]; + let mut public_key = [0u8; MAX_PUBLIC_KEY_LEN]; + let public_key_len = self.public_key_length() as u16; + let secret_key_len = self.private_key_length() as u16; + match self.kem_id { + KemId::X25519Sha256 => { + labeled_expand( + &self.suite_id_kem, + self.hash, + prk.as_ref(), + "sk", + &[], + self.private_key_length() as u16, + &mut out[..self.private_key_length()], + )?; + let secret_key = ec::Seed::from_bytes( + self.agreement.curve, + untrusted::Input::from(&out[..self.private_key_length()]), + cpu::features(), + )?; + self.public_key_bytes(&secret_key, &mut public_key[..self.public_key_length()])?; + Ok(KeyPair { + secret_key, + public_key, + public_key_len, + secret_key_len, + }) + } + KemId::P256Sha256 | KemId::P384Sha384 => { + for counter in 0u8..=255 { + labeled_expand( + &self.suite_id_kem, + self.hash, + prk.as_ref(), + "candidate", + &[counter], + self.private_key_length() as u16, + &mut out[..self.private_key_length()], + )?; + let candidate_key = ec::Seed::from_bytes( + self.agreement.curve, + untrusted::Input::from(&out[..self.private_key_length()]), + cpu::features(), + ); + if let Ok(k) = candidate_key { + self.public_key_bytes(&k, &mut public_key[..self.public_key_length()])?; + return Ok(KeyPair { + secret_key: k, + public_key, + public_key_len, + secret_key_len, + }); + } + } + Err(error::Unspecified) + } + _ => { + // unimplemented + Err(error::Unspecified) + } + } + } + + /// Non interactive DH key exchange to derive shared secret + fn dh_kex( + &mut self, + public_key_receiver: &[u8], + shared_secret: &mut [u8], + encapped_key: &mut [u8], + rng: &dyn rand::SecureRandom, + seed: Option<&[u8]>, + ) -> Result<(), error::Unspecified> { + let keypair = match seed { + Some(s) => self.derive_key_pair(s)?, + None => self.generate_key_pair(Some(rng))?, + }; + let fixed_rng = FixedSliceRandom { + bytes: keypair.secret_key.bytes_less_safe(), + }; + let secret_key = agreement::EphemeralPrivateKey::generate(self.agreement, &fixed_rng)?; + let public_key = secret_key.compute_public_key()?; + let peer_public_key = + agreement::UnparsedPublicKey::new(self.agreement, public_key_receiver); + agreement::agree_ephemeral(secret_key, &peer_public_key, |key_material| { + shared_secret.copy_from_slice(key_material) + })?; + encapped_key.copy_from_slice(public_key.as_ref()); + Ok(()) + } + + fn extract_and_expand( + &self, + kex_result: &[u8], + context: &[&[u8]; 2], + out: &mut [u8], + ) -> Result<(), error::Unspecified> { + let prk = labeled_extract(&self.suite_id_kem, &[], "eae_prk", kex_result, self.hash); + let mut kem_ctx = [0u8; MAX_PUBLIC_KEY_LEN * 2]; + kem_ctx[..context[0].len()].copy_from_slice(context[0]); + kem_ctx[context[0].len()..context[0].len() + context[1].len()].copy_from_slice(context[1]); + labeled_expand( + &self.suite_id_kem, + self.hash, + prk.as_ref(), + "shared_secret", + &kem_ctx[..context[0].len() + context[1].len()], + self.private_key_length() as u16, + out, + ) + } + + /// Randomized algorithm to generate an ephemeral, fixed-length symmetric key (the KEM shared + /// secret) and a fixed-length encapsulation of that key that can be decapsulated by the receiver. + fn encap( + &self, + pk_receiver: &[u8], + encapped_key: &mut [u8], + shared_secret: &mut [u8], + rng: &dyn rand::SecureRandom, + seed: Option<&[u8]>, + ) -> Result<(), error::Unspecified> { + let mut kem = Kem::new(self.kem_id)?; + let mut kex_result = [0u8; MAX_PRIVATE_KEY_LEN]; + kem.dh_kex( + pk_receiver, + &mut kex_result[..self.private_key_length()], + encapped_key, + rng, + seed, + )?; + kem.extract_and_expand( + &kex_result[..self.private_key_length()], + &[encapped_key, pk_receiver], + shared_secret, + )?; + Ok(()) + } + + /// Deterministic algorithm using the private key of the receiver to recover the ephemeral + /// symmetric key (the KEM shared secret) from its encapsulated representation `enc`. + fn decap( + &self, + keypair: &KeyPair, + encapped_key: &[u8], + shared_secret: &mut [u8], + ) -> Result<(), error::Unspecified> { + let public_key_ephemeral = agreement::UnparsedPublicKey::new(self.agreement, encapped_key); + let kem = Kem::new(self.kem_id)?; + let mut kex_result = [0u8; MAX_PRIVATE_KEY_LEN]; + (kem.agreement.ecdh)( + &mut kex_result[..self.private_key_length()], + &keypair.secret_key, + untrusted::Input::from(public_key_ephemeral.bytes()), + )?; + kem.extract_and_expand( + &kex_result[..self.private_key_length()], + &[ + encapped_key, + &keypair.public_key[..keypair.public_key_len as usize], + ], + shared_secret, + )?; + Ok(()) + } +} + +impl Suite { + /// Instantiate a new suite with a given [KemId], [KdfId] and [AeadId]. + pub fn new(kem_id: KemId, kdf_id: KdfId, aead_id: AeadId) -> Result { + let kem = Kem::new(kem_id)?; + Self::with_existing_kem(kem, kdf_id, aead_id) + } + + /// Instantiate a new `Suite` from `u16` arguments `kem_id`, `kdf_id` and `aead_id`. + /// `kem_id`, `kdf_id` and `aead_id` should be valid variants of [KemId], [KdfId] and [AeadId]. + pub fn from_u16(kem_id: u16, kdf_id: u16, aead_id: u16) -> Result { + let kem_id = KemId::try_from(kem_id)?; + let kdf_id = KdfId::try_from(kdf_id)?; + let aead_id = AeadId::try_from(aead_id)?; + Self::new(kem_id, kdf_id, aead_id) + } + + /// Instantiate a new suite with an existing [Kem], [KdfId] and [AeadId]. + pub fn with_existing_kem(kem: Kem, kdf_id: KdfId, aead_id: AeadId) -> Result { + let h = match kdf_id { + KdfId::Sha256 => &hkdf::HKDF_SHA256, + KdfId::Sha384 => &hkdf::HKDF_SHA384, + KdfId::Sha512 => &hkdf::HKDF_SHA512, + }; + let a = match aead_id { + AeadId::Aes128Gcm => &aead::AES_128_GCM, + AeadId::Aes256Gcm => &aead::AES_256_GCM, + AeadId::ChaCha20Poly1305 => &aead::CHACHA20_POLY1305, + }; + Ok(Suite { + suite_id_context: get_suite_id_context(kem.kem_id, kdf_id, aead_id), + hash: h, + kem, + aead: a, + }) + } + + /// Instantiate a new `Suite` with an existing [Kem] and `u16` arguments `kdf_id` and `aead_id`. + /// `kdf_id` and `aead_id` should be valid variants of [KdfId] and [AeadId]. + pub fn from_u16_with_existing_kem(kem: Kem, kdf_id: u16, aead_id: u16) -> Result { + let kdf_id = KdfId::try_from(kdf_id)?; + let aead_id = AeadId::try_from(aead_id)?; + Self::with_existing_kem(kem, kdf_id, aead_id) + } + + /// Length of the authentication tag of the associated AEAD algorithm. + pub fn aead_tag_len(&self) -> usize { + self.aead.tag_len() + } + + /// This function combines the KEM shared secret, an info slice that can be chosen by the + /// application, and an optional pre-shared key to generate the key schedule. + fn key_schedule( + &self, + mode: Mode, + shared_secret: &[u8], + info: &[u8], + psk: Option<&[u8]>, + psk_id: Option<&[u8]>, + ) -> Result { + let (psk_id_used, psk_used) = match psk_id { + Some(p) => (p, psk.unwrap()), + None => (&[][..], &[][..]), + }; + + let psk_id_hash = labeled_extract( + &self.suite_id_context, + &[], + "psk_id_hash", + psk_id_used, + self.hash, + ); + let info_hash = labeled_extract(&self.suite_id_context, &[], "info_hash", info, self.hash); + let mut key_schedule_ctx = [0u8; 1 + MAX_DIGEST_LEN * 2]; + key_schedule_ctx[0] = mode as u8; + key_schedule_ctx[1..self.hash.len() + 1].copy_from_slice(psk_id_hash.as_ref()); + key_schedule_ctx[self.hash.len() + 1..self.hash.len() * 2 + 1] + .copy_from_slice(info_hash.as_ref()); + let key_schedule_ctx_spliced = &key_schedule_ctx[..self.hash.len() * 2 + 1]; + let secret = labeled_extract( + &self.suite_id_context, + shared_secret, + "secret", + psk_used, + self.hash, + ); + let mut key_out = [0u8; MAX_AEAD_KEY_LEN]; + labeled_expand( + &self.suite_id_context, + self.hash, + secret.as_ref(), + "key", + key_schedule_ctx_spliced, + self.aead.key_len() as u16, + &mut key_out[..self.aead.key_len()], + )?; + let mut nonce_out = [0u8; aead::NONCE_LEN]; + labeled_expand( + &self.suite_id_context, + self.hash, + secret.as_ref(), + "base_nonce", + key_schedule_ctx_spliced, + self.aead.nonce_len() as u16, + &mut nonce_out, + )?; + let mut exporter_secret = [0u8; MAX_DIGEST_LEN]; + labeled_expand( + &self.suite_id_context, + self.hash, + secret.as_ref(), + "exp", + key_schedule_ctx_spliced, + self.hash.len() as u16, + &mut exporter_secret[..self.hash.len()], + )?; + Ok(InnerContext { + suite_id_context: self.suite_id_context, + hash: self.hash, + aead_ctx: AeadContext { + sealing_key: make_key( + self.aead, + &key_out[..self.aead.key_len()], + &HpkeNonce { + base_nonce: nonce_out, + nonce_counter: [0u8; aead::NONCE_LEN], + }, + ), + opening_key: make_key( + self.aead, + &key_out[..self.aead.key_len()], + &HpkeNonce { + base_nonce: nonce_out, + nonce_counter: [0u8; aead::NONCE_LEN], + }, + ), + raw_key: key_out, + base_nonce: nonce_out, + }, + exporter_secret, + aead_algorithm: self.aead, + peer_aead_ctx: None, + }) + } + + /// Instantiate a new [SenderContext] that can be used to encrypt. This function takes as input + /// all the long term key material involved, and an application specific string. + /// This binds the context to the sender and receiver and the application it is being used for. + /// + /// `pk_receiver`: encoded public key of the receiver. + /// `info`: application specific slice chosen by user. + /// `encapped_key`: buffer to which the encapsulated key will be written. + pub fn new_sender_context( + &self, + pk_receiver: &[u8], + info: &[u8], + encapped_key: &mut [u8], + ) -> Result { + let mut shared_secret = [0u8; MAX_PRIVATE_KEY_LEN]; + self.kem.encap( + pk_receiver, + encapped_key, + &mut shared_secret[..self.kem.private_key_length()], + &rand::SystemRandom::new(), + None, + )?; + + Ok(SenderContext { + inner: self.key_schedule( + Mode::Base, + &shared_secret[..self.kem.private_key_length()], + info, + None, + None, + )?, + }) + } + + /// NOTE: Only use for testing + /// Instantiate a new [SenderContext] that can be used to encrypt. This function takes as input + /// all the long term key material involved, and an application specific string. + /// This binds the context to the sender and receiver and the application it is being used for. + /// + /// `pk_receiver`: encoded public key of the receiver. + /// `info`: application specific slice chosen by user. + /// `rng`: optional rng for randomized vs deterministic context. If not test, this is required. + /// `seed`: optional seed can be specified for deterministic context generation. + /// `encapped_key`: buffer to which the encapsulated key will be written. + #[doc(hidden)] + pub fn new_deterministic_sender_context( + &self, + pk_receiver: &[u8], + info: &[u8], + seed: &[u8], + encapped_key: &mut [u8], + ) -> Result { + let mut shared_secret = [0u8; MAX_PRIVATE_KEY_LEN]; + self.kem.encap( + pk_receiver, + encapped_key, + &mut shared_secret[..self.kem.private_key_length()], + &rand::SystemRandom::new(), + Some(seed), + )?; + + Ok(SenderContext { + inner: self.key_schedule( + Mode::Base, + &shared_secret[..self.kem.private_key_length()], + info, + None, + None, + )?, + }) + } + + /// Instantiate a new [ReceiverContext] that can be used to decrypt. This function takes as input + /// the encapsulated key and the application specific info slice. + pub fn new_receiver_context( + &self, + keypair: &KeyPair, + encapped_key: &[u8], + info: &[u8], + ) -> Result { + let mut shared_secret = [0u8; MAX_PRIVATE_KEY_LEN]; + self.kem.decap( + keypair, + encapped_key, + &mut shared_secret[..self.kem.private_key_length()], + )?; + Ok(ReceiverContext { + inner: self.key_schedule( + Mode::Base, + &shared_secret[..self.kem.private_key_length()], + info, + None, + None, + )?, + }) + } +} + +/// InnerContext stores the state required to encrypt and decrypt messages. +struct InnerContext { + suite_id_context: [u8; 10], + hash: &'static hkdf::Algorithm, + exporter_secret: [u8; MAX_DIGEST_LEN], + aead_algorithm: &'static aead::Algorithm, + aead_ctx: AeadContext, + peer_aead_ctx: Option, +} + +/// SenderContext stores the state required to encrypt messages. +pub struct SenderContext { + inner: InnerContext, +} + +/// ReceiverContext stores the state required to decrypt messages. +pub struct ReceiverContext { + inner: InnerContext, +} + +impl InnerContext { + /// Enables AEAD encryption from recipient to sender by deriving a key and nonce + /// from the current context. It follows guidelines for bidirectional communication as + /// mentioned in the RFC: https://cfrg.github.io/draft-irtf-cfrg-hpke/draft-irtf-cfrg-hpke.html#section-9.8 + fn response_state(&mut self) -> Result { + let mut key = [0u8; MAX_AEAD_KEY_LEN]; + self.export( + "response key".as_bytes(), + self.aead_algorithm.key_len() as u16, + &mut key[..self.aead_algorithm.key_len()], + )?; + let mut base_nonce = [0u8; aead::NONCE_LEN]; + self.export( + "response nonce".as_bytes(), + self.aead_algorithm.nonce_len() as u16, + &mut base_nonce, + )?; + Ok(AeadContext { + sealing_key: make_key( + self.aead_algorithm, + &key[..self.aead_algorithm.key_len()], + &HpkeNonce { + base_nonce, + nonce_counter: [0u8; aead::NONCE_LEN], + }, + ), + opening_key: make_key( + self.aead_algorithm, + &key[..self.aead_algorithm.key_len()], + &HpkeNonce { + base_nonce, + nonce_counter: [0u8; aead::NONCE_LEN], + }, + ), + raw_key: key, + base_nonce, + }) + } + + /// `export` generates a secret of `length` bytes from `exporter_context`, which is a + /// secret known by the sender and receiver. It writes the generated secret into the + /// buffer `exported`. + fn export( + &self, + exporter_context: &[u8], + length: u16, + exported: &mut [u8], + ) -> Result<(), error::Unspecified> { + labeled_expand( + &self.suite_id_context, + self.hash, + &self.exporter_secret[..self.hash.len()], + "sec", + exporter_context, + length, + exported, + ) + } +} + +impl SenderContext { + /// Performs in-place encryption of the message `msg` with additional data `aad` to the receiver. + /// The resulting ciphertext + tag is written to `msg`. + pub fn encrypt_to_receiver( + &mut self, + msg: &mut [u8], + aad: &[u8], + ) -> Result<(), error::Unspecified> { + let msg_len = msg.len() - self.inner.aead_algorithm.tag_len(); + let out = self + .inner + .aead_ctx + .sealing_key + .seal_in_place_separate_tag(aead::Aad::from(aad), &mut msg[..msg_len])?; + msg[msg_len..].copy_from_slice(out.as_ref()); + Ok(()) + } + + /// Performs in-place decryption of the `ciphertext` received, with additional data `aad`. + /// `ciphertext` should contain the `ciphertext` followed by `tag`. + /// The resulting plaintext (without the tag) is written to `ciphertext`. + pub fn decrypt_from_receiver( + &mut self, + aad: &[u8], + ciphertext: &mut [u8], + ) -> Result<(), error::Unspecified> { + if self.inner.peer_aead_ctx.is_none() { + self.inner.peer_aead_ctx = Some(self.inner.response_state()?); + } + let _ = self + .inner + .peer_aead_ctx + .as_mut() + .unwrap() + .opening_key + .open_in_place(aead::Aad::from(aad), ciphertext)?; + Ok(()) + } + + /// `export` generates a secret of `length` bytes from `exporter_context`, which is a + /// secret known by the sender and receiver. It writes the generated secret into the + /// buffer `exported`. + pub fn export( + &self, + exporter_context: &[u8], + length: u16, + exported: &mut [u8], + ) -> Result<(), error::Unspecified> { + self.inner.export(exporter_context, length, exported) + } +} + +impl ReceiverContext { + /// Performs in-place decryption of the `ciphertext` received, with additional data `aad`. + /// `ciphertext` should contain the `ciphertext` followed by `tag`. + /// The resulting plaintext (without the tag) is written to `ciphertext`. + pub fn decrypt_from_sender( + &mut self, + aad: &[u8], + ciphertext: &mut [u8], + ) -> Result<(), error::Unspecified> { + let _ = self + .inner + .aead_ctx + .opening_key + .open_in_place(aead::Aad::from(aad), ciphertext)?; + Ok(()) + } + + /// Performs in-place encryption of the message `msg` with additional data `aad` to the sender. + /// The resulting ciphertext + tag is written to `msg`. + /// NOTE: In Base Mode, using this method means there is no authentication of the remote. + /// For more information, see [Bidirectional Encryption](https://cfrg.github.io/draft-irtf-cfrg-hpke/draft-irtf-cfrg-hpke.html#section-9.8) + pub fn encrypt_to_sender( + &mut self, + msg: &mut [u8], + aad: &[u8], + ) -> Result<(), error::Unspecified> { + if self.inner.peer_aead_ctx.is_none() { + self.inner.peer_aead_ctx = Some(self.inner.response_state()?); + } + let msg_len = msg.len() - self.inner.aead_algorithm.tag_len(); + let out = self + .inner + .peer_aead_ctx + .as_mut() + .unwrap() + .sealing_key + .seal_in_place_separate_tag(aead::Aad::from(aad), &mut msg[..msg_len])?; + msg[msg_len..].copy_from_slice(out.as_ref()); + Ok(()) + } + + /// `export` generates a secret of `length` bytes from `exporter_context`, which is a + /// secret known by the sender and receiver. It writes the generated secret into the + /// buffer `exported`. + pub fn export( + &self, + exporter_context: &[u8], + length: u16, + exported: &mut [u8], + ) -> Result<(), error::Unspecified> { + self.inner.export(exporter_context, length, exported) + } +} + +#[derive(Debug, Clone, PartialEq)] +struct HpkeNonce { + base_nonce: [u8; aead::NONCE_LEN], + nonce_counter: [u8; aead::NONCE_LEN], +} + +/// `AeadContext` contains the symmetric key and nonce necessary for encryption and decryption. +#[derive(Debug)] +struct AeadContext { + sealing_key: aead::SealingKey, + opening_key: aead::OpeningKey, + raw_key: [u8; MAX_AEAD_KEY_LEN], + base_nonce: [u8; 12], +} + +impl aead::NonceSequence for HpkeNonce { + fn advance(&mut self) -> Result { + let mut new_nonce = [0u8; aead::NONCE_LEN]; + for (i, (&x1, &x2)) in self + .base_nonce + .iter() + .zip(self.nonce_counter.iter()) + .enumerate() + { + new_nonce[i] = x1 ^ x2; + } + self.increment_bytes()?; + aead::Nonce::try_assume_unique_for_key(&new_nonce) + } +} + +impl HpkeNonce { + /// Increments the bytes by 1, assuming the most significant bit is first. + /// Returns an error in case of overflow. + fn increment_bytes(&mut self) -> Result<(), error::Unspecified> { + let mut carry = 1 as u16; + for i in (0..self.nonce_counter.len()).rev() { + let d = (self.nonce_counter[i] as u16) + carry; + self.nonce_counter[i] = (d & 0xff) as u8; + carry = d >> 8; + } + if carry != 0 { + // Overflow error + return Err(error::Unspecified) + } + Ok(()) + } +} + +fn make_key>( + algorithm: &'static aead::Algorithm, + key: &[u8], + nonce: &HpkeNonce, +) -> K { + let key = aead::UnboundKey::new(algorithm, key).unwrap(); + let nonce_sequence = HpkeNonce { + base_nonce: nonce.base_nonce, + nonce_counter: nonce.nonce_counter, + }; + K::new(key, nonce_sequence) +} + +/// Generic newtype wrapper that lets us implement traits for externally-defined +/// types. +#[derive(Debug, PartialEq)] +struct HpkeWrap(T); + +impl KeyType for HpkeWrap { + fn len(&self) -> usize { + self.0 + } +} + +impl From>> for HpkeWrap<[u8; MAX_DIGEST_LEN]> { + fn from(okm: hkdf::Okm>) -> Self { + let mut r = [0u8; MAX_DIGEST_LEN]; + okm.fill(&mut r).unwrap(); + Self(r) + } +} + +/// KDF function to extract a PRK from an initial seed `ikm` with context specific information +pub fn labeled_extract( + suite_id: &[u8], + salt: &[u8], + label: &str, + ikm: &[u8], + hash: &'static hkdf::Algorithm, +) -> hmac::Tag { + let key = hmac::Key::new(hash.hmac_algorithm(), salt); + let mut ctx = crate::hmac::Context::with_key(&key); + ctx.update(VERSION); + ctx.update(suite_id); + ctx.update(label.as_bytes()); + ctx.update(ikm); + ctx.sign() +} + +/// KDF function that allows expanding a `prk` with context specific information +/// The user must specify the buffer `out` with `length` bytes +pub fn labeled_expand<'a>( + suite_id: &'a [u8], + hash: &'static hkdf::Algorithm, + prk: &'a [u8], + label: &'a str, + info: &[u8], + length: u16, + out: &'a mut [u8], +) -> Result<(), error::Unspecified> { + let be_len = length.to_be_bytes(); + let prk = hkdf::Prk::new_less_safe(*hash, prk); + let labeled_info = [&be_len[0..2], VERSION, suite_id, label.as_bytes(), info]; + let okm = prk.expand(&labeled_info, HpkeWrap(length as usize))?; + okm.fill(out)?; + Ok(()) +} + +/// An implementation of `SecureRandom` that always fills the output slice +/// with the slice in `bytes`. The length of the slice given to `slice` +/// must match exactly. +#[derive(Debug)] +struct FixedSliceRandom<'a> { + pub bytes: &'a [u8], +} + +impl rand::sealed::SecureRandom for FixedSliceRandom<'_> { + fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { + dest.copy_from_slice(self.bytes); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::hpke::*; + use crate::rand; + + #[test] + fn generate_kp() { + let kem = Kem::new(KemId::X25519Sha256).unwrap(); + let _ = kem.generate_key_pair(None).unwrap(); + } + + #[test] + fn derive_kp() { + let kem = Kem::new(KemId::X25519Sha256).unwrap(); + let initial: [u8; 32] = rand::generate(&rand::SystemRandom::new()).unwrap().expose(); + let _ = kem.derive_key_pair(&initial).unwrap(); + } + + #[test] + fn test_setup_context() { + let receiver_kem = Kem::new(KemId::X25519Sha256).unwrap(); + let kp = receiver_kem.generate_key_pair(None).unwrap(); + let suite = Suite::with_existing_kem(receiver_kem, KdfId::Sha256, AeadId::ChaCha20Poly1305).unwrap(); + let mut encapped_key = [0u8; 32]; + let _ = suite + .new_sender_context(kp.public_key_bytes(), "test".as_bytes(), &mut encapped_key) + .unwrap(); + let _ = suite + .new_receiver_context(&kp, &encapped_key, "test".as_bytes()) + .unwrap(); + } + + #[test] + fn test_encrypt_to_recv() { + let receiver_kem = Kem::new(KemId::X25519Sha256).unwrap(); + let kp = receiver_kem.generate_key_pair(None).unwrap(); + let suite = Suite::with_existing_kem(receiver_kem, KdfId::Sha256, AeadId::ChaCha20Poly1305).unwrap(); + let mut encapped_key = [0u8; 32]; + let mut sender_ctx = suite + .new_sender_context(kp.public_key_bytes(), "test".as_bytes(), &mut encapped_key) + .unwrap(); + let mut receiver_ctx = suite + .new_receiver_context(&kp, &encapped_key, "test".as_bytes()) + .unwrap(); + let mut msg = b"jinx".to_vec(); + msg.resize(suite.aead_tag_len() + 4, 0); + sender_ctx.encrypt_to_receiver(&mut msg, &[]).unwrap(); + let mut ciphertext = msg.clone(); + assert_eq!(ciphertext.len(), 20); + let res = receiver_ctx.decrypt_from_sender(&[], &mut ciphertext); + assert_eq!(res.is_err(), false); + assert_eq!(&ciphertext[0..4], "jinx".as_bytes()); + } + + #[test] + fn test_encrypt_to_sender() { + let receiver_kem = Kem::new(KemId::X25519Sha256).unwrap(); + let kp = receiver_kem.generate_key_pair(None).unwrap(); + let suite = Suite::with_existing_kem(receiver_kem, KdfId::Sha256, AeadId::ChaCha20Poly1305).unwrap(); + let mut encapped_key = [0u8; 32]; + let mut sender_ctx = suite + .new_sender_context(&kp.public_key[..kp.public_key_len as usize], "test".as_bytes(), &mut encapped_key) + .unwrap(); + let mut receiver_ctx = suite + .new_receiver_context(&kp, &encapped_key, "test".as_bytes()) + .unwrap(); + let mut msg = b"jinx".to_vec(); + msg.resize(suite.aead_tag_len() + 4, 0); + sender_ctx.encrypt_to_receiver(&mut msg, &[]).unwrap(); + let mut ciphertext = msg.clone(); + assert_eq!(ciphertext.len(), 20); + let res = receiver_ctx.decrypt_from_sender(&[], &mut ciphertext); + assert_eq!(res.is_err(), false); + assert_eq!(&ciphertext[0..4], "jinx".as_bytes()); + + // encrypt to sender + let mut recv_msg = b"silco".to_vec(); + recv_msg.resize(suite.aead_tag_len() + 5, 0); + receiver_ctx.encrypt_to_sender(&mut recv_msg, &[]).unwrap(); + let mut new_ct = recv_msg.clone(); + assert_eq!(new_ct.len(), 21); + let new_res = sender_ctx.decrypt_from_receiver(&[], &mut new_ct); + assert_eq!(new_res.is_err(), false); + assert_eq!(&new_ct[0..5], "silco".as_bytes()); + } +} diff --git a/src/lib.rs b/src/lib.rs index f61d249743..78881c93fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ mod endian; pub mod error; pub mod hkdf; pub mod hmac; +pub mod hpke; mod limb; pub mod pbkdf2; pub mod pkcs8; diff --git a/tests/hpke_tests.rs b/tests/hpke_tests.rs new file mode 100644 index 0000000000..ead7bcc730 --- /dev/null +++ b/tests/hpke_tests.rs @@ -0,0 +1,178 @@ +use ring::{hpke, test, test_file}; +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +enum SectionType { + Setup, + Encryption, + Exporter, +} + +impl FromStr for SectionType { + type Err = (); + fn from_str(input: &str) -> Result { + match input { + "setup" => Ok(SectionType::Setup), + "enc" => Ok(SectionType::Encryption), + "exp" => Ok(SectionType::Exporter), + _ => Err(()), + } + } +} + +#[derive(Debug, Default, Clone, PartialEq)] +struct Setup { + mode: usize, + kem_id: usize, + kdf_id: usize, + aead_id: usize, + info: Vec, + ikm_e: Vec, + pke_m: Vec, + ske_m: Vec, + ikm_r: Vec, + pkr_m: Vec, + skr_m: Vec, + enc: Vec, + shared_secret: Vec, + key_schedule_context: Vec, + secret: Vec, + key: Vec, + base_nonce: Vec, + exporter_secret: Vec, +} + +struct Context { + client_ctx: Option, + server_ctx: Option, + suite: Option, + seq: u64, +} + +#[derive(Debug, Default, Clone, PartialEq)] +struct Enc { + sequence_number: usize, + pt: Vec, + aad: Vec, + nonce: Vec, + ct: Vec, +} + +#[derive(Debug, Default, Clone, PartialEq)] +struct Exp { + exporter_ctx: Vec, + len: usize, + exported: Vec, +} + +#[test] +fn hpke_tests() { + let mut latest_s = Setup::default(); + let mut ctx = Context { + client_ctx: None, + server_ctx: None, + suite: None, + seq: 0, + }; + test::run(test_file!("hpke_tests.txt"), |section, test_case| { + let s: Vec<&str> = section.split("-").collect(); + let s_type = SectionType::from_str(s[0]).unwrap(); + + match s_type { + SectionType::Setup => { + let setup = Setup { + mode: test_case.consume_usize("mode"), + kem_id: test_case.consume_usize("kem_id"), + kdf_id: test_case.consume_usize("kdf_id"), + aead_id: test_case.consume_usize("aead_id"), + info: test_case.consume_bytes("info"), + ikm_e: test_case.consume_bytes("ikmE"), + pke_m: test_case.consume_bytes("pkEm"), + ske_m: test_case.consume_bytes("skEm"), + ikm_r: test_case.consume_bytes("ikmR"), + pkr_m: test_case.consume_bytes("pkRm"), + skr_m: test_case.consume_bytes("skRm"), + enc: test_case.consume_bytes("enc"), + shared_secret: test_case.consume_bytes("shared_secret"), + key_schedule_context: test_case.consume_bytes("key_schedule_context"), + secret: test_case.consume_bytes("secret"), + key: test_case.consume_bytes("key"), + base_nonce: test_case.consume_bytes("base_nonce"), + exporter_secret: test_case.consume_bytes("exporter_secret"), + }; + latest_s = setup.clone(); + let kem = hpke::Kem::from_u16(setup.kem_id as u16).unwrap(); + let kp = kem.derive_key_pair(&setup.ikm_r).unwrap(); + assert_eq!(setup.pkr_m, kp.public_key_bytes()); + let mut encapped_key = vec![0u8; kem.public_key_length()]; + let suite = + hpke::Suite::from_u16_with_existing_kem(kem, setup.kdf_id as u16, setup.aead_id as u16) + .unwrap(); + let client_ctx = suite + .new_deterministic_sender_context( + kp.public_key_bytes(), + &setup.info, + &setup.ikm_e, + &mut encapped_key, + ) + .unwrap(); + assert_eq!(setup.enc, encapped_key); + let server_ctx = suite + .new_receiver_context(&kp, &encapped_key, &setup.info) + .unwrap(); + ctx.client_ctx = Some(client_ctx); + ctx.server_ctx = Some(server_ctx); + ctx.suite = Some(suite); + ctx.seq = 0; + } + SectionType::Encryption => { + let enc = Enc { + sequence_number: test_case.consume_usize("sequence_number"), + pt: test_case.consume_bytes("pt"), + aad: test_case.consume_bytes("aad"), + nonce: test_case.consume_bytes("nonce"), + ct: test_case.consume_bytes("ct"), + }; + let mut msg = enc.pt.clone(); + let mut tag = vec![0u8; ctx.suite.as_ref().unwrap().aead_tag_len()]; + msg.append(&mut tag); + for i in 0..enc.sequence_number-ctx.seq as usize { + let mut msg = vec![0u8; ctx.suite.as_ref().unwrap().aead_tag_len()]; + ctx.client_ctx.as_mut().unwrap().encrypt_to_receiver(&mut msg, &[]).unwrap(); + ctx.server_ctx.as_mut().unwrap().decrypt_from_sender(&[], &mut msg).unwrap(); + ctx.seq += 1 + } + ctx.client_ctx + .as_mut() + .unwrap() + .encrypt_to_receiver(&mut msg, &enc.aad) + .unwrap(); + + msg.extend_from_slice(&tag); + assert_eq!(enc.ct, msg); + ctx.server_ctx + .as_mut() + .unwrap() + .decrypt_from_sender(&enc.aad, &mut msg) + .unwrap(); + ctx.seq += 1; + assert_eq!(enc.pt, &msg[0..enc.pt.len()]); + } + SectionType::Exporter => { + let exp = Exp { + exporter_ctx: test_case.consume_bytes("exporter_context"), + len: test_case.consume_usize("L"), + exported: test_case.consume_bytes("exported_value"), + }; + let mut exported = vec![0u8; exp.len]; + ctx.client_ctx + .as_ref() + .unwrap() + .export(&exp.exporter_ctx, exp.len as u16, &mut exported) + .unwrap(); + assert_eq!(exp.exported, exported); + } + }; + Ok(()) + }); +} diff --git a/tests/hpke_tests.txt b/tests/hpke_tests.txt new file mode 100644 index 0000000000..b249c9db1e --- /dev/null +++ b/tests/hpke_tests.txt @@ -0,0 +1,366 @@ +# Test vectors from draft-irtf-cfrg-hpke-12 + +# DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM + +# Base Mode +[setup] +mode = 0 +kem_id = 32 +kdf_id = 1 +aead_id = 1 +info = 4f6465206f6e2061204772656369616e2055726e +ikmE = 7268600d403fce431561aef583ee1613527cff655c1343f29812e66706df3234 +pkEm = 37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431 +skEm = 52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736 +ikmR = 6db9df30aa07dd42ee5e8181afdb977e538f5e1fec8a06223f33f7013e525037 +pkRm = 3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d +skRm = 4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8 +enc = 37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431 +shared_secret = fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc +key_schedule_context = 00725611c9d98c07c03f60095cd32d400d8347d45ed67097bbad50fc56da742d07cb6cffde367bb0565ba28bb02c90744a20f5ef37f30523526106f637abb05449 +secret = 12fff91991e93b48de37e7daddb52981084bd8aa64289c3788471d9a9712f397 +key = 4531685d41d65f03dc48f6b8302c05b0 +base_nonce = 56d890e5accaaf011cff4b7d +exporter_secret = 45ff1c2e220db587171952c0592d5f5ebe103f1561a2614e38f2ffd47e99e3f8 + +[enc] +sequence_number = 0 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d30 +nonce = 56d890e5accaaf011cff4b7d +ct = f938558b5d72f1a23810b4be2ab4f84331acc02fc97babc53a52ae8218a355a96d8770ac83d07bea87e13c512a + +sequence_number = 1 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d31 +nonce = 56d890e5accaaf011cff4b7c +ct = af2d7e9ac9ae7e270f46ba1f975be53c09f8d875bdc8535458c2494e8a6eab251c03d0c22a56b8ca42c2063b84 + +sequence_number = 2 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d32 +nonce = 56d890e5accaaf011cff4b7f +ct = 498dfcabd92e8acedc281e85af1cb4e3e31c7dc394a1ca20e173cb72516491588d96a19ad4a683518973dcc180 + +sequence_number = 4 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d34 +nonce = 56d890e5accaaf011cff4b79 +ct = 583bd32bc67a5994bb8ceaca813d369bca7b2a42408cddef5e22f880b631215a09fc0012bc69fccaa251c0246d + +sequence_number = 255 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323535 +nonce = 56d890e5accaaf011cff4b82 +ct = 7175db9717964058640a3a11fb9007941a5d1757fda1a6935c805c21af32505bf106deefec4a49ac38d71c9e0a + +sequence_number = 256 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323536 +nonce = 56d890e5accaaf011cff4a7d +ct = 957f9800542b0b8891badb026d79cc54597cb2d225b54c00c5238c25d05c30e3fbeda97d2e0e1aba483a2df9f2 + +[exp] +exporter_context = "" +L = 32 +exported_value = 3853fe2b4035195a573ffc53856e77058e15d9ea064de3e59f4961d0095250ee + +exporter_context = 00 +L = 32 +exported_value = 2e8f0b54673c7029649d4eb9d5e33bf1872cf76d623ff164ac185da9e88c21a5 + +exporter_context = 54657374436f6e74657874 +L = 32 +exported_value = e9e43065102c3836401bed8c3c3c75ae46be1639869391d62c61f1ec7af54931 + + +# DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305 + +# Base Mode +[setup] +mode = 0 +kem_id = 32 +kdf_id = 1 +aead_id = 3 +info = 4f6465206f6e2061204772656369616e2055726e +ikmE = 909a9b35d3dc4713a5e72a4da274b55d3d3821a37e5d099e74a647db583a904b +pkEm = 1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a +skEm = f4ec9b33b792c372c1d2c2063507b684ef925b8c75a42dbcbf57d63ccd381600 +ikmR = 1ac01f181fdf9f352797655161c58b75c656a6cc2716dcb66372da835542e1df +pkRm = 4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a +skRm = 8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb +enc = 1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a +shared_secret = 0bbe78490412b4bbea4812666f7916932b828bba79942424abb65244930d69a7 +key_schedule_context = 00431df6cd95e11ff49d7013563baf7f11588c75a6611ee2a4404a49306ae4cfc5b69c5718a60cc5876c358d3f7fc31ddb598503f67be58ea1e798c0bb19eb9796 +secret = 5b9cd775e64b437a2335cf499361b2e0d5e444d5cb41a8a53336d8fe402282c6 +key = ad2744de8e17f4ebba575b3f5f5a8fa1f69c2a07f6e7500bc60ca6e3e3ec1c91 +base_nonce = 5c4d98150661b848853b547f +exporter_secret = a3b010d4994890e2c6968a36f64470d3c824c8f5029942feb11e7a74b2921922 + +[enc] +sequence_number = 0 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d30 +nonce = 5c4d98150661b848853b547f +ct = 1c5250d8034ec2b784ba2cfd69dbdb8af406cfe3ff938e131f0def8c8b60b4db21993c62ce81883d2dd1b51a28 + +sequence_number = 1 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d31 +nonce = 5c4d98150661b848853b547e +ct = 6b53c051e4199c518de79594e1c4ab18b96f081549d45ce015be002090bb119e85285337cc95ba5f59992dc98c + +sequence_number = 2 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d32 +nonce = 5c4d98150661b848853b547d +ct = 71146bd6795ccc9c49ce25dda112a48f202ad220559502cef1f34271e0cb4b02b4f10ecac6f48c32f878fae86b + +sequence_number = 4 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d34 +nonce = 5c4d98150661b848853b547b +ct = 63357a2aa291f5a4e5f27db6baa2af8cf77427c7c1a909e0b37214dd47db122bb153495ff0b02e9e54a50dbe16 + +sequence_number = 255 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323535 +nonce = 5c4d98150661b848853b5480 +ct = 18ab939d63ddec9f6ac2b60d61d36a7375d2070c9b683861110757062c52b8880a5f6b3936da9cd6c23ef2a95c + +sequence_number = 256 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323536 +nonce = 5c4d98150661b848853b557f +ct = 7a4a13e9ef23978e2c520fd4d2e757514ae160cd0cd05e556ef692370ca53076214c0c40d4c728d6ed9e727a5b + +[exp] +exporter_context = "" +L = 32 +exported_value = 4bbd6243b8bb54cec311fac9df81841b6fd61f56538a775e7c80a9f40160606e + +exporter_context = 00 +L = 32 +exported_value = 8c1df14732580e5501b00f82b10a1647b40713191b7c1240ac80e2b68808ba69 + +exporter_context = 54657374436f6e74657874 +L = 32 +exported_value = 5acb09211139c43b3090489a9da433e8a30ee7188ba8b0a9a1ccf0c229283e53 + +# DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM +# Base Mode +[setup] +mode = 0 +kem_id = 16 +kdf_id = 1 +aead_id = 1 +info = 4f6465206f6e2061204772656369616e2055726e +ikmE = 4270e54ffd08d79d5928020af4686d8f6b7d35dbe470265f1f5aa22816ce860e +pkEm = 04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4 +skEm = 4995788ef4b9d6132b249ce59a77281493eb39af373d236a1fe415cb0c2d7beb +ikmR = 668b37171f1072f3cf12ea8a236a45df23fc13b82af3609ad1e354f6ef817550 +pkRm = 04fe8c19ce0905191ebc298a9245792531f26f0cece2460639e8bc39cb7f706a826a779b4cf969b8a0e539c7f62fb3d30ad6aa8f80e30f1d128aafd68a2ce72ea0 +skRm = f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2 +enc = 04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4 +shared_secret = c0d26aeab536609a572b07695d933b589dcf363ff9d93c93adea537aeabb8cb8 +key_schedule_context = 00b88d4e6d91759e65e87c470e8b9141113e9ad5f0c8ceefc1e088c82e6980500798e486f9c9c09c9b5c753ac72d6005de254c607d1b534ed11d493ae1c1d9ac85 +secret = 2eb7b6bf138f6b5aff857414a058a3f1750054a9ba1f72c2cf0684a6f20b10e1 +key = 868c066ef58aae6dc589b6cfdd18f97e +base_nonce = 4e0bc5018beba4bf004cca59 +exporter_secret = 14ad94af484a7ad3ef40e9f3be99ecc6fa9036df9d4920548424df127ee0d99f + +[enc] +sequence_number = 0 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d30 +nonce = 4e0bc5018beba4bf004cca59 +ct = 5ad590bb8baa577f8619db35a36311226a896e7342a6d836d8b7bcd2f20b6c7f9076ac232e3ab2523f39513434 + +sequence_number = 1 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d31 +nonce = 4e0bc5018beba4bf004cca58 +ct = fa6f037b47fc21826b610172ca9637e82d6e5801eb31cbd3748271affd4ecb06646e0329cbdf3c3cd655b28e82 + +sequence_number = 2 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d32 +nonce = 4e0bc5018beba4bf004cca5b +ct = 895cabfac50ce6c6eb02ffe6c048bf53b7f7be9a91fc559402cbc5b8dcaeb52b2ccc93e466c28fb55fed7a7fec + +sequence_number = 4 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d34 +nonce = 4e0bc5018beba4bf004cca5d +ct = 8787491ee8df99bc99a246c4b3216d3d57ab5076e18fa27133f520703bc70ec999dd36ce042e44f0c3169a6a8f + +sequence_number = 255 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323535 +nonce = 4e0bc5018beba4bf004ccaa6 +ct = 2ad71c85bf3f45c6eca301426289854b31448bcf8a8ccb1deef3ebd87f60848aa53c538c30a4dac71d619ee2cd + +sequence_number = 256 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323536 +nonce = 4e0bc5018beba4bf004ccb59 +ct = 10f179686aa2caec1758c8e554513f16472bd0a11e2a907dde0b212cbe87d74f367f8ffe5e41cd3e9962a6afb2 + +[exp] +exporter_context = "" +L = 32 +exported_value = 5e9bc3d236e1911d95e65b576a8a86d478fb827e8bdfe77b741b289890490d4d + +exporter_context = 00 +L = 32 +exported_value = 6cff87658931bda83dc857e6353efe4987a201b849658d9b047aab4cf216e796 + +exporter_context = 54657374436f6e74657874 +L = 32 +exported_value = d8f1ea7942adbba7412c6d431c62d01371ea476b823eb697e1f6e6cae1dab85a + +# DHKEM(P-256, HKDF-SHA256), HKDF-SHA512, AES-128-GCM + +# Base Mode +[setup] +mode = 0 +kem_id = 16 +kdf_id = 3 +aead_id = 1 +info = 4f6465206f6e2061204772656369616e2055726e +ikmE = 4ab11a9dd78c39668f7038f921ffc0993b368171d3ddde8031501ee1e08c4c9a +pkEm = 0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580 +skEm = 2292bf14bb6e15b8c81a0f45b7a6e93e32d830e48cca702e0affcfb4d07e1b5c +ikmR = ea9ff7cc5b2705b188841c7ace169290ff312a9cb31467784ca92d7a2e6e1be8 +pkRm = 04085aa5b665dc3826f9650ccbcc471be268c8ada866422f739e2d531d4a8818a9466bc6b449357096232919ec4fe9070ccbac4aac30f4a1a53efcf7af90610edd +skRm = 3ac8530ad1b01885960fab38cf3cdc4f7aef121eaa239f222623614b4079fb38 +enc = 0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a472580 +shared_secret = 02f584736390fc93f5b4ad039826a3fa08e9911bd1215a3db8e8791ba533cafd +key_schedule_context = 005b8a3617af7789ee716e7911c7e77f84cdc4cc46e60fb7e19e4059f9aeadc00585e26874d1ddde76e551a7679cd47168c466f6e1f705cc9374c192778a34fcd5ca221d77e229a9d11b654de7942d685069c633b2362ce3b3d8ea4891c9a2a87a4eb7cdb289ba5e2ecbf8cd2c8498bb4a383dc021454d70d46fcbbad1252ef4f9 +secret = 0c7acdab61693f936c4c1256c78e7be30eebfe466812f9cc49f0b58dc970328dfc03ea359be0250a471b1635a193d2dfa8cb23c90aa2e25025b892a725353eeb +key = 090ca96e5f8aa02b69fac360da50ddf9 +base_nonce = 9c995e621bf9a20c5ca45546 +exporter_secret = 4a7abb2ac43e6553f129b2c5750a7e82d149a76ed56dc342d7bca61e26d494f4855dff0d0165f27ce57756f7f16baca006539bb8e4518987ba610480ac03efa8 + +[enc] +sequence_number = 0 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d30 +nonce = 9c995e621bf9a20c5ca45546 +ct = d3cf4984931484a080f74c1bb2a6782700dc1fef9abe8442e44a6f09044c88907200b332003543754eb51917ba + +sequence_number = 1 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d31 +nonce = 9c995e621bf9a20c5ca45547 +ct = d14414555a47269dfead9fbf26abb303365e40709a4ed16eaefe1f2070f1ddeb1bdd94d9e41186f124e0acc62d + +sequence_number = 2 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d32 +nonce = 9c995e621bf9a20c5ca45544 +ct = 9bba136cade5c4069707ba91a61932e2cbedda2d9c7bdc33515aa01dd0e0f7e9d3579bf4016dec37da4aafa800 + +sequence_number = 4 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d34 +nonce = 9c995e621bf9a20c5ca45542 +ct = a531c0655342be013bf32112951f8df1da643602f1866749519f5dcb09cc68432579de305a77e6864e862a7600 + +sequence_number = 255 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323535 +nonce = 9c995e621bf9a20c5ca455b9 +ct = be5da649469efbad0fb950366a82a73fefeda5f652ec7d3731fac6c4ffa21a7004d2ab8a04e13621bd3629547d + +sequence_number = 256 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323536 +nonce = 9c995e621bf9a20c5ca45446 +ct = 62092672f5328a0dde095e57435edf7457ace60b26ee44c9291110ec135cb0e14b85594e4fea11247d937deb62 + +[exp] +exporter_context = "" +L = 32 +exported_value = a32186b8946f61aeead1c093fe614945f85833b165b28c46bf271abf16b57208 + +exporter_context = 00 +L = 32 +exported_value = 84998b304a0ea2f11809398755f0abd5f9d2c141d1822def79dd15c194803c2a + +exporter_context = 54657374436f6e74657874 +L = 32 +exported_value = 93fb9411430b2cfa2cf0bed448c46922a5be9beff20e2e621df7e4655852edbc + +# DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305 + +# Base Mode +[setup] +mode = 0 +kem_id = 16 +kdf_id = 1 +aead_id = 3 +info = 4f6465206f6e2061204772656369616e2055726e +ikmE = f1f1a3bc95416871539ecb51c3a8f0cf608afb40fbbe305c0a72819d35c33f1f +pkEm = 04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824fc1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e381291 +skEm = 7550253e1147aae48839c1f8af80d2770fb7a4c763afe7d0afa7e0f42a5b3689 +ikmR = 61092f3f56994dd424405899154a9918353e3e008171517ad576b900ddb275e7 +pkRm = 04a697bffde9405c992883c5c439d6cc358170b51af72812333b015621dc0f40bad9bb726f68a5c013806a790ec716ab8669f84f6b694596c2987cf35baba2a006 +skRm = a4d1c55836aa30f9b3fbb6ac98d338c877c2867dd3a77396d13f68d3ab150d3b +enc = 04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824fc1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e381291 +shared_secret = 806520f82ef0b03c823b7fc524b6b55a088f566b9751b89551c170f4113bd850 +key_schedule_context = 00b738cd703db7b4106e93b4621e9a19c89c838e55964240e5d3f331aaf8b0d58b2e986ea1c671b61cf45eec134dac0bae58ec6f63e790b1400b47c33038b0269c +secret = fe891101629aa355aad68eff3cc5170d057eca0c7573f6575e91f9783e1d4506 +key = a8f45490a92a3b04d1dbf6cf2c3939ad8bfc9bfcb97c04bffe116730c9dfe3fc +base_nonce = 726b4390ed2209809f58c693 +exporter_secret = 4f9bd9b3a8db7d7c3a5b9d44fdc1f6e37d5d77689ade5ec44a7242016e6aa205 + +[enc] +sequence_number = 0 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d30 +nonce = 726b4390ed2209809f58c693 +ct = 6469c41c5c81d3aa85432531ecf6460ec945bde1eb428cb2fedf7a29f5a685b4ccb0d057f03ea2952a27bb458b + +sequence_number = 1 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d31 +nonce = 726b4390ed2209809f58c692 +ct = f1564199f7e0e110ec9c1bcdde332177fc35c1adf6e57f8d1df24022227ffa8716862dbda2b1dc546c9d114374 + +sequence_number = 2 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d32 +nonce = 726b4390ed2209809f58c691 +ct = 39de89728bcb774269f882af8dc5369e4f3d6322d986e872b3a8d074c7c18e8549ff3f85b6d6592ff87c3f310c + +sequence_number = 4 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d34 +nonce = 726b4390ed2209809f58c697 +ct = bc104a14fbede0cc79eeb826ea0476ce87b9c928c36e5e34dc9b6905d91473ec369a08b1a25d305dd45c6c5f80 + +sequence_number = 255 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323535 +nonce = 726b4390ed2209809f58c66c +ct = 8f2814a2c548b3be50259713c6724009e092d37789f6856553d61df23ebc079235f710e6af3c3ca6eaba7c7c6c + +sequence_number = 256 +pt = 4265617574792069732074727574682c20747275746820626561757479 +aad = 436f756e742d323536 +nonce = 726b4390ed2209809f58c793 +ct = b45b69d419a9be7219d8c94365b89ad6951caf4576ea4774ea40e9b7047a09d6537d1aa2f7c12d6ae4b729b4d0 + +[exp] +exporter_context = "" +L = 32 +exported_value = 9b13c510416ac977b553bf1741018809c246a695f45eff6d3b0356dbefe1e660 + +exporter_context = 00 +L = 32 +exported_value = 6c8b7be3a20a5684edecb4253619d9051ce8583baf850e0cb53c402bdcaf8ebb + +exporter_context = 54657374436f6e74657874 +L = 32 +exported_value = 477a50d804c7c51941f69b8e32fe8288386ee1a84905fe4938d58972f24ac938