diff --git a/mla/src/crypto/hpke.rs b/mla/src/crypto/hpke.rs index 21dc9c7c..ea9f095f 100644 --- a/mla/src/crypto/hpke.rs +++ b/mla/src/crypto/hpke.rs @@ -99,8 +99,11 @@ type HpkeAead = HPKEAesGcm256; const HPKE_MODE_BASE: u8 = 0; /// Custom KEM ID, not in the RFC 9180 -/// Hybrid : DHKEM(X25519, HKDF-SHA256) + MLKEM +/// Hybrid : DHKEM(X25519, HKDF-SHA256) + MLKEM, wrapping a shared secret to support multi-recipient const HYBRID_KEM_ID: u16 = 0x1020; +/// Custom KEM ID, not in the RFC 9180 +/// Hybrid Recipient : DHKEM(X25519, HKDF-SHA256) + MLKEM, used internally in the Hybrid KEM to wrap the per-recipient shared secret +const HYBRID_KEM_RECIPIENT_ID: u16 = 0x1120; /// Return the suite_id for the Hybrid KEM (RFC 9180 §5.1) /// suite_id = concat( @@ -121,9 +124,7 @@ fn build_suite_id(kem_id: u16) -> [u8; 10] { out } -/// Key schedule for the sender (RFC 9180 §5.1), internal version -/// -/// This version is kept for testing purpose (against RFC 9180 test vectors) +/// Key schedule (RFC 9180 §5.1), mode Base /// /// Parameters are: /// - `shared_secret`: the shared secret from the Hybrid KEM @@ -134,7 +135,7 @@ fn build_suite_id(kem_id: u16) -> [u8; 10] { /// - Kem: `kem_id` /// - Kdf: HKDF-SHA512 /// - Aead: AES-GCM-256 -fn key_schedule_s_internal( +fn key_schedule_base( shared_secret: &[u8], info: &[u8], kem_id: u16, @@ -164,19 +165,20 @@ fn key_schedule_s_internal( Ok((key, base_nonce)) } -/// Key schedule for the sender (RFC 9180 §5.1) -/// -/// Parameters are: -/// - `shared_secret`: the shared secret from the Hybrid KEM -/// - mode: set to Base (no PSK nor sender key) -/// - info: set to "MLA Encrypt Layer" -/// - psk: no PSK, because the mode used is "Base" -/// - algorithms: -/// - Kem: HYBRID_KEM_ID (custom value) -/// - Kdf: HKDF-SHA512 -/// - Aead: AES-GCM-256 -pub(crate) fn key_schedule_s(shared_secret: &[u8], info: &[u8]) -> Result<(Key, Nonce), Error> { - key_schedule_s_internal(shared_secret, info, HYBRID_KEM_ID) +/// Key schedule (RFC 9180 §5.1), mode Base, for the custom multi-recipient Hybrid KEM +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) +} + +/// Key schedule (RFC 9180 §5.1), mode Base, for the custom per-recipient Hybrid KEM +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) } /// Compute the nonce for a given sequence number (RFC 9180 §5.2) @@ -323,9 +325,9 @@ mod tests { /// Use A.6 for HKDF-SHA512 and AES-256-GCM /// In MLA, we rather use a custom Kem ID (Hybrid KEM), but this method does the main job #[test] - fn test_key_schedule_s_internal() { + fn test_key_schedule_base() { let (key, nonce) = - key_schedule_s_internal(&RFC_A6_SHARED_SECRET, &RFC_A6_INFO, RFC_A6_KEM_ID).unwrap(); + 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); } diff --git a/mla/src/crypto/hybrid.rs b/mla/src/crypto/hybrid.rs index 6a2e9d37..7815a5a9 100644 --- a/mla/src/crypto/hybrid.rs +++ b/mla/src/crypto/hybrid.rs @@ -1,5 +1,7 @@ use crate::crypto::aesgcm::{ConstantTimeEq, Key, KEY_SIZE, TAG_LENGTH}; -use crate::crypto::hpke::{dhkem_decap, dhkem_encap, key_schedule_s, DHKEMCiphertext}; +use crate::crypto::hpke::{ + dhkem_decap, dhkem_encap, key_schedule_base_hybrid_kem_recipient, DHKEMCiphertext, +}; use crate::errors::ConfigError; use crate::layers::encrypt::get_crypto_rng; use hkdf::Hkdf; @@ -187,8 +189,9 @@ impl Decapsulate for &recipient.ct_ml, ); - let (unwrap_key, unwrap_nonce) = key_schedule_s(&ss_recipient, HPKE_INFO_RECIPIENT) - .or(Err(ConfigError::KeyWrappingComputationError))?; + let (unwrap_key, unwrap_nonce) = + key_schedule_base_hybrid_kem_recipient(&ss_recipient, HPKE_INFO_RECIPIENT) + .or(Err(ConfigError::KeyWrappingComputationError))?; // Unwrap the candidate shared secret and check it using AES-GCM tag validation let mut cipher = crate::crypto::aesgcm::AesGcm256::new( @@ -255,8 +258,9 @@ impl Encapsulate let ss_recipient = combine(&ss_ecc.0, ss_ml, &ct_ecc.to_bytes(), ct_ml); // Wrap the final shared secret - let (wrap_key, wrap_nonce) = key_schedule_s(&ss_recipient, HPKE_INFO_RECIPIENT) - .or(Err(ConfigError::KeyWrappingComputationError))?; + let (wrap_key, wrap_nonce) = + key_schedule_base_hybrid_kem_recipient(&ss_recipient, HPKE_INFO_RECIPIENT) + .or(Err(ConfigError::KeyWrappingComputationError))?; let mut cipher = crate::crypto::aesgcm::AesGcm256::new( &wrap_key, &wrap_nonce, diff --git a/mla/src/layers/encrypt.rs b/mla/src/layers/encrypt.rs index d288e629..b69f78b2 100644 --- a/mla/src/layers/encrypt.rs +++ b/mla/src/layers/encrypt.rs @@ -2,7 +2,7 @@ use crate::crypto::aesgcm::{ AesGcm256, ConstantTimeEq, Key, Nonce, Tag, KEY_COMMITMENT_SIZE, TAG_LENGTH, }; -use crate::crypto::hpke::{compute_nonce, key_schedule_s}; +use crate::crypto::hpke::{compute_nonce, key_schedule_base_hybrid_kem}; use crate::crypto::hybrid::{ HybridKemSharedSecret, HybridMultiRecipientEncapsulatedKey, HybridMultiRecipientsPublicKeys, HybridPrivateKey, HybridPublicKey, @@ -154,7 +154,7 @@ pub(crate) struct InternalEncryptionConfig { impl InternalEncryptionConfig { fn from(shared_secret: HybridKemSharedSecret) -> Result { - let (key, nonce) = key_schedule_s(&shared_secret.0, HPKE_INFO_LAYER)?; + let (key, nonce) = key_schedule_base_hybrid_kem(&shared_secret.0, HPKE_INFO_LAYER)?; Ok(Self { key, nonce }) } @@ -245,7 +245,7 @@ impl EncryptionReaderConfig { } for private_key in &self.private_keys { if let Ok(ss_hybrid) = private_key.decapsulate(&config.multi_recipient) { - let (key, nonce) = key_schedule_s(&ss_hybrid.0, HPKE_INFO_LAYER) + let (key, nonce) = key_schedule_base_hybrid_kem(&ss_hybrid.0, HPKE_INFO_LAYER) .or(Err(ConfigError::KeyWrappingComputationError))?; self.encrypt_parameters = Some((key, nonce)); break;