upgrade kkt

This commit is contained in:
Georgio Nicolas
2026-02-13 05:00:22 +01:00
parent 6489ed5dd5
commit 65df76efea
10 changed files with 728 additions and 775 deletions
+42 -303
View File
@@ -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<R>(
// rng: &mut R,
// responder_public_key: &DHPublicKey,
// payload: &[u8],
// header: Option<&[u8]>,
// ) -> Result<(Self, Vec<u8>), 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<R>(
rng: &mut R,
responder_public_key: &DHPublicKey,
version_byte: u8,
kkt_frame: &KKTFrame,
) -> Result<(Self, Vec<u8>), 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);
}
}
+2 -2
View File
@@ -45,7 +45,7 @@ impl Debug for EncapsulationKey {
}
}
impl EncapsulationKey {
pub(crate) fn decode(kem: KEM, bytes: &[u8]) -> Result<Self, KKTError> {
pub(crate) fn decode(kem: &KEM, bytes: &[u8]) -> Result<Self, KKTError> {
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<Algorithm, KKTError> {
pub const fn map_kem_to_libcrux_kem(kem: &KEM) -> Result<Algorithm, KKTError> {
match kem {
KEM::MlKem768 => Ok(Algorithm::MlKem768),
KEM::XWing => Ok(Algorithm::XWingKemDraft06),
+10 -20
View File
@@ -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();
+3
View File
@@ -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 },
+98 -41
View File
@@ -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<u8>,
}
@@ -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<Self, KKTError> {
let context_bytes = context.encode()?;
Ok(Self {
context: context_bytes,
body: Vec::from(body),
session_id,
}
})
}
pub fn encrypt_initiator_frame<R>(
&self,
rng: &mut R,
responder_public_key: &DHPublicKey,
version_byte: u8,
) -> Result<(Carrier, Vec<u8>), 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<u8> {
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))
}
}
+174
View File
@@ -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<R>(
rng: &mut R,
ciphersuite: &Ciphersuite,
responder_dh_public_key: &DHPublicKey,
expected_hash: &'a [u8],
outer_protocol_version: u8,
) -> Result<(Self, Vec<u8>), 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<R>(
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<u8>), 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<R>(
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<u8>), 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(),
}),
}
}
+107 -260
View File
@@ -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)
}
+31 -5
View File
@@ -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<u8, MaskedByteError> {
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<u8, MaskedByteError> {
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
}
+261
View File
@@ -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<HashFunction>,
supported_signature_schemes: HashSet<SignatureScheme>,
supported_outer_protocol_versions: HashSet<u8>,
}
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<Self, KKTError> {
let hash_functions: HashSet<HashFunction> =
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<SignatureScheme> =
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<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 {
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<u8> {
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<u8>, Option<EncapsulationKey>), 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<EncapsulationKey>), 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.",
}),
}
}
-144
View File
@@ -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<R>(
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<EncapsulationKey, KKTError> {
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<EncapsulationKey>), 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<KKTFrame, KKTError> {
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(())
}