Adding concrete types within KKT exchange

This commit is contained in:
Jędrzej Stuczyński
2026-02-16 09:40:14 +00:00
parent 5621c94c0a
commit 5536d49f65
8 changed files with 387 additions and 259 deletions
+14
View File
@@ -55,6 +55,20 @@ pub enum KKTError {
#[error("Generic libcrux error")]
LibcruxError,
#[error("failed to derive shared secret: {inner:?}")]
SharedSecretDerivationFailure {
inner: libcrux_psq::handshake::HandshakeError,
},
#[error("the received encapsulation key hash does not match the expected value")]
MismatchedKEMHash,
}
impl KKTError {
pub fn shared_secret_derivation_failure(inner: libcrux_psq::handshake::HandshakeError) -> Self {
KKTError::SharedSecretDerivationFailure { inner }
}
}
#[derive(Error, Debug)]
+50 -82
View File
@@ -7,22 +7,23 @@
// [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::message::{DecryptedRequestFrame, KKTRequest, KKTRequestPlaintext};
use crate::{
carrier::Carrier,
context::{KKT_CONTEXT_LEN, KKTContext},
error::KKTError,
masked_byte::{MASKED_BYTE_LEN, MaskedByte},
};
use libcrux_psq::handshake::types::{DHKeyPair, DHPrivateKey, DHPublicKey};
use nym_kkt_ciphersuite::x25519;
use nym_kkt_ciphersuite::x25519::PUBLIC_KEY_LENGTH;
use rand09::{CryptoRng, RngCore};
const KKT_CARRIER_CONTEXT: &[u8] = b"CARRIER_V1_KKT_V1_KDF";
pub(crate) const KKT_CARRIER_CONTEXT: &[u8] = b"CARRIER_V1_KKT_V1_KDF";
#[derive(Debug, PartialEq, Clone)]
pub struct KKTFrame {
context: [u8; KKT_CONTEXT_LEN],
context: KKTContext,
body: Vec<u8>,
}
@@ -31,124 +32,92 @@ pub struct KKTFrame {
// if coming from responder => body has the responder's kem public key.
impl KKTFrame {
pub fn new(context: &KKTContext, body: &[u8]) -> Result<Self, KKTError> {
let context_bytes = context.encode()?;
Ok(Self {
context: context_bytes,
pub fn new(context: KKTContext, body: &[u8]) -> Self {
Self {
context,
body: Vec::from(body),
})
}
}
pub fn context(&self) -> &KKTContext {
&self.context
}
pub fn encrypt_initiator_frame<R>(
&self,
self,
rng: &mut R,
responder_public_key: &DHPublicKey,
version_byte: u8,
) -> Result<(Carrier, Vec<u8>), KKTError>
) -> Result<(Carrier, KKTRequest), 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 plaintext =
KKTRequestPlaintext::new(ephemeral_keypair.pk, responder_public_key, version_byte);
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);
let mut carrier =
plaintext.derive_initiator_carrier(ephemeral_keypair.sk(), responder_public_key)?;
let full_kkt_message = plaintext.into_message(&mut carrier, self)?;
Ok((carrier, full_kkt_message))
}
pub fn decrypt_initiator_frame(
responder_keypair: &DHKeyPair,
message: &[u8],
message: KKTRequest,
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]);
) -> Result<DecryptedRequestFrame, KKTError> {
let mask = message.plaintext.version_mask(&responder_keypair.pk);
// 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)?;
let outer_protocol_version = message
.plaintext
.masked_version_bytes
.unmask_check_version(&mask, supported_versions)?;
// now that the version is ok, we can try dh
// after verifying the version, we can perform the DH and continue processing the request
let mut carrier = message
.plaintext
.derive_responder_carrier(responder_keypair)?;
let initiator_public_key = DHPublicKey::from_bytes(&initiator_public_key_bytes);
let decrypted_message = carrier.decrypt(&message.encrypted_frame)?;
let frame = KKTFrame::from_bytes(&decrypted_message)?;
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
}
pub fn context(&self) -> Result<KKTContext, KKTError> {
KKTContext::try_decode(self.context)
Ok(DecryptedRequestFrame {
carrier,
remote_frame: frame,
outer_protocol_version,
})
}
pub fn body_ref(&self) -> &[u8] {
&self.body
}
pub fn body(self) -> Vec<u8> {
self.body
}
pub fn body_mut(&mut self) -> &mut [u8] {
&mut self.body
}
pub fn frame_length(&self) -> usize {
self.context.len() + self.body.len()
KKT_CONTEXT_LEN + self.body.len()
}
pub fn to_bytes(&self) -> Vec<u8> {
pub fn try_to_bytes(&self) -> Result<Vec<u8>, KKTError> {
let mut bytes = Vec::with_capacity(self.frame_length());
bytes.extend_from_slice(&self.context);
bytes.extend_from_slice(&self.context.encode()?);
bytes.extend_from_slice(&self.body);
bytes
Ok(bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Result<(Self, KKTContext), KKTError> {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, KKTError> {
let len = bytes.len();
if bytes.len() < KKT_CONTEXT_LEN {
return Err(KKTError::FrameDecodingError {
@@ -180,7 +149,6 @@ impl KKTFrame {
body.extend_from_slice(body_bytes);
}
let frame = KKTFrame::new(&context, &body)?;
Ok((frame, context))
Ok(KKTFrame::new(context, &body))
}
}
+44 -57
View File
@@ -1,9 +1,13 @@
// Copyright 2025-2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use libcrux_psq::handshake::types::DHPublicKey;
use nym_kkt_ciphersuite::{Ciphersuite, KEM};
use nym_kkt_ciphersuite::Ciphersuite;
use rand09::{CryptoRng, RngCore};
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::keys::EncapsulationKey;
use crate::message::{KKTRequest, KKTResponse, ProcessedKKTResponse};
use crate::{
carrier::Carrier,
context::{KKTContext, KKTMode, KKTRole, KKTStatus},
@@ -12,25 +16,16 @@ use crate::{
key_utils::validate_encapsulation_key,
};
pub struct KKTResponse {
/// The obtained encapsulation key of the remote
pub encapsulation_key: EncapsulationKey,
/// Indicates whether responder was able to verify the initiator's kem key,
pub verified_initiator_kem_key: bool,
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct KKTInitiator<'a> {
carrier: Carrier,
#[zeroize(skip)]
context: KKTContext,
#[zeroize(skip)]
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
@@ -40,7 +35,7 @@ impl<'a> KKTInitiator<'a> {
responder_dh_public_key: &DHPublicKey,
expected_hash: &'a [u8],
outer_protocol_version: u8,
) -> Result<(Self, Vec<u8>), KKTError>
) -> Result<(Self, KKTRequest), KKTError>
where
R: CryptoRng + RngCore,
{
@@ -63,7 +58,7 @@ impl<'a> KKTInitiator<'a> {
responder_dh_public_key: &DHPublicKey,
expected_hash: &'a [u8],
outer_protocol_version: u8,
) -> Result<(Self, Vec<u8>), KKTError>
) -> Result<(Self, KKTRequest), KKTError>
where
R: CryptoRng + RngCore,
{
@@ -86,11 +81,12 @@ impl<'a> KKTInitiator<'a> {
responder_dh_public_key: &DHPublicKey,
expected_hash: &'a [u8],
outer_protocol_version: u8,
) -> Result<(Self, Vec<u8>), KKTError>
) -> Result<(Self, KKTRequest), KKTError>
where
R: CryptoRng + RngCore,
{
let (context, frame) = initiator_process(mode, ciphersuite, local_encapsulation_key)?;
let frame = initiator_process(mode, ciphersuite, local_encapsulation_key)?;
let context = *frame.context();
let (carrier, message_bytes) =
frame.encrypt_initiator_frame(rng, responder_dh_public_key, outer_protocol_version)?;
@@ -104,25 +100,13 @@ impl<'a> KKTInitiator<'a> {
))
}
// 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<KKTResponse, KKTError> {
let decrypted_response_bytes = self.carrier.decrypt(response_bytes)?;
let (response_frame, remote_context) = KKTFrame::from_bytes(&decrypted_response_bytes)?;
let (kem_bytes, verified_initiator_kem_key) = initiator_ingest_response(
&mut self.context,
&response_frame,
&remote_context,
self.expected_hash,
)?;
let encapsulation_key =
EncapsulationKey::try_from_bytes(kem_bytes, self.context.ciphersuite().kem())?;
Ok(KKTResponse {
encapsulation_key,
verified_initiator_kem_key,
})
pub fn process_response(
&mut self,
response: KKTResponse,
) -> Result<ProcessedKKTResponse, KKTError> {
let decrypted_response_bytes = self.carrier.decrypt(&response.encrypted_frame)?;
let response_frame = KKTFrame::from_bytes(&decrypted_response_bytes)?;
initiator_ingest_response(&mut self.context, &response_frame, self.expected_hash)
}
}
@@ -130,7 +114,7 @@ pub fn initiator_process(
mode: KKTMode,
ciphersuite: Ciphersuite,
own_encapsulation_key: Option<&[u8]>,
) -> Result<(KKTContext, KKTFrame), KKTError> {
) -> Result<KKTFrame, KKTError> {
let context = KKTContext::new(KKTRole::Initiator, mode, ciphersuite);
let body: &[u8] = match mode {
@@ -147,18 +131,16 @@ pub fn initiator_process(
},
};
let frame = KKTFrame::new(&context, body)?;
Ok((context, frame))
Ok(KKTFrame::new(context, body))
}
pub fn initiator_ingest_response(
own_context: &mut KKTContext,
own_context: &KKTContext,
remote_frame: &KKTFrame,
remote_context: &KKTContext,
expected_hash: &[u8],
) -> Result<(Vec<u8>, bool), KKTError> {
match remote_context.status() {
) -> Result<ProcessedKKTResponse, KKTError> {
let remote_context = remote_frame.context();
let verified_initiator_kem_key = match remote_context.status() {
KKTStatus::Ok | KKTStatus::UnverifiedKEMKey => {
match validate_encapsulation_key(
own_context.ciphersuite().hash_function(),
@@ -166,19 +148,24 @@ pub fn initiator_ingest_response(
remote_frame.body_ref(),
expected_hash,
) {
true => Ok((
remote_frame.body_ref().to_vec(),
remote_context.status() != KKTStatus::UnverifiedKEMKey,
)),
true => 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.",
}),
false => return Err(KKTError::MismatchedKEMHash),
}
}
_ => Err(KKTError::ResponderFlaggedError {
status: remote_context.status(),
}),
}
_ => {
return Err(KKTError::ResponderFlaggedError {
status: remote_context.status(),
});
}
};
let kem = own_context.ciphersuite().kem();
let kem_bytes = remote_frame.body_ref();
let encapsulation_key = EncapsulationKey::try_from_bytes(kem_bytes.to_vec(), kem)?;
Ok(ProcessedKKTResponse {
encapsulation_key,
verified_initiator_kem_key,
})
}
+1
View File
@@ -9,6 +9,7 @@ pub mod initiator;
pub mod key_utils;
pub mod keys;
pub mod masked_byte;
pub mod message;
pub mod rekey;
pub mod responder;
+10 -11
View File
@@ -78,7 +78,7 @@ impl MaskedByte {
&self.0
}
pub fn to_bytes(self) -> [u8; 16] {
pub fn to_bytes(self) -> [u8; MASKED_BYTE_LEN] {
self.0
}
}
@@ -91,7 +91,7 @@ impl From<[u8; MASKED_BYTE_LEN]> for MaskedByte {
impl From<&[u8; MASKED_BYTE_LEN]> for MaskedByte {
fn from(value: &[u8; MASKED_BYTE_LEN]) -> Self {
MaskedByte(value.to_owned())
MaskedByte(*value)
}
}
@@ -99,14 +99,13 @@ impl TryFrom<&[u8]> for MaskedByte {
type Error = MaskedByteError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != MASKED_BYTE_LEN {
Err(InvalidLength {
let Ok(inner) = value.try_into() else {
return Err(InvalidLength {
expected: MASKED_BYTE_LEN,
actual: value.len(),
})
} else {
Ok(Self::from(value.as_chunks::<MASKED_BYTE_LEN>().0[0]))
}
});
};
Ok(MaskedByte(inner))
}
}
@@ -171,19 +170,19 @@ mod test {
// add more one more byte
wire_bytes_messy.push(0x42);
assert!(wire_bytes_messy.len() == MASKED_BYTE_LEN + 1);
assert_eq!(wire_bytes_messy.len(), MASKED_BYTE_LEN + 1);
// should fail
assert!(MaskedByte::try_from(wire_bytes_messy.as_slice()).is_err());
// pop the added byte
_ = wire_bytes_messy.pop();
assert!(wire_bytes_messy.len() == MASKED_BYTE_LEN);
assert_eq!(wire_bytes_messy.len(), MASKED_BYTE_LEN);
// should succeed
assert!(MaskedByte::try_from(wire_bytes_messy.as_slice()).is_ok());
// pop one more byte
_ = wire_bytes_messy.pop();
assert!(wire_bytes_messy.len() == MASKED_BYTE_LEN - 1);
assert_eq!(wire_bytes_messy.len(), MASKED_BYTE_LEN - 1);
// should fail
assert!(MaskedByte::try_from(wire_bytes_messy.as_slice()).is_err());
}
+168
View File
@@ -0,0 +1,168 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::carrier::Carrier;
use crate::context::KKTContext;
use crate::error::KKTError;
use crate::frame::KKTFrame;
use crate::keys::EncapsulationKey;
use crate::masked_byte::{MASKED_BYTE_LEN, MaskedByte};
use libcrux_psq::handshake::types::{DHKeyPair, DHPrivateKey, DHPublicKey};
use nym_kkt_ciphersuite::x25519;
pub struct KKTRequest {
/// The plaintext part of the request
pub(crate) plaintext: KKTRequestPlaintext,
/// Ciphertext of an initial request `KKTFrame`
pub(crate) encrypted_frame: Vec<u8>,
}
impl KKTRequest {
pub(crate) fn into_bytes(mut self) -> Vec<u8> {
let mut out = self.plaintext.to_bytes();
out.append(&mut self.encrypted_frame);
out
}
}
pub(crate) struct KKTRequestPlaintext {
/// Ephemeral Diffie-Hellman public key of the initiator
pub(crate) dh_pubkey: DHPublicKey,
/// Masked bytes representing the outer protocol version information
pub(crate) masked_version_bytes: MaskedByte,
}
impl KKTRequestPlaintext {
pub(crate) fn new(
initiator_pubkey: DHPublicKey,
responder_pubkey: &DHPublicKey,
outer_protocol_version: u8,
) -> Self {
let mask = Self::create_version_mask(&initiator_pubkey, responder_pubkey);
let masked_version_bytes = MaskedByte::new(outer_protocol_version, &mask);
KKTRequestPlaintext {
dh_pubkey: initiator_pubkey,
masked_version_bytes,
}
}
pub(crate) fn into_message(
self,
carrier: &mut Carrier,
frame: KKTFrame,
) -> Result<KKTRequest, KKTError> {
let frame_bytes = frame.try_to_bytes()?;
let frame_ciphertext = carrier.encrypt(&frame_bytes)?;
Ok(KKTRequest {
plaintext: self,
encrypted_frame: frame_ciphertext,
})
}
pub(crate) fn create_version_mask(
initiator_pubkey: &DHPublicKey,
responder_pubkey: &DHPublicKey,
) -> Vec<u8> {
let mut mask = Vec::with_capacity(2 * x25519::PUBLIC_KEY_LENGTH);
mask.extend_from_slice(&initiator_pubkey.as_ref());
mask.extend_from_slice(&responder_pubkey.as_ref());
mask
}
fn create_carrier_ctx(
masked_version: &MaskedByte,
initiator_pubkey: &DHPublicKey,
responder_pubkey: &DHPublicKey,
) -> Vec<u8> {
let mut context = Vec::new();
context.extend_from_slice(masked_version.as_slice());
context.extend_from_slice(crate::frame::KKT_CARRIER_CONTEXT);
context.extend_from_slice(initiator_pubkey.as_ref());
context.extend_from_slice(responder_pubkey.as_ref());
context
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(x25519::PUBLIC_KEY_LENGTH + MASKED_BYTE_LEN);
out.extend_from_slice(self.dh_pubkey.as_ref());
out.extend_from_slice(&self.masked_version_bytes.as_slice());
out
}
pub(crate) fn version_mask(&self, responder_pubkey: &DHPublicKey) -> Vec<u8> {
Self::create_version_mask(&self.dh_pubkey, responder_pubkey)
}
pub(crate) fn derive_initiator_carrier(
&self,
initiator_sk: &DHPrivateKey,
responder_pubkey: &DHPublicKey,
) -> Result<Carrier, KKTError> {
let ctx = Self::create_carrier_ctx(
&self.masked_version_bytes,
&self.dh_pubkey,
responder_pubkey,
);
let shared_secret = initiator_sk
.diffie_hellman(responder_pubkey)
.map_err(KKTError::shared_secret_derivation_failure)?;
Ok(Carrier::from_secret_slice(shared_secret.as_ref(), &ctx))
}
pub(crate) fn derive_responder_carrier(
&self,
responder_keys: &DHKeyPair,
) -> Result<Carrier, KKTError> {
let ctx = Self::create_carrier_ctx(
&self.masked_version_bytes,
&self.dh_pubkey,
&responder_keys.pk,
);
let shared_secret = responder_keys
.sk()
.diffie_hellman(&self.dh_pubkey)
.map_err(KKTError::shared_secret_derivation_failure)?;
Ok(Carrier::from_secret_slice(shared_secret.as_ref(), &ctx).flip_keys())
}
}
pub(crate) struct DecryptedRequestFrame {
/// Derived carrier used for decrypting this frame and encrypting the response
pub(crate) carrier: Carrier,
/// The remote frame sent in the message
pub(crate) remote_frame: KKTFrame,
/// The unmasked byte representing the outer protocol version sent by the initiator
pub(crate) outer_protocol_version: u8,
}
impl DecryptedRequestFrame {
pub(crate) fn remote_context(&self) -> &KKTContext {
self.remote_frame.context()
}
}
pub struct ProcessedKKTRequest {
pub response: KKTResponse,
/// The obtained encapsulation key of the remote
pub remote_encapsulation_key: Option<EncapsulationKey>,
}
pub struct KKTResponse {
/// Encrypted KKT frame that is going to be sent back to the initiator
pub encrypted_frame: Vec<u8>,
}
pub struct ProcessedKKTResponse {
/// The obtained encapsulation key of the remote
pub encapsulation_key: EncapsulationKey,
/// Indicates whether responder was able to verify the initiator's kem key,
pub verified_initiator_kem_key: bool,
}
+85 -92
View File
@@ -1,21 +1,33 @@
use std::collections::HashSet;
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::key_utils::validate_encapsulation_key;
use crate::keys::KEMKeys;
use crate::keys::{EncapsulationKey, KEMKeys};
use crate::message::{KKTRequest, KKTResponse, ProcessedKKTRequest};
use crate::{
context::{KKTContext, KKTMode, KKTRole, KKTStatus},
error::KKTError,
frame::KKTFrame,
};
use libcrux_psq::handshake::types::DHKeyPair;
use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, KEM, SignatureScheme};
use nym_kkt_ciphersuite::{Ciphersuite, HashFunction, SignatureScheme};
/// Representation of a KKT Responder
pub struct KKTResponder<'a> {
/// Long-term x25519 DH key pair of this Responder
x25519_keypair: &'a DHKeyPair,
/// KEM keys of this responder
kem_keys: &'a KEMKeys,
supported_hash_functions: HashSet<HashFunction>,
supported_signature_schemes: HashSet<SignatureScheme>,
supported_outer_protocol_versions: HashSet<u8>,
/// List of supported Hash Functions by this Responder
supported_hash_functions: Vec<HashFunction>,
/// List of supported Signature Schemes by this Responder
supported_signature_schemes: Vec<SignatureScheme>,
/// List of supported outer (LP) protocol version by this Responder
supported_outer_protocol_versions: Vec<u8>,
}
impl<'a> KKTResponder<'a> {
@@ -23,68 +35,50 @@ impl<'a> KKTResponder<'a> {
x25519_keypair: &'a DHKeyPair,
kem_keys: &'a KEMKeys,
supported_hash_functions: &[HashFunction],
supported_outer_protocol_versions: &[u8],
supported_signature_schemes: &[SignatureScheme],
supported_outer_protocol_versions: &[u8],
) -> Result<Self, KKTError> {
let hash_functions: HashSet<HashFunction> =
supported_hash_functions.iter().copied().collect();
if hash_functions.is_empty() {
if supported_hash_functions.is_empty() {
return Err(KKTError::FunctionInputError {
info: "Did not provide a supported HashFunction when instaciating a KKTResponder",
info: "Did not provide a supported HashFunction when instantiating a KKTResponder",
});
}
let signature_schemes: HashSet<SignatureScheme> =
supported_signature_schemes.iter().copied().collect();
if signature_schemes.is_empty() {
if supported_signature_schemes.is_empty() {
return Err(KKTError::FunctionInputError {
info: "Did not provide a supported SignatureScheme when instaciating a KKTResponder",
info: "Did not provide a supported SignatureScheme when instantiating a KKTResponder",
});
}
let outer_protocol_versions: HashSet<u8> =
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 {
Ok(Self {
x25519_keypair,
kem_keys,
supported_hash_functions: hash_functions,
supported_signature_schemes: signature_schemes,
supported_outer_protocol_versions: outer_protocol_versions,
})
if supported_outer_protocol_versions.is_empty() {
return Err(KKTError::FunctionInputError {
info: "Did not provide a supported outer protocol version when instantiating a KKTResponder",
});
}
}
fn supported_protocol_versions(&self) -> Vec<u8> {
self.supported_outer_protocol_versions
.iter()
.copied()
.collect()
Ok(Self {
x25519_keypair,
kem_keys,
supported_hash_functions: supported_hash_functions.to_vec(),
supported_signature_schemes: supported_signature_schemes.to_vec(),
supported_outer_protocol_versions: supported_outer_protocol_versions.to_vec(),
})
}
fn check_ciphersuite_compatiblity(
&self,
remote_ciphersuite: Ciphersuite,
) -> Result<(), KKTError> {
if !self
.supported_hash_functions
.contains(&remote_ciphersuite.hash_function())
{
let r_hash = remote_ciphersuite.hash_function();
let r_sig = remote_ciphersuite.signature_scheme();
if !self.supported_hash_functions.contains(&r_hash) {
return Err(KKTError::IncompatibilityError {
info: "Unsupported HashFunction",
});
}
if !self
.supported_signature_schemes
.contains(&remote_ciphersuite.signature_scheme())
{
if !self.supported_signature_schemes.contains(&r_sig) {
return Err(KKTError::IncompatibilityError {
info: "Unsupported SignatureScheme",
});
@@ -93,29 +87,30 @@ impl<'a> KKTResponder<'a> {
Ok(())
}
// When this function fails, we do that silently (i.e. we dont generate a response to the initiator).
// When this function fails, we do that silently (i.e. we don't generate a response to the initiator).
pub fn process_request(
&self,
request_bytes: &[u8],
) -> Result<(Vec<u8>, Option<Vec<u8>>), KKTError> {
let (mut carrier, remote_frame, remote_context) = KKTFrame::decrypt_initiator_frame(
pub fn process_request(&self, request: KKTRequest) -> Result<ProcessedKKTRequest, KKTError> {
let processed_req = KKTFrame::decrypt_initiator_frame(
self.x25519_keypair,
request_bytes,
&self.supported_protocol_versions(),
request,
&self.supported_outer_protocol_versions,
)?;
let remote_context = *processed_req.remote_context();
let remote_frame = processed_req.remote_frame;
let mut carrier = processed_req.carrier;
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::OneWay => responder_ingest_message(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)?
responder_ingest_message(None, remote_frame)?
}
};
@@ -125,20 +120,24 @@ impl<'a> KKTResponder<'a> {
info: "Unsupported KEM",
});
};
let frame = KKTFrame::new(&local_context, kem_key)?;
let frame = KKTFrame::new(local_context, kem_key);
// encryption - responder frame
let response_bytes = carrier.encrypt(&frame.to_bytes())?;
Ok((response_bytes, remote_encapsulation_key))
let encrypted_frame = carrier.encrypt(&frame.try_to_bytes()?)?;
Ok(ProcessedKKTRequest {
response: KKTResponse { encrypted_frame },
remote_encapsulation_key,
})
}
}
pub fn responder_ingest_message(
remote_context: &KKTContext,
expected_hash: Option<&[u8]>,
remote_frame: &KKTFrame,
) -> Result<(KKTContext, Option<Vec<u8>>), KKTError> {
remote_frame: KKTFrame,
) -> Result<(KKTContext, Option<EncapsulationKey>), KKTError> {
let remote_context = remote_frame.context();
let mut own_context = remote_context.derive_responder_header()?;
let cs = own_context.ciphersuite();
match remote_context.role() {
KKTRole::Initiator => {
@@ -146,39 +145,33 @@ pub fn responder_ingest_message(
match own_context.mode() {
KKTMode::OneWay => Ok((own_context, None)),
KKTMode::Mutual => {
match expected_hash {
Some(expected_hash) => {
if validate_encapsulation_key(
own_context.ciphersuite().hash_function(),
own_context.ciphersuite().hash_len(),
remote_frame.body_ref(),
expected_hash,
) {
Ok((own_context, Some(remote_frame.body_ref().to_vec())))
}
// 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
let Some(expected_hash) = expected_hash else {
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)))
//
// 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)))
//
return Ok((own_context, None));
};
Ok((own_context, None))
}
if !validate_encapsulation_key(
cs.hash_function(),
cs.hash_len(),
remote_frame.body_ref(),
expected_hash,
) {
// The key does not match the hash obtained from the directory
return Err(KKTError::MismatchedKEMHash);
}
let remote_key =
EncapsulationKey::try_from_bytes(remote_frame.body(), cs.kem())?;
Ok((own_context, Some(remote_key)))
}
}
}
+15 -17
View File
@@ -494,39 +494,37 @@ mod tests {
fn e2e_test_plain() {
let mut rng = deterministic_rng_09();
// SETUP START:
let kem = KEM::MlKem768;
let protocol_version = 1;
let (mut init, resp) = mock_peers();
init.ciphersuite = Ciphersuite::default().with_kem(kem);
let resp_remote = resp.as_remote();
let dir_hash = resp_remote.expected_kem_key_hash(init.ciphersuite).unwrap();
let resp_keys = resp.kem_keypairs.as_ref().unwrap();
// generate responder x25519 keys
let responder_x25519_keypair = resp.x25519();
let hash_function = HashFunction::Blake3;
// generate kem public keys
let supported_sigs = [SignatureScheme::Ed25519];
let supported_hash = [
HashFunction::Blake3,
HashFunction::Shake256,
HashFunction::Shake128,
HashFunction::SHA256,
];
let kkt_responder = KKTResponder::new(
&responder_x25519_keypair,
&resp_keys,
&[
HashFunction::Blake3,
HashFunction::SHA256,
HashFunction::Shake128,
HashFunction::Shake256,
],
&supported_hash,
&supported_sigs,
&[protocol_version],
&[SignatureScheme::Ed25519],
)
.unwrap();
// OneWay - MlKem
let psq_ciphersuite = CiphersuiteName::X25519_MLKEM768_X25519_AESGCM128_HKDFSHA256;
// SETUP END
let (mut initiator, request_bytes) = KKTInitiator::generate_one_way_request(
// OneWay - MlKem
let (mut initiator, request) = KKTInitiator::generate_one_way_request(
&mut rng,
init.ciphersuite,
&responder_x25519_keypair.pk,
@@ -535,9 +533,9 @@ mod tests {
)
.unwrap();
let (response_bytes, _) = kkt_responder.process_request(&request_bytes).unwrap();
let processed_req = kkt_responder.process_request(request).unwrap();
let response = initiator.process_response(&response_bytes).unwrap();
let response = initiator.process_response(processed_req.response).unwrap();
let encapsulation_key = response.encapsulation_key;
let mut msg_channel = vec![0u8; 8192];