diff --git a/common/nym-kkt/src/carrier.rs b/common/nym-kkt/src/carrier.rs index 1ea440629b..38686e97f9 100644 --- a/common/nym-kkt/src/carrier.rs +++ b/common/nym-kkt/src/carrier.rs @@ -1,74 +1,15 @@ use libcrux_chacha20poly1305::TAG_LEN; -use libcrux_psq::handshake::types::{DHKeyPair, DHPrivateKey, DHPublicKey}; -use nym_crypto::hkdf::blake3::{derive_key_blake3, derive_key_blake3_multi_input}; -use nym_kkt_ciphersuite::x25519::PUBLIC_KEY_LENGTH; +use libcrux_psq::handshake::types::{DHKeyPair, DHPublicKey}; +use nym_crypto::hkdf::blake3::derive_key_blake3; use rand09::{CryptoRng, RngCore}; use zeroize::{Zeroize, ZeroizeOnDrop}; -use crate::{ - ciphersuite::EncapsulationKey, - context::{KKTContext, KKTRole}, - error::KKTError, - frame::{KKT_SESSION_ID_LEN, KKTFrame}, - masked_byte::{MASKED_BYTE_LEN, MaskedByte}, -}; +use crate::error::KKTError; -#[derive(Zeroize, ZeroizeOnDrop)] -pub struct KKTCarrier(Carrier); -impl KKTCarrier { - fn read_message(&mut self) { - // if we are reading a message and we have not read or written a message before - // this means that we are a responder who is - // reading the initiator's first handshake message - if self.0.rx_counter() == 1 && self.0.tx_counter() == 1 { - // in this case, we parse out the eph public key, the nonce, and the version hash - // we decode to see if the hash is - } - // if we are reading a message and we have written a message before - // this means that we are an initiator - // who is reading the responder's handshake message - if self.0.rx_counter() == 1 && self.0.tx_counter() > 1 {} - } - fn write_message(&mut self) {} - - // Generate carrier and encrypt first message - - // the first message would look like this - // (outer_header || eph_pk || - - // fn init( - // rng: &mut R, - // responder_public_key: &DHPublicKey, - // payload: &[u8], - // header: Option<&[u8]>, - // ) -> Result<(Self, Vec), KKTError> - // where - // R: RngCore + CryptoRng, - // { - // let ephemeral_keypair = DHKeyPair::new(rng); - // let shared_secret = ephemeral_keypair - // .sk() - // .diffie_hellman(responder_public_key) - // .map_err(|_| KKTError::X25519Error { - // info: "Key Derivation Error", - // })?; - - // let carrier = Carrier::from_secret(&shared_secret.as_ref(), context); - - // match header { - // Some(header) => {} - // } - // } - - fn respond(responder_private_key: &DHPrivateKey, expects_header: bool) -> Self { - todo!() - } -} // This is arbitrary pub const MAX_PAYLOAD_LEN: usize = 1_000_000; const CARRIER_KDF_INFO_TX: &str = "CARRIER_V1_KDF_RX"; const CARRIER_KDF_INFO_RX: &str = "CARRIER_V1_KDF_TX"; -const KKT_CARRIER_CONTEXT: &[u8] = b"CARRIER_V1_KKT_V1_KDF"; #[derive(Zeroize, ZeroizeOnDrop)] pub struct Carrier { @@ -105,8 +46,8 @@ fn as_nonce_bytes(nonce: u64) -> [u8; 12] { impl Carrier { fn init(tx_key: [u8; 32], rx_key: [u8; 32]) -> Self { Self { - tx_key: tx_key, - rx_key: rx_key, + tx_key, + rx_key, tx_counter: 1, rx_counter: 1, } @@ -133,116 +74,20 @@ impl Carrier { )) } - pub(crate) fn tx_counter(&self) -> u64 { - self.tx_counter - } - - pub(crate) fn rx_counter(&self) -> u64 { - self.rx_counter - } - - pub fn new_kkt_responder( - responder_keypair: &DHKeyPair, - message: &[u8], - supported_versions: &[u8], - ) -> Result<(Carrier, KKTFrame, KKTContext), KKTError> { - let mut initiator_public_key_bytes: [u8; PUBLIC_KEY_LENGTH] = [0; PUBLIC_KEY_LENGTH]; - initiator_public_key_bytes.clone_from_slice(&message[0..PUBLIC_KEY_LENGTH]); - - // check mask - - // todo: deal with this - let masked_byte = - MaskedByte::try_from(&message[PUBLIC_KEY_LENGTH..PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN]) - .unwrap(); - - let mut mask = Vec::from(&initiator_public_key_bytes); - mask.extend_from_slice(responder_keypair.pk.as_ref()); - - // todo: deal with this - let byte = masked_byte.unmask(&mask).unwrap(); - - // todo: derive version from byte - - if supported_versions.iter().find(|x| *x == &byte).is_some() { - // now that the version is ok, we can try dh - - let initiator_public_key = DHPublicKey::from_bytes(&initiator_public_key_bytes); - - let shared_secret = responder_keypair - .sk() - .diffie_hellman(&initiator_public_key) - .map_err(|_| KKTError::X25519Error { - info: "Key Derivation Error", - })?; - - let mut context = Vec::from(masked_byte.as_slice()); - context.extend_from_slice(&KKT_CARRIER_CONTEXT); - context.extend_from_slice(&initiator_public_key.as_ref()); - context.extend_from_slice(&responder_keypair.pk.as_ref()); - - let mut carrier = Self::from_secret_slice(shared_secret.as_ref(), &context).flip_keys(); - - let decrypted_message = - carrier.decrypt(&message[PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN..])?; - let (frame, context) = KKTFrame::from_bytes(&decrypted_message)?; - - Ok((carrier, frame, context)) - } else { - panic!("unsupported protocol version"); - } - } - pub fn new_kkt_initiator( - rng: &mut R, - responder_public_key: &DHPublicKey, - version_byte: u8, - kkt_frame: &KKTFrame, - ) -> Result<(Self, Vec), KKTError> - where - R: CryptoRng + RngCore, - { - let ephemeral_keypair = DHKeyPair::new(rng); - let shared_secret = ephemeral_keypair - .sk() - .diffie_hellman(responder_public_key) - .map_err(|_| KKTError::X25519Error { - info: "Key Derivation Error", - })?; - - let mut mask = Vec::from(ephemeral_keypair.pk.as_ref()); - mask.extend_from_slice(responder_public_key.as_ref()); - - let masked_byte = MaskedByte::new(version_byte, &mask); - - let mut context = Vec::from(masked_byte.as_slice()); - context.extend_from_slice(&KKT_CARRIER_CONTEXT); - context.extend_from_slice(&ephemeral_keypair.pk.as_ref()); - context.extend_from_slice(&responder_public_key.as_ref()); - - let mut carrier = Self::from_secret_slice(shared_secret.as_ref(), &context); - - let mut full_kkt_message = Vec::from(ephemeral_keypair.pk.as_ref()); - full_kkt_message.extend_from_slice(masked_byte.as_slice()); - let encrypted_kkt_frame = carrier.encrypt(&kkt_frame.to_bytes())?; - full_kkt_message.extend_from_slice(&encrypted_kkt_frame); - - Ok((carrier, full_kkt_message)) - } - - pub fn from_secret_slice(secret: &[u8], context: &[u8]) -> Self { - let tx_key = derive_key_blake3(CARRIER_KDF_INFO_TX, secret, &context); - let rx_key = derive_key_blake3(CARRIER_KDF_INFO_RX, secret, &context); + pub(crate) fn from_secret_slice(secret: &[u8], context: &[u8]) -> Self { + let tx_key = derive_key_blake3(CARRIER_KDF_INFO_TX, secret, context); + let rx_key = derive_key_blake3(CARRIER_KDF_INFO_RX, secret, context); Self::init(tx_key, rx_key) } pub fn from_secret(mut secret: [u8; 32], context: &[u8]) -> Self { - let tx_key = derive_key_blake3(CARRIER_KDF_INFO_TX, secret.as_ref(), &context); - let rx_key = derive_key_blake3(CARRIER_KDF_INFO_RX, secret.as_ref(), &context); + let tx_key = derive_key_blake3(CARRIER_KDF_INFO_TX, secret.as_ref(), context); + let rx_key = derive_key_blake3(CARRIER_KDF_INFO_RX, secret.as_ref(), context); secret.zeroize(); Self::init(tx_key, rx_key) } - fn flip_keys(self) -> Self { + pub(crate) fn flip_keys(self) -> Self { Self { tx_key: self.rx_key, rx_key: self.tx_key, @@ -293,158 +138,52 @@ impl Carrier { #[cfg(test)] mod tests { - use crate::{ - KKT_RESPONSE_AAD, - carrier::Carrier, - ciphersuite::EncapsulationKey, - context::{KKTMode, KKTRole}, - frame::KKTFrame, - key_utils::{ - generate_keypair_libcrux, generate_keypair_mceliece, generate_keypair_mlkem, - generate_keypair_x25519, hash_encapsulation_key, - }, - session::{ - initiator_ingest_response, initiator_process, responder_ingest_message, - responder_process, - }, - }; - use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, KEM}; + use crate::{carrier::Carrier, key_utils::generate_keypair_x25519}; + use rand09::RngCore; #[test] fn test_e2e() { let mut rng = rand09::rng(); // generate responder x25519 keys - let responder_x25519_keypair = generate_keypair_x25519(&mut rng); + let r_x25519 = generate_keypair_x25519(&mut rng); - for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { - for hash_function in [ - HashFunction::Blake3, - HashFunction::SHA256, - HashFunction::Shake128, - HashFunction::Shake256, - ] { - let ciphersuite = Ciphersuite::resolve_ciphersuite( - kem, - hash_function, - crate::ciphersuite::SignatureScheme::Ed25519, - None, - ) - .unwrap(); + let mut context: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut context); - // generate kem public keys + let ephemeral_keypair = generate_keypair_x25519(&mut rng); - let (responder_kem_public_key, initiator_kem_public_key) = match kem { - KEM::MlKem768 => ( - EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1), - EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1), - ), - KEM::XWing => ( - EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), - EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), - ), - KEM::X25519 => ( - EncapsulationKey::X25519( - generate_keypair_libcrux(&mut rng, kem).unwrap().1, - ), - EncapsulationKey::X25519( - generate_keypair_libcrux(&mut rng, kem).unwrap().1, - ), - ), - KEM::McEliece => ( - EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), - EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), - ), - }; + let i_shared_secret = ephemeral_keypair.sk().diffie_hellman(&r_x25519.pk).unwrap(); - let i_kem_key_bytes = initiator_kem_public_key.encode(); + let r_shared_secret = r_x25519.sk().diffie_hellman(&ephemeral_keypair.pk).unwrap(); - let r_kem_key_bytes = responder_kem_public_key.encode(); + let mut i_carrier = Carrier::from_secret_slice(i_shared_secret.as_ref(), &context); + let mut r_carrier = + Carrier::from_secret_slice(r_shared_secret.as_ref(), &context).flip_keys(); - let i_dir_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &i_kem_key_bytes, - ); + let test1 = b"test1: i>r #1"; + let ct1 = i_carrier.encrypt(test1).unwrap(); + let pt1 = r_carrier.decrypt(&ct1).unwrap(); + assert_eq!(pt1, test1); - let r_dir_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &r_kem_key_bytes, - ); + let test2 = b"test2: r>i #1"; + let ct2 = i_carrier.encrypt(test2).unwrap(); + let pt2 = r_carrier.decrypt(&ct2).unwrap(); + assert_eq!(pt2, test2); + let test3 = b"test3: i>r #2"; - // OneWay + let ct3 = i_carrier.encrypt(test3).unwrap(); + let pt3 = r_carrier.decrypt(&ct3).unwrap(); + assert_eq!(pt3, test3); - let (mut i_context, i_frame) = - initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap(); + let test4 = b"test4: i>r #3"; + let ct4 = i_carrier.encrypt(test4).unwrap(); + let pt4 = r_carrier.decrypt(&ct4).unwrap(); + assert_eq!(pt4, test4); - // encryption - initiator frame - let (mut i_carrier, i_bytes) = Carrier::new_kkt_initiator( - &mut rng, - &responder_x25519_keypair.pk, - 1u8, - &i_frame, - ) - .unwrap(); - - // decryption - initiator frame - - let (mut r_carrier, i_frame_r, i_context_r) = - Carrier::new_kkt_responder(&responder_x25519_keypair, &i_bytes, &[1]).unwrap(); - - let (mut r_context, _) = - responder_ingest_message(&i_context_r, None, &i_frame_r).unwrap(); - - let r_frame = responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap(); - - // encryption - responder frame - let r_bytes = r_carrier.encrypt(&r_frame.to_bytes()).unwrap(); - - // decryption - responder frame - - let (i_frame_r, i_context_r) = - KKTFrame::from_bytes(&i_carrier.decrypt(&r_bytes).unwrap()).unwrap(); - - let i_obtained_key = initiator_ingest_response( - &mut i_context, - &i_frame_r, - &i_context_r, - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(i_obtained_key.encode(), r_kem_key_bytes); - - let test1 = b"test1: i>r #1"; - let ct1 = i_carrier.encrypt(test1).unwrap(); - let pt1 = r_carrier.decrypt(&ct1).unwrap(); - assert_eq!(pt1, test1); - - let test2 = b"test2: r>i #1"; - let ct2 = i_carrier.encrypt(test2).unwrap(); - let pt2 = r_carrier.decrypt(&ct2).unwrap(); - assert_eq!(pt2, test2); - let test3 = b"test3: i>r #2"; - - let ct3 = i_carrier.encrypt(test3).unwrap(); - let pt3 = r_carrier.decrypt(&ct3).unwrap(); - assert_eq!(pt3, test3); - - let test4 = b"test4: i>r #3"; - let ct4 = i_carrier.encrypt(test4).unwrap(); - let pt4 = r_carrier.decrypt(&ct4).unwrap(); - assert_eq!(pt4, test4); - - let test5 = b"test5: r>i #2"; - let ct5 = i_carrier.encrypt(test5).unwrap(); - let pt5 = r_carrier.decrypt(&ct5).unwrap(); - assert_eq!(pt5, test5); - } - } + let test5 = b"test5: r>i #2"; + let ct5 = i_carrier.encrypt(test5).unwrap(); + let pt5 = r_carrier.decrypt(&ct5).unwrap(); + assert_eq!(pt5, test5); } } diff --git a/common/nym-kkt/src/ciphersuite.rs b/common/nym-kkt/src/ciphersuite.rs index fc19cdd50b..43e0286cc8 100644 --- a/common/nym-kkt/src/ciphersuite.rs +++ b/common/nym-kkt/src/ciphersuite.rs @@ -45,7 +45,7 @@ impl Debug for EncapsulationKey { } } impl EncapsulationKey { - pub(crate) fn decode(kem: KEM, bytes: &[u8]) -> Result { + pub(crate) fn decode(kem: &KEM, bytes: &[u8]) -> Result { match kem { KEM::McEliece => { if bytes.len() != mceliece::PUBLIC_KEY_LENGTH { @@ -110,7 +110,7 @@ impl Debug for DecapsulationKey { } } -pub const fn map_kem_to_libcrux_kem(kem: KEM) -> Result { +pub const fn map_kem_to_libcrux_kem(kem: &KEM) -> Result { match kem { KEM::MlKem768 => Ok(Algorithm::MlKem768), KEM::XWing => Ok(Algorithm::XWingKemDraft06), diff --git a/common/nym-kkt/src/context.rs b/common/nym-kkt/src/context.rs index 1f6e9af2a5..cbb38082a4 100644 --- a/common/nym-kkt/src/context.rs +++ b/common/nym-kkt/src/context.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::ciphersuite::CIPHERSUITE_ENCODING_LEN; -use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError, frame::KKT_SESSION_ID_LEN}; +use crate::{KKT_VERSION, ciphersuite::Ciphersuite, error::KKTError}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::fmt::Display; @@ -19,6 +19,7 @@ pub enum KKTStatus { UnsupportedKKTVersion = 0b1000_0000, InvalidKey = 0b1010_0000, Timeout = 0b1100_0000, + UnverifiedKEMKey = 0b1110_0000, } impl Display for KKTStatus { @@ -30,6 +31,7 @@ impl Display for KKTStatus { KKTStatus::UnsupportedCiphersuite => "Unsupported Ciphersuite", KKTStatus::UnsupportedKKTVersion => "Unsupported KKT Version", KKTStatus::InvalidKey => "Invalid Key", + KKTStatus::UnverifiedKEMKey => "Could not verify received encapsulation key", KKTStatus::Timeout => "Timeout", }) } @@ -61,14 +63,14 @@ pub struct KKTContext { ciphersuite: Ciphersuite, } impl KKTContext { - pub fn new(role: KKTRole, mode: KKTMode, ciphersuite: Ciphersuite) -> Self { + pub fn new(role: KKTRole, mode: KKTMode, ciphersuite: &Ciphersuite) -> Self { Self { version: KKT_VERSION, message_sequence: 0, status: KKTStatus::Ok, mode, role, - ciphersuite, + ciphersuite: *ciphersuite, } } @@ -99,8 +101,8 @@ impl KKTContext { pub fn status(&self) -> KKTStatus { self.status } - pub fn ciphersuite(&self) -> Ciphersuite { - self.ciphersuite + pub fn ciphersuite(&self) -> &Ciphersuite { + &self.ciphersuite } pub fn role(&self) -> KKTRole { self.role @@ -110,7 +112,7 @@ impl KKTContext { } pub fn body_len(&self) -> usize { - if self.status != KKTStatus::Ok + if (self.status != KKTStatus::Ok && self.status != KKTStatus::UnverifiedKEMKey) || // no payload (self.mode == KKTMode::OneWay && self.role == KKTRole::Initiator) @@ -125,20 +127,8 @@ impl KKTContext { KKT_CONTEXT_LEN } - pub const fn session_id_len(&self) -> usize { - // note: if anyone decides to update this function and changes the constant value, - // you will have to adjust encoding/decoding functions - - // match self.role { - // KKTRole::Initiator | KKTRole::Responder => SESSION_ID_LENGTH, - // It doesn't make sense to send a session_id if we send messages in the clear - // KKTRole::AnonymousInitiator => 0, - // } - KKT_SESSION_ID_LEN - } - pub fn full_message_len(&self) -> usize { - self.body_len() + self.header_len() + self.session_id_len() + self.body_len() + self.header_len() } pub fn encode(&self) -> Result<[u8; KKT_CONTEXT_LEN], KKTError> { @@ -214,7 +204,7 @@ mod tests { let valid_context = KKTContext::new( KKTRole::Initiator, KKTMode::Mutual, - Ciphersuite::decode([255, 1, 0, 0]).unwrap(), + &Ciphersuite::decode([255, 1, 0, 0]).unwrap(), ); let encoded = valid_context.encode().unwrap(); let decoded = KKTContext::try_decode(encoded).unwrap(); diff --git a/common/nym-kkt/src/error.rs b/common/nym-kkt/src/error.rs index db3886be5c..8ace2a9007 100644 --- a/common/nym-kkt/src/error.rs +++ b/common/nym-kkt/src/error.rs @@ -11,6 +11,9 @@ pub enum KKTError { #[error(transparent)] CiphersuiteDecodingError(#[from] KKTCiphersuiteError), + #[error(transparent)] + MaskedByteError(#[from] MaskedByteError), + #[error("KEM mapping failure: {}", info)] KEMMapping { info: &'static str }, diff --git a/common/nym-kkt/src/frame.rs b/common/nym-kkt/src/frame.rs index fc82f9f9c8..6e43178ef5 100644 --- a/common/nym-kkt/src/frame.rs +++ b/common/nym-kkt/src/frame.rs @@ -7,19 +7,22 @@ // [2..=5] => Ciphersuite // [6] => Reserved +use libcrux_psq::handshake::types::{DHKeyPair, DHPublicKey}; +use nym_kkt_ciphersuite::x25519::PUBLIC_KEY_LENGTH; +use rand09::{CryptoRng, RngCore}; + use crate::{ + carrier::Carrier, context::{KKT_CONTEXT_LEN, KKTContext}, error::KKTError, + masked_byte::{MASKED_BYTE_LEN, MaskedByte}, }; -pub const KKT_SESSION_ID_LEN: usize = 16; - -pub type KKTSessionId = [u8; KKT_SESSION_ID_LEN]; +const KKT_CARRIER_CONTEXT: &[u8] = b"CARRIER_V1_KKT_V1_KDF"; #[derive(Debug, PartialEq, Clone)] pub struct KKTFrame { context: [u8; KKT_CONTEXT_LEN], - session_id: KKTSessionId, body: Vec, } @@ -28,17 +31,96 @@ pub struct KKTFrame { // if coming from responder => body has the responder's kem public key. impl KKTFrame { - pub fn new( - context: [u8; KKT_CONTEXT_LEN], - body: &[u8], - session_id: [u8; KKT_SESSION_ID_LEN], - ) -> Self { - Self { - context, + pub fn new(context: &KKTContext, body: &[u8]) -> Result { + let context_bytes = context.encode()?; + Ok(Self { + context: context_bytes, body: Vec::from(body), - session_id, - } + }) } + + pub fn encrypt_initiator_frame( + &self, + rng: &mut R, + responder_public_key: &DHPublicKey, + version_byte: u8, + ) -> Result<(Carrier, Vec), KKTError> + where + R: CryptoRng + RngCore, + { + let ephemeral_keypair = DHKeyPair::new(rng); + let shared_secret = ephemeral_keypair + .sk() + .diffie_hellman(responder_public_key) + .map_err(|_| KKTError::X25519Error { + info: "Key Derivation Error", + })?; + + let mut mask = Vec::from(ephemeral_keypair.pk.as_ref()); + mask.extend_from_slice(responder_public_key.as_ref()); + + let masked_byte = MaskedByte::new(version_byte, &mask); + + let mut context = Vec::from(masked_byte.as_slice()); + context.extend_from_slice(KKT_CARRIER_CONTEXT); + context.extend_from_slice(ephemeral_keypair.pk.as_ref()); + context.extend_from_slice(responder_public_key.as_ref()); + + let mut carrier = Carrier::from_secret_slice(shared_secret.as_ref(), &context); + + let mut full_kkt_message = Vec::from(ephemeral_keypair.pk.as_ref()); + full_kkt_message.extend_from_slice(masked_byte.as_slice()); + let encrypted_kkt_frame = carrier.encrypt(&self.to_bytes())?; + full_kkt_message.extend_from_slice(&encrypted_kkt_frame); + + Ok((carrier, full_kkt_message)) + } + + pub fn decrypt_initiator_frame( + responder_keypair: &DHKeyPair, + message: &[u8], + supported_versions: &[u8], + ) -> Result<(Carrier, KKTFrame, KKTContext), KKTError> { + let mut initiator_public_key_bytes: [u8; PUBLIC_KEY_LENGTH] = [0; PUBLIC_KEY_LENGTH]; + initiator_public_key_bytes.clone_from_slice(&message[0..PUBLIC_KEY_LENGTH]); + + // check mask + + let masked_byte = + MaskedByte::try_from(&message[PUBLIC_KEY_LENGTH..PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN])?; + + let mut mask = Vec::from(&initiator_public_key_bytes); + mask.extend_from_slice(responder_keypair.pk.as_ref()); + + // this could be used later when we have multiple versions + // if this call fails, it does before the server has to run a DH + let _outer_protocol_version = + masked_byte.unmask_check_version(&mask, supported_versions)?; + + // now that the version is ok, we can try dh + + let initiator_public_key = DHPublicKey::from_bytes(&initiator_public_key_bytes); + + let shared_secret = responder_keypair + .sk() + .diffie_hellman(&initiator_public_key) + .map_err(|_| KKTError::X25519Error { + info: "Key Derivation Error", + })?; + + let mut context = Vec::from(masked_byte.as_slice()); + context.extend_from_slice(KKT_CARRIER_CONTEXT); + context.extend_from_slice(initiator_public_key.as_ref()); + context.extend_from_slice(responder_keypair.pk.as_ref()); + + let mut carrier = Carrier::from_secret_slice(shared_secret.as_ref(), &context).flip_keys(); + + let decrypted_message = carrier.decrypt(&message[PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN..])?; + let (frame, context) = KKTFrame::from_bytes(&decrypted_message)?; + + Ok((carrier, frame, context)) + } + pub fn context_ref(&self) -> &[u8] { &self.context } @@ -51,30 +133,18 @@ impl KKTFrame { &self.body } - pub fn session_id_ref(&self) -> &[u8] { - &self.session_id - } - pub fn session_id(&self) -> [u8; KKT_SESSION_ID_LEN] { - self.session_id - } - pub fn body_mut(&mut self) -> &mut [u8] { &mut self.body } - pub fn session_id_mut(&mut self) -> &mut [u8] { - &mut self.session_id - } - pub fn frame_length(&self) -> usize { - self.context.len() + self.session_id.len() + self.body.len() + self.context.len() + self.body.len() } pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::with_capacity(self.frame_length()); bytes.extend_from_slice(&self.context); bytes.extend_from_slice(&self.body); - bytes.extend_from_slice(&self.session_id); bytes } @@ -110,20 +180,7 @@ impl KKTFrame { body.extend_from_slice(body_bytes); } - let session_bytes = &bytes[KKT_CONTEXT_LEN + context.body_len() - ..KKT_CONTEXT_LEN + context.body_len() + KKT_SESSION_ID_LEN]; - // SAFETY: we're using exactly KKT_SESSION_ID_LEN bytes and we checked for sufficient bytes - #[allow(clippy::unwrap_used)] - let session_id = session_bytes.try_into().unwrap(); - - // // old code left for reference if session id becomes variable in length: - // if context.session_id_len() > 0 { - // session_id.extend_from_slice( - // &bytes[KKT_CONTEXT_LEN + context.body_len() - // ..KKT_CONTEXT_LEN + context.body_len() + context.session_id_len()], - // ); - // } - - Ok((KKTFrame::new(context_bytes, &body, session_id), context)) + let frame = KKTFrame::new(&context, &body)?; + Ok((frame, context)) } } diff --git a/common/nym-kkt/src/initiator.rs b/common/nym-kkt/src/initiator.rs new file mode 100644 index 0000000000..90741e7ab9 --- /dev/null +++ b/common/nym-kkt/src/initiator.rs @@ -0,0 +1,174 @@ +use libcrux_psq::handshake::types::DHPublicKey; +use nym_kkt_ciphersuite::Ciphersuite; +use rand09::{CryptoRng, RngCore}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use crate::{ + carrier::Carrier, + ciphersuite::EncapsulationKey, + context::{KKTContext, KKTMode, KKTRole, KKTStatus}, + error::KKTError, + frame::KKTFrame, + key_utils::validate_encapsulation_key, +}; + +pub struct KKTInitiator<'a> { + carrier: Carrier, + context: KKTContext, + expected_hash: &'a [u8], +} +impl<'a> Zeroize for KKTInitiator<'a> { + fn zeroize(&mut self) { + self.carrier.zeroize(); + } +} +impl<'a> ZeroizeOnDrop for KKTInitiator<'a> {} + +impl<'a> KKTInitiator<'a> { + // to be used by clients + pub fn generate_one_way_request( + rng: &mut R, + ciphersuite: &Ciphersuite, + responder_dh_public_key: &DHPublicKey, + expected_hash: &'a [u8], + outer_protocol_version: u8, + ) -> Result<(Self, Vec), KKTError> + where + R: CryptoRng + RngCore, + { + Self::generate_encrypted_request( + rng, + KKTMode::OneWay, + ciphersuite, + None, + responder_dh_public_key, + expected_hash, + outer_protocol_version, + ) + } + + // to be used by nodes + pub fn generate_mutual_request( + rng: &mut R, + ciphersuite: &Ciphersuite, + local_encapsulation_key: &EncapsulationKey, + responder_dh_public_key: &DHPublicKey, + expected_hash: &'a [u8], + outer_protocol_version: u8, + ) -> Result<(Self, Vec), KKTError> + where + R: CryptoRng + RngCore, + { + Self::generate_encrypted_request( + rng, + KKTMode::Mutual, + ciphersuite, + Some(local_encapsulation_key), + responder_dh_public_key, + expected_hash, + outer_protocol_version, + ) + } + + fn generate_encrypted_request( + rng: &mut R, + mode: KKTMode, + ciphersuite: &Ciphersuite, + local_encapsulation_key: Option<&EncapsulationKey>, + responder_dh_public_key: &DHPublicKey, + expected_hash: &'a [u8], + outer_protocol_version: u8, + ) -> Result<(Self, Vec), KKTError> + where + R: CryptoRng + RngCore, + { + let (context, frame) = initiator_process(mode, ciphersuite, local_encapsulation_key)?; + let (carrier, message_bytes) = + frame.encrypt_initiator_frame(rng, responder_dh_public_key, outer_protocol_version)?; + + Ok(( + Self { + carrier, + context, + expected_hash, + }, + message_bytes, + )) + } + + // bool would be true if the initiator was using mutual mode + // and the responder was able to verify the initiator's kem key + pub fn process_response( + &mut self, + response_bytes: &[u8], + ) -> Result<(EncapsulationKey, bool), KKTError> { + let decrypted_response_bytes = self.carrier.decrypt(response_bytes)?; + let (response_frame, remote_context) = KKTFrame::from_bytes(&decrypted_response_bytes)?; + initiator_ingest_response( + &mut self.context, + &response_frame, + &remote_context, + self.expected_hash, + ) + } +} + +pub fn initiator_process( + mode: KKTMode, + ciphersuite: &Ciphersuite, + own_encapsulation_key: Option<&EncapsulationKey>, +) -> Result<(KKTContext, KKTFrame), KKTError> { + let context = KKTContext::new(KKTRole::Initiator, mode, ciphersuite); + + let body: &[u8] = match mode { + KKTMode::OneWay => &[], + KKTMode::Mutual => match own_encapsulation_key { + Some(encaps_key) => &encaps_key.encode(), + + // Missing key + None => { + return Err(KKTError::FunctionInputError { + info: "KEM Key Not Provided", + }); + } + }, + }; + + let frame = KKTFrame::new(&context, body)?; + + Ok((context, frame)) +} + +pub fn initiator_ingest_response( + own_context: &mut KKTContext, + remote_frame: &KKTFrame, + remote_context: &KKTContext, + expected_hash: &[u8], +) -> Result<(EncapsulationKey, bool), KKTError> { + match remote_context.status() { + KKTStatus::Ok | KKTStatus::UnverifiedKEMKey => { + let received_encapsulation_key = + EncapsulationKey::decode(own_context.ciphersuite().kem(), remote_frame.body_ref())?; + + match validate_encapsulation_key( + own_context.ciphersuite().hash_function(), + own_context.ciphersuite().hash_len(), + remote_frame.body_ref(), + expected_hash, + ) { + true => Ok(( + received_encapsulation_key, + remote_context.status() != KKTStatus::UnverifiedKEMKey, + )), + + // The key does not match the hash obtained from the directory + false => Err(KKTError::KEMError { + info: "Hash of received encapsulation key does not match the value stored on the directory.", + }), + } + } + _ => Err(KKTError::ResponderFlaggedError { + status: remote_context.status(), + }), + } +} diff --git a/common/nym-kkt/src/lib.rs b/common/nym-kkt/src/lib.rs index 80f87d5c17..5af73fc508 100644 --- a/common/nym-kkt/src/lib.rs +++ b/common/nym-kkt/src/lib.rs @@ -6,183 +6,30 @@ pub mod ciphersuite; pub mod context; pub mod error; pub mod frame; +pub mod initiator; pub mod key_utils; pub mod masked_byte; pub mod rekey; -pub mod session; +pub mod responder; // This must be less than 4 bits pub const KKT_VERSION: u8 = 1; const _: () = assert!(KKT_VERSION < 1 << 4); -pub const KKT_RESPONSE_AAD: &[u8] = b"KKT_Response"; -pub(crate) const KKT_INITIAL_FRAME_AAD: &[u8] = b"KKT_INITIAL_FRAME"; #[cfg(test)] mod test { + use nym_kkt_ciphersuite::SignatureScheme; + use crate::{ - KKT_RESPONSE_AAD, - carrier::Carrier, ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM}, - context::KKTMode, - frame::KKTFrame, + initiator::KKTInitiator, key_utils::{ - generate_keypair_libcrux, generate_keypair_mceliece, generate_keypair_mlkem, - generate_keypair_x25519, hash_encapsulation_key, - }, - session::{ - initiator_ingest_response, initiator_process, responder_ingest_message, - responder_process, + generate_keypair_mceliece, generate_keypair_mlkem, generate_keypair_x25519, + hash_encapsulation_key, }, + responder::KKTResponder, }; - #[test] - fn test_kkt_psq_e2e() { - let mut rng = rand09::rng(); - - for encryption in [false, true] { - for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { - for hash_function in [ - HashFunction::Blake3, - HashFunction::SHA256, - HashFunction::Shake128, - HashFunction::Shake256, - ] { - let ciphersuite = Ciphersuite::resolve_ciphersuite( - kem, - hash_function, - crate::ciphersuite::SignatureScheme::Ed25519, - None, - ) - .unwrap(); - - // generate kem public keys - - let (responder_kem_public_key, initiator_kem_public_key) = match kem { - KEM::MlKem768 => ( - EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1), - EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1), - ), - KEM::XWing => ( - EncapsulationKey::XWing( - generate_keypair_libcrux(&mut rng, kem).unwrap().1, - ), - EncapsulationKey::XWing( - generate_keypair_libcrux(&mut rng, kem).unwrap().1, - ), - ), - KEM::X25519 => ( - EncapsulationKey::X25519( - generate_keypair_libcrux(&mut rng, kem).unwrap().1, - ), - EncapsulationKey::X25519( - generate_keypair_libcrux(&mut rng, kem).unwrap().1, - ), - ), - KEM::McEliece => ( - EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), - EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), - ), - }; - - let i_kem_key_bytes = initiator_kem_public_key.encode(); - - let r_kem_key_bytes = responder_kem_public_key.encode(); - - let i_dir_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &i_kem_key_bytes, - ); - - let r_dir_hash = hash_encapsulation_key( - &ciphersuite.hash_function(), - ciphersuite.hash_len(), - &r_kem_key_bytes, - ); - - // OneWay - { - let (mut i_context, i_frame) = - initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None) - .unwrap(); - - let i_frame_bytes = i_frame.to_bytes(); - - let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); - - let (mut r_context, r_obtained_key) = - responder_ingest_message(&r_context, None, &i_frame_r).unwrap(); - - assert!(r_obtained_key.is_none()); - - let r_frame = responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap(); - - let r_bytes = r_frame.to_bytes(); - - let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap(); - - let i_obtained_key = initiator_ingest_response( - &mut i_context, - &i_frame_r, - &i_context_r, - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) - } - - // Mutual - { - let (mut i_context, i_frame) = initiator_process( - &mut rng, - KKTMode::Mutual, - ciphersuite, - Some(&initiator_kem_public_key), - ) - .unwrap(); - - let i_frame_bytes = i_frame.to_bytes(); - - let (i_frame_r, r_context) = KKTFrame::from_bytes(&i_frame_bytes).unwrap(); - - let (mut r_context, r_obtained_key) = - responder_ingest_message(&r_context, Some(&i_dir_hash), &i_frame_r) - .unwrap(); - - assert_eq!(r_obtained_key.unwrap().encode(), i_kem_key_bytes); - - let r_frame = responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap(); - - let r_bytes = r_frame.to_bytes(); - - let (i_frame_r, i_context_r) = KKTFrame::from_bytes(&r_bytes).unwrap(); - - let i_obtained_key = initiator_ingest_response( - &mut i_context, - &i_frame_r, - &i_context_r, - &r_dir_hash, - ) - .unwrap(); - - assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) - } - } - } - } - } - #[test] fn test_kkt_psq_e2e_encrypted_carrier() { let mut rng = rand09::rng(); @@ -190,7 +37,7 @@ mod test { // generate responder x25519 keys let responder_x25519_keypair = generate_keypair_x25519(&mut rng); - for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] { + for kem in [KEM::MlKem768, KEM::McEliece] { for hash_function in [ HashFunction::Blake3, HashFunction::SHA256, @@ -212,29 +59,29 @@ mod test { EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1), EncapsulationKey::MlKem768(generate_keypair_mlkem(&mut rng).1), ), - KEM::XWing => ( - EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), - EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), - ), - KEM::X25519 => ( - EncapsulationKey::X25519( - generate_keypair_libcrux(&mut rng, kem).unwrap().1, - ), - EncapsulationKey::X25519( - generate_keypair_libcrux(&mut rng, kem).unwrap().1, - ), - ), KEM::McEliece => ( EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), EncapsulationKey::McEliece(generate_keypair_mceliece(&mut rng).1), ), + _ => unreachable!(), // KEM::XWing => ( + // EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), + // EncapsulationKey::XWing(generate_keypair_libcrux(&mut rng, kem).unwrap().1), + // ), + // KEM::X25519 => ( + // EncapsulationKey::X25519( + // generate_keypair_libcrux(&mut rng, kem).unwrap().1, + // ), + // EncapsulationKey::X25519( + // generate_keypair_libcrux(&mut rng, kem).unwrap().1, + // ), + // ), }; let i_kem_key_bytes = initiator_kem_public_key.encode(); let r_kem_key_bytes = responder_kem_public_key.encode(); - let i_dir_hash = hash_encapsulation_key( + let _i_dir_hash = hash_encapsulation_key( &ciphersuite.hash_function(), ciphersuite.hash_len(), &i_kem_key_bytes, @@ -248,106 +95,106 @@ mod test { // OneWay { - let (mut i_context, i_frame) = - initiator_process(&mut rng, KKTMode::OneWay, ciphersuite, None).unwrap(); - - // encryption - initiator frame - let (mut i_carrier, i_bytes) = Carrier::new_kkt_initiator( + let (mut initiator, request_bytes) = KKTInitiator::generate_one_way_request( &mut rng, + &ciphersuite, &responder_x25519_keypair.pk, - 1u8, - &i_frame, - ) - .unwrap(); - - // decryption - initiator frame - - let (mut r_carrier, i_frame_r, i_context_r) = - Carrier::new_kkt_responder(&responder_x25519_keypair, &i_bytes, &[1]) - .unwrap(); - - let (mut r_context, _) = - responder_ingest_message(&i_context_r, None, &i_frame_r).unwrap(); - - let r_frame = responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap(); - - // encryption - responder frame - let r_bytes = r_carrier.encrypt(&r_frame.to_bytes()).unwrap(); - - // decryption - responder frame - - let (i_frame_r, i_context_r) = - KKTFrame::from_bytes(&i_carrier.decrypt(&r_bytes).unwrap()).unwrap(); - - let i_obtained_key = initiator_ingest_response( - &mut i_context, - &i_frame_r, - &i_context_r, &r_dir_hash, + 1u8, ) .unwrap(); + let responder = if kem == KEM::McEliece { + KKTResponder::new( + &responder_x25519_keypair, + None, + Some(&responder_kem_public_key), + &[ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::Shake128, + HashFunction::Shake256, + ], + &[1], + &[SignatureScheme::Ed25519], + ) + .unwrap() + } else if kem == KEM::MlKem768 { + KKTResponder::new( + &responder_x25519_keypair, + Some(&responder_kem_public_key), + None, + &[ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::Shake128, + HashFunction::Shake256, + ], + &[1], + &[SignatureScheme::Ed25519], + ) + .unwrap() + } else { + unreachable!(); + }; + + let (response_bytes, _) = responder.process_request(&request_bytes).unwrap(); + + let (i_obtained_key, _) = initiator.process_response(&response_bytes).unwrap(); assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) } // Mutual { - let (mut i_context, i_frame) = initiator_process( - &mut rng, - KKTMode::Mutual, - ciphersuite, - Some(&initiator_kem_public_key), - ) - .unwrap(); - - // encryption - initiator frame - let (mut i_carrier, i_bytes) = Carrier::new_kkt_initiator( + let (mut initiator, request_bytes) = KKTInitiator::generate_mutual_request( &mut rng, + &ciphersuite, + &initiator_kem_public_key, &responder_x25519_keypair.pk, - 1u8, - &i_frame, - ) - .unwrap(); - - // decryption - initiator frame - - let (mut r_carrier, i_frame_r, i_context_r) = - Carrier::new_kkt_responder(&responder_x25519_keypair, &i_bytes, &[1]) - .unwrap(); - - let (mut r_context, _) = responder_ingest_message( - &i_context_r, - Some(i_dir_hash.as_slice()), - &i_frame_r, - ) - .unwrap(); - - let r_frame = responder_process( - &mut r_context, - i_frame_r.session_id(), - &responder_kem_public_key, - ) - .unwrap(); - - // encryption - responder frame - let r_bytes = r_carrier.encrypt(&r_frame.to_bytes()).unwrap(); - - // decryption - responder frame - - let (i_frame_r, i_context_r) = - KKTFrame::from_bytes(&i_carrier.decrypt(&r_bytes).unwrap()).unwrap(); - - let i_obtained_key = initiator_ingest_response( - &mut i_context, - &i_frame_r, - &i_context_r, &r_dir_hash, + 1u8, ) .unwrap(); + let responder = if kem == KEM::McEliece { + KKTResponder::new( + &responder_x25519_keypair, + None, + Some(&responder_kem_public_key), + &[ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::Shake128, + HashFunction::Shake256, + ], + &[1], + &[SignatureScheme::Ed25519], + ) + .unwrap() + } else if kem == KEM::MlKem768 { + KKTResponder::new( + &responder_x25519_keypair, + Some(&responder_kem_public_key), + None, + &[ + HashFunction::Blake3, + HashFunction::SHA256, + HashFunction::Shake128, + HashFunction::Shake256, + ], + &[1], + &[SignatureScheme::Ed25519], + ) + .unwrap() + } else { + unreachable!(); + }; + + let (response_bytes, r_obtained_key) = + responder.process_request(&request_bytes).unwrap(); + + // if we keep unverified keys, this should change + assert!(r_obtained_key.is_none()); + + let (i_obtained_key, _) = initiator.process_response(&response_bytes).unwrap(); assert_eq!(i_obtained_key.encode(), r_kem_key_bytes) } diff --git a/common/nym-kkt/src/masked_byte.rs b/common/nym-kkt/src/masked_byte.rs index 24211b077b..b991c3da7e 100644 --- a/common/nym-kkt/src/masked_byte.rs +++ b/common/nym-kkt/src/masked_byte.rs @@ -8,6 +8,22 @@ use crate::error::{ pub const MASKED_BYTE_LEN: usize = 16; pub const MASKED_BYTE_CONTEXT_STR: &[u8] = b"NYM_MASKED_BYTE_V1"; +const U8_RANGE: [u8; 256] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, + 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, + 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, + 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, + 250, 251, 252, 253, 254, 255, +]; + #[derive(Clone, Copy)] pub struct MaskedByte([u8; MASKED_BYTE_LEN]); @@ -31,24 +47,34 @@ impl MaskedByte { /// If we find an `i` which yields back the hash input, then we found the masked byte. /// Otherwise, the function returns an error. pub fn unmask(&self, mask: &[u8]) -> Result { + self.unmask_check_version(mask, &U8_RANGE) + } + + // This is could be more efficient than unmask, + // because we just could check against a smaller list of supported versions. + pub fn unmask_check_version( + &self, + mask: &[u8], + supported_versions: &[u8], + ) -> Result { let mut buf: [u8; MASKED_BYTE_LEN] = [0u8; MASKED_BYTE_LEN]; let mut hasher = blake3::Hasher::new(); hasher.update(MASKED_BYTE_CONTEXT_STR); hasher.update(mask); // avoid zero update hasher.update(&[0xFF]); - for i in 0..=u8::MAX { + for i in supported_versions { let mut t_hasher = hasher.clone(); - t_hasher.update(&[i]); + t_hasher.update(&[*i]); t_hasher.finalize_xof_into(&mut buf); if buf == self.0 { - return Ok(i); + return Ok(*i); } } - return Err(Failure); + Err(Failure) } - pub fn as_slice<'a>(&'a self) -> &'a [u8] { + pub fn as_slice(&self) -> &[u8] { &self.0 } diff --git a/common/nym-kkt/src/responder.rs b/common/nym-kkt/src/responder.rs new file mode 100644 index 0000000000..93f8587a10 --- /dev/null +++ b/common/nym-kkt/src/responder.rs @@ -0,0 +1,261 @@ +use std::collections::HashSet; + +use crate::key_utils::validate_encapsulation_key; +use crate::{ + ciphersuite::EncapsulationKey, + context::{KKTContext, KKTMode, KKTRole, KKTStatus}, + error::KKTError, + frame::KKTFrame, +}; +use libcrux_psq::handshake::types::DHKeyPair; +use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, KEM, SignatureScheme}; +pub struct KKTResponder<'a> { + x25519_keypair: &'a DHKeyPair, + mlkem_encapsulation_key: Option<&'a EncapsulationKey>, + mceliece_encapsulation_key: Option<&'a EncapsulationKey>, + supported_hash_functions: HashSet, + supported_signature_schemes: HashSet, + supported_outer_protocol_versions: HashSet, +} +impl<'a> KKTResponder<'a> { + pub fn new( + x25519_keypair: &'a DHKeyPair, + mlkem_encapsulation_key: Option<&'a EncapsulationKey>, + mceliece_encapsulation_key: Option<&'a EncapsulationKey>, + supported_hash_functions: &[HashFunction], + supported_outer_protocol_versions: &[u8], + supported_signature_schemes: &[SignatureScheme], + ) -> Result { + let hash_functions: HashSet = + supported_hash_functions.iter().copied().collect(); + + if hash_functions.is_empty() { + Err(KKTError::FunctionInputError { + info: "Did not provide a supported HashFunction when instaciating a KKTResponder", + }) + } else { + let signature_schemes: HashSet = + supported_signature_schemes.iter().copied().collect(); + + if signature_schemes.is_empty() { + Err(KKTError::FunctionInputError { + info: "Did not provide a supported SignatureScheme when instaciating a KKTResponder", + }) + } else { + let outer_protocol_versions: HashSet = + supported_outer_protocol_versions.iter().copied().collect(); + + if outer_protocol_versions.is_empty() { + Err(KKTError::FunctionInputError { + info: "Did not provide a supported outer protocol version when instaciating a KKTResponder", + }) + } else { + match (mlkem_encapsulation_key, mceliece_encapsulation_key) { + (Some(mlkem_key), Some(mceliece_key)) => match (mlkem_key, mceliece_key) { + (EncapsulationKey::MlKem768(_), EncapsulationKey::McEliece(_)) => { + Ok(Self { + x25519_keypair, + mlkem_encapsulation_key, + mceliece_encapsulation_key, + supported_hash_functions: hash_functions, + supported_signature_schemes: signature_schemes, + supported_outer_protocol_versions: outer_protocol_versions, + }) + } + (EncapsulationKey::MlKem768(_), _) => { + Err(KKTError::FunctionInputError { + info: "Provided a non-MlKem768 encapsulation key as the MlKem768 key.", + }) + } + (EncapsulationKey::McEliece(_), _) => { + Err(KKTError::FunctionInputError { + info: "Provided a non-McEliece encapsulation key as the McEliece key.", + }) + } + _ => Err(KKTError::FunctionInputError { + info: "Provided incompatible encapsulation keys.", + }), + }, + (Some(mlkem_key), None) => match mlkem_key { + EncapsulationKey::MlKem768(_) => Ok(Self { + x25519_keypair, + mlkem_encapsulation_key, + mceliece_encapsulation_key: None, + supported_hash_functions: hash_functions, + supported_signature_schemes: signature_schemes, + supported_outer_protocol_versions: outer_protocol_versions, + }), + _ => Err(KKTError::FunctionInputError { + info: "Provided a non-MlKem768 encapsulation key as the MlKem768 key.", + }), + }, + (None, Some(mceliece_key)) => match mceliece_key { + EncapsulationKey::McEliece(_) => Ok(Self { + x25519_keypair, + mlkem_encapsulation_key: None, + mceliece_encapsulation_key, + supported_hash_functions: hash_functions, + supported_signature_schemes: signature_schemes, + supported_outer_protocol_versions: outer_protocol_versions, + }), + _ => Err(KKTError::FunctionInputError { + info: "Provided a non-McEliece encapsulation key as the McEliece key.", + }), + }, + (None, None) => Err(KKTError::FunctionInputError { + info: "Did not provide an encapsulation key when instanciating a KKTResponder.", + }), + } + } + } + } + } + fn supported_protocol_versions(&self) -> Vec { + self.supported_outer_protocol_versions + .iter() + .copied() + .collect() + } + + fn check_ciphersuite_compatiblity( + &self, + remote_ciphersuite: &Ciphersuite, + ) -> Result<(), KKTError> { + if !self + .supported_hash_functions + .contains(remote_ciphersuite.hash_function()) + { + Err(KKTError::IncompatibilityError { + info: "Unsupported HashFunction", + }) + } else { + if !self + .supported_signature_schemes + .contains(remote_ciphersuite.signature_scheme()) + { + Err(KKTError::IncompatibilityError { + info: "Unsupported SignatureScheme", + }) + } else { + if match remote_ciphersuite.kem() { + KEM::MlKem768 => self.mlkem_encapsulation_key.is_some(), + KEM::McEliece => self.mceliece_encapsulation_key.is_some(), + _ => false, + } { + Ok(()) + } else { + Err(KKTError::IncompatibilityError { + info: "Unsupported KEM", + }) + } + } + } + } + + // When this function fails, we do that silently (i.e. we dont generate a response to the initiator). + + pub fn process_request( + &self, + request_bytes: &[u8], + ) -> Result<(Vec, Option), KKTError> { + let (mut carrier, remote_frame, remote_context) = KKTFrame::decrypt_initiator_frame( + self.x25519_keypair, + request_bytes, + &self.supported_protocol_versions(), + )?; + + self.check_ciphersuite_compatiblity(remote_context.ciphersuite())?; + + let (local_context, remote_encapsulation_key) = match remote_context.mode() { + KKTMode::OneWay => responder_ingest_message(&remote_context, None, &remote_frame)?, + KKTMode::Mutual => { + // So we can either fetch the remote hash here using some async call to the directory, + // which might make registration hang or accept the sent key then verify later. + + // If we choose to not accept, the response's status will be KKTStatus::UnverifiedKEMKey. + // The response would still contain the responder's encapsulation key. + responder_ingest_message(&remote_context, None, &remote_frame)? + } + }; + + let frame = if local_context.ciphersuite().kem() == &KEM::MlKem768 { + KKTFrame::new( + &local_context, + // SAFETY: the self.check_ciphersuite_compatibility call above guarantees that we will have a key in the right place + #[allow(clippy::unwrap_used)] + &self.mlkem_encapsulation_key.unwrap().encode(), + )? + } else { + KKTFrame::new( + &local_context, + // SAFETY: the self.check_ciphersuite_compatibility call above guarantees that we will have a key in the right place + #[allow(clippy::unwrap_used)] + &self.mceliece_encapsulation_key.unwrap().encode(), + )? + }; + + // encryption - responder frame + let response_bytes = carrier.encrypt(&frame.to_bytes())?; + Ok((response_bytes, remote_encapsulation_key)) + } +} + +pub fn responder_ingest_message( + remote_context: &KKTContext, + expected_hash: Option<&[u8]>, + remote_frame: &KKTFrame, +) -> Result<(KKTContext, Option), KKTError> { + let mut own_context = remote_context.derive_responder_header()?; + + match remote_context.role() { + KKTRole::Initiator => { + // using own_context here because maybe for whatever reason we want to ignore the remote kem key + match own_context.mode() { + KKTMode::OneWay => Ok((own_context, None)), + KKTMode::Mutual => { + match expected_hash { + Some(expected_hash) => { + let received_encapsulation_key = EncapsulationKey::decode( + own_context.ciphersuite().kem(), + remote_frame.body_ref(), + )?; + if validate_encapsulation_key( + own_context.ciphersuite().hash_function(), + own_context.ciphersuite().hash_len(), + remote_frame.body_ref(), + expected_hash, + ) { + Ok((own_context, Some(received_encapsulation_key))) + } + // The key does not match the hash obtained from the directory + else { + Err(KKTError::KEMError { + info: "Hash of received encapsulation key does not match the value stored on the directory.", + }) + } + } + None => { + own_context.update_status(KKTStatus::UnverifiedKEMKey); + // we don't store an unverified key + // changing the status notifies the initiator that we didn't + + // we could still keep it here and then verify later... + // let received_encapsulation_key = EncapsulationKey::decode( + // own_context.ciphersuite().kem(), + // remote_frame.body_ref(), + // )?; + // Ok((own_context, Some(received_encapsulation_key))) + // + + Ok((own_context, None)) + } + } + } + } + } + + KKTRole::Responder => Err(KKTError::IncompatibilityError { + info: "Responder received a request from another responder.", + }), + } +} diff --git a/common/nym-kkt/src/session.rs b/common/nym-kkt/src/session.rs deleted file mode 100644 index ed0e7dd29f..0000000000 --- a/common/nym-kkt/src/session.rs +++ /dev/null @@ -1,144 +0,0 @@ -use rand09::{CryptoRng, RngCore}; - -use crate::frame::KKTSessionId; -use crate::{ - ciphersuite::{Ciphersuite, EncapsulationKey}, - context::{KKTContext, KKTMode, KKTRole, KKTStatus}, - error::KKTError, - frame::{KKT_SESSION_ID_LEN, KKTFrame}, - key_utils::validate_encapsulation_key, -}; - -pub fn initiator_process( - rng: &mut R, - mode: KKTMode, - ciphersuite: Ciphersuite, - own_encapsulation_key: Option<&EncapsulationKey>, -) -> Result<(KKTContext, KKTFrame), KKTError> -where - R: CryptoRng + RngCore, -{ - let context = KKTContext::new(KKTRole::Initiator, mode, ciphersuite); - - let context_bytes = context.encode()?; - - let mut session_id = [0; KKT_SESSION_ID_LEN]; - // Generate Session ID - rng.fill_bytes(&mut session_id); - - let body: &[u8] = match mode { - KKTMode::OneWay => &[], - KKTMode::Mutual => match own_encapsulation_key { - Some(encaps_key) => &encaps_key.encode(), - - // Missing key - None => { - return Err(KKTError::FunctionInputError { - info: "KEM Key Not Provided", - }); - } - }, - }; - - Ok((context, KKTFrame::new(context_bytes, body, session_id))) -} - -pub fn initiator_ingest_response( - own_context: &mut KKTContext, - remote_frame: &KKTFrame, - remote_context: &KKTContext, - expected_hash: &[u8], -) -> Result { - check_compatibility(own_context, remote_context)?; - match remote_context.status() { - KKTStatus::Ok => { - let received_encapsulation_key = - EncapsulationKey::decode(own_context.ciphersuite().kem(), remote_frame.body_ref())?; - - match validate_encapsulation_key( - &own_context.ciphersuite().hash_function(), - own_context.ciphersuite().hash_len(), - remote_frame.body_ref(), - expected_hash, - ) { - true => Ok(received_encapsulation_key), - - // The key does not match the hash obtained from the directory - false => Err(KKTError::KEMError { - info: "Hash of received encapsulation key does not match the value stored on the directory.", - }), - } - } - _ => Err(KKTError::ResponderFlaggedError { - status: remote_context.status(), - }), - } -} - -// todo: figure out how to handle errors using status codes - -pub fn responder_ingest_message( - remote_context: &KKTContext, - expected_hash: Option<&[u8]>, - remote_frame: &KKTFrame, -) -> Result<(KKTContext, Option), KKTError> { - let own_context = remote_context.derive_responder_header()?; - - match remote_context.role() { - KKTRole::Initiator => { - // using own_context here because maybe for whatever reason we want to ignore the remote kem key - match own_context.mode() { - KKTMode::OneWay => Ok((own_context, None)), - KKTMode::Mutual => { - match expected_hash { - Some(expected_hash) => { - let received_encapsulation_key = EncapsulationKey::decode( - own_context.ciphersuite().kem(), - remote_frame.body_ref(), - )?; - if validate_encapsulation_key( - &own_context.ciphersuite().hash_function(), - own_context.ciphersuite().hash_len(), - remote_frame.body_ref(), - expected_hash, - ) { - Ok((own_context, Some(received_encapsulation_key))) - } - // The key does not match the hash obtained from the directory - else { - Err(KKTError::KEMError { - info: "Hash of received encapsulation key does not match the value stored on the directory.", - }) - } - } - None => Err(KKTError::FunctionInputError { - info: "Expected hash of the remote encapsulation key is not provided.", - }), - } - } - } - } - - KKTRole::Responder => Err(KKTError::IncompatibilityError { - info: "Responder received a request from another responder.", - }), - } -} - -pub fn responder_process( - own_context: &mut KKTContext, - session_id: KKTSessionId, - encapsulation_key: &EncapsulationKey, -) -> Result { - let body = encapsulation_key.encode(); - let context_bytes = own_context.encode()?; - Ok(KKTFrame::new(context_bytes, &body, session_id)) -} - -fn check_compatibility( - _own_context: &KKTContext, - _remote_context: &KKTContext, -) -> Result<(), KKTError> { - // todo: check ciphersuite/context compatibility - Ok(()) -}