try to enable kkt encryption

This commit is contained in:
Georgio Nicolas
2025-12-10 13:14:12 +01:00
parent 4317ad3031
commit 77bcad5455
11 changed files with 925 additions and 423 deletions
+2
View File
@@ -22,6 +22,7 @@ tokio-util = { workspace = true, features = ["codec"] }
# internal
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"]}
nym-sphinx = { path = "../nymsphinx" }
libcrux-traits = { git = "https://github.com/cryspen/libcrux" }
libcrux-kem = { git = "https://github.com/cryspen/libcrux" }
@@ -29,6 +30,7 @@ libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = ["test-ut
libcrux-sha3 = { git = "https://github.com/cryspen/libcrux" }
libcrux-ml-kem = { git = "https://github.com/cryspen/libcrux" }
libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", features = ["codec"]}
libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" }
rand = "0.9.2"
curve25519-dalek = {version = "4.1.3", features = ["rand_core", "serde"] }
+4 -4
View File
@@ -8,7 +8,7 @@ use nym_crypto::asymmetric::ed25519;
use crate::error::KKTError;
pub const HASH_LEN_256: u8 = 32;
pub const HASH_LEN_256: usize = 32;
pub const CIPHERSUITE_ENCODING_LEN: usize = 4;
pub const CURVE25519_KEY_LEN: usize = 32;
@@ -172,7 +172,7 @@ impl Ciphersuite {
l
}
}
None => HASH_LEN_256,
None => HASH_LEN_256 as u8,
};
Ok(Self {
hash_function,
@@ -218,8 +218,8 @@ impl Ciphersuite {
HashFunction::SHAKE128 => 2,
HashFunction::SHA256 => 3,
},
match self.hash_length {
HASH_LEN_256 => 0,
match self.hash_length as usize {
HASH_LEN_256 => 0u8,
_ => self.hash_length,
},
match self.signature_scheme {
+201 -74
View File
@@ -1,95 +1,222 @@
use core::hash;
use blake3::Hasher;
use blake3::{Hash, Hasher};
use curve25519_dalek::digest::DynDigest;
use libcrux_psq::traits::Ciphertext;
use nym_crypto::symmetric::aead::{AeadKey, Nonce};
use nym_crypto::{
aes::Aes256,
asymmetric::x25519::{self, PrivateKey, PublicKey},
generic_array::GenericArray,
Aes256GcmSiv,
};
// use rand::{CryptoRng, RngCore};
use libcrux_chacha20poly1305::{NONCE_LEN, TAG_LEN};
use nym_sphinx::{PrivateKey, PublicKey};
use rand::{CryptoRng, RngCore};
use zeroize::Zeroize;
use nym_crypto::aes::cipher::crypto_common::rand_core::{CryptoRng, RngCore};
use crate::{
ciphersuite::{CURVE25519_KEY_LEN, HASH_LEN_256},
context::KKTContext,
error::KKTError,
frame::KKTFrame,
};
use crate::error::KKTError;
#[derive(Clone, Copy, Zeroize)]
pub struct KKTSessionSecret([u8; 32]);
fn generate_round_trip_symmetric_key<R>(
rng: &mut R,
remote_public_key: &PublicKey,
) -> ([u8; 64], [u8; 32])
where
R: CryptoRng + RngCore,
{
let mut s = x25519::PrivateKey::new(rng);
let gs = s.public_key();
impl KKTSessionSecret {
pub fn new(remote_public_key: &PublicKey) -> (Self, PublicKey) {
// this doesn't use the newer rand crate
let ephemeral_private_key = PrivateKey::random();
let ephemeral_public_key = PublicKey::from(&ephemeral_private_key);
let mut gbs = s.diffie_hellman(remote_public_key);
s.zeroize();
(
Self::derive(&ephemeral_private_key, &remote_public_key),
ephemeral_public_key,
)
}
pub fn from_bytes(secret: [u8; 32]) -> Self {
Self(secret)
}
pub fn try_derive(private_key: &PrivateKey, public_key: &[u8]) -> Result<Self, KKTError> {
let mut pub_key: [u8; 32] = [0u8; 32];
pub_key.copy_from_slice(&public_key[0..CURVE25519_KEY_LEN]);
let mut message: [u8; 64] = [0u8; 64];
message[0..32].clone_from_slice(gs.as_bytes());
// Todo: check validity of pk...
let pk = PublicKey::from(pub_key);
Ok(Self::derive(private_key, &pk))
}
let mut hasher = Hasher::new();
pub fn derive(private_key: &PrivateKey, public_key: &PublicKey) -> Self {
let mut shared_secret = private_key.diffie_hellman(&public_key);
hasher.update(&gbs);
gbs.zeroize();
let key: [u8; 32] = hasher.finalize().as_bytes().to_owned();
let mut hasher = Hasher::new();
hasher.update(remote_public_key.as_bytes());
hasher.update(gs.as_bytes());
hasher.update(shared_secret.as_bytes());
shared_secret.zeroize();
hasher.finalize_into_reset(&mut message[32..64]);
(message, key)
}
fn extract_shared_secret(b: &PrivateKey, message: &[u8; 64]) -> Result<[u8; 32], KKTError> {
let gs = PublicKey::from_bytes(&message[0..32])?;
let mut gsb = b.diffie_hellman(&gs);
let mut hasher = Hasher::new();
hasher.update(&gsb);
gsb.zeroize();
let key: [u8; 32] = hasher.finalize().as_bytes().to_owned();
hasher.update(b.public_key().as_bytes());
hasher.update(gs.as_bytes());
// This runs in constant time
if hasher.finalize() == message[32..64] {
Ok(key)
} else {
Err(KKTError::X25519Error {
info: format!("Symmetric Key Hash Validation Error"),
})
Self(hasher.finalize().as_bytes().to_owned())
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
fn encrypt(mut key: [u8; 32], message: &[u8]) -> Result<Vec<u8>, KKTError> {
// The empty nonce is fine since we use the key once.
let nonce = Nonce::<Aes256GcmSiv>::from_slice(&[]);
pub fn encrypt_initial_kkt_frame<R>(
rng: &mut R,
remote_public_key: &PublicKey,
kkt_frame: &KKTFrame,
) -> Result<(KKTSessionSecret, Vec<u8>), KKTError>
where
R: CryptoRng + RngCore,
{
let (session_secret_key, ephemeral_public_key) = KKTSessionSecret::new(remote_public_key);
let ciphertext =
nym_crypto::symmetric::aead::encrypt::<Aes256GcmSiv>(&key.into(), nonce, message)?;
let mut encrypted_frame =
encrypt_kkt_frame(rng, &session_secret_key, &kkt_frame, b"KKT_INITIAL_FRAME")?;
key.zeroize();
let mut output_buffer = Vec::with_capacity(encrypted_frame.len() + CURVE25519_KEY_LEN);
output_buffer.extend_from_slice(ephemeral_public_key.as_bytes());
output_buffer.append(&mut encrypted_frame);
Ok(ciphertext)
// [ 32 | 12 | ciphertext | 16];
// [eph_pub_key | nonce | ciphertext | tag];
Ok((session_secret_key, output_buffer))
}
fn decrypt(key: [u8; 32], ciphertext: Vec<u8>) -> Vec<u8> {
// The empty nonce is fine since we use the key once.
let nonce = Nonce::<Aes256>::from_slice(&[]);
pub fn decrypt_initial_kkt_frame(
responder_private_key: &PrivateKey,
encrypted_frame_bytes: &[u8],
) -> Result<(KKTSessionSecret, KKTFrame, KKTContext), KKTError> {
if encrypted_frame_bytes.len() < CURVE25519_KEY_LEN + TAG_LEN + NONCE_LEN {
return Err(KKTError::AEADError {
info: "Encrypted KKT Frame is too short.",
});
} else {
let shared_secret = KKTSessionSecret::try_derive(
responder_private_key,
&encrypted_frame_bytes[0..CURVE25519_KEY_LEN],
)?;
let ciphertext =
nym_crypto::symmetric::aead::encrypt::<Aes256GcmSiv>(&key.into(), nonce, message)?;
key.zeroize();
Ok(ciphertext)
let (kkt_frame, kkt_context) = decrypt_kkt_frame(
&shared_secret,
&encrypted_frame_bytes[CURVE25519_KEY_LEN..],
b"KKT_INITIAL_FRAME",
)?;
Ok((shared_secret, kkt_frame, kkt_context))
}
}
pub fn encrypt_kkt_frame<R>(
rng: &mut R,
secret_key: &KKTSessionSecret,
kkt_frame: &KKTFrame,
aad: &[u8],
) -> Result<Vec<u8>, KKTError>
where
R: CryptoRng + RngCore,
{
let kkt_frame_bytes = kkt_frame.to_bytes();
// generate nonce
let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
let mut ciphertext = encrypt(&secret_key.as_bytes(), &kkt_frame_bytes, &aad, &nonce)?;
// [ 12 | ciphertext | 16];
// [nonce | ciphertext | tag];
let mut output_buffer: Vec<u8> =
Vec::with_capacity(NONCE_LEN + kkt_frame_bytes.len() + TAG_LEN);
output_buffer.extend_from_slice(&nonce);
output_buffer.append(&mut ciphertext);
Ok(output_buffer)
}
// kkt_frame_bytes should look like this
// [ 12 | ciphertext | 16];
// [nonce | ciphertext | tag];
pub fn decrypt_kkt_frame(
secret_key: &KKTSessionSecret,
kkt_frame_bytes: &[u8],
aad: &[u8],
) -> Result<(KKTFrame, KKTContext), KKTError> {
let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN];
nonce.copy_from_slice(&kkt_frame_bytes[0..NONCE_LEN]);
let plaintext = decrypt(
secret_key.as_bytes(),
&kkt_frame_bytes[NONCE_LEN..],
aad,
&nonce,
)?;
KKTFrame::from_bytes(&plaintext)
}
fn encrypt(
secret_key: &[u8; 32],
plaintext: &[u8],
aad: &[u8],
nonce: &[u8; NONCE_LEN],
) -> Result<Vec<u8>, KKTError> {
let mut output_buffer = vec![0; plaintext.len() + TAG_LEN];
libcrux_chacha20poly1305::encrypt(&secret_key, &plaintext, &mut output_buffer, &aad, &nonce)?;
Ok(output_buffer)
}
fn decrypt(
secret_key: &[u8; 32],
ciphertext: &[u8],
aad: &[u8],
nonce: &[u8; NONCE_LEN],
) -> Result<Vec<u8>, KKTError> {
let mut output_buffer = vec![0; ciphertext.len() - TAG_LEN];
libcrux_chacha20poly1305::decrypt(&secret_key, &mut output_buffer, &ciphertext, &aad, &nonce)?;
Ok(output_buffer)
}
#[cfg(test)]
mod test {
use rand::{RngCore, rng};
use crate::{
ciphersuite::HASH_LEN_256,
encryption::{KKTSessionSecret, decrypt, encrypt},
key_utils::generate_keypair_x25519,
};
#[test]
fn test_keygen() {
let responder_x25519_keypair = generate_keypair_x25519();
let (session_secret_key, ephemeral_public_key) =
KKTSessionSecret::new(&responder_x25519_keypair.1);
let shared_secret = KKTSessionSecret::try_derive(
&responder_x25519_keypair.0,
&ephemeral_public_key.as_bytes().as_slice(),
)
.unwrap();
assert_eq!(shared_secret.as_bytes(), session_secret_key.as_bytes())
}
#[test]
fn test_encryption() {
let mut rng = rng();
let mut secret_key = [0u8; HASH_LEN_256];
rng.fill_bytes(&mut secret_key);
let mut plaintext = vec![0; 100];
rng.fill_bytes(&mut plaintext);
let mut nonce = [0; 12];
rng.fill_bytes(&mut nonce);
let mut aad = vec![0; 124];
rng.fill_bytes(&mut aad);
let ciphertext = encrypt(&secret_key, &plaintext, &aad, &nonce).unwrap();
let o_plaintext = decrypt(&secret_key, &ciphertext, &aad, &nonce).unwrap();
assert_eq!(o_plaintext, plaintext)
}
}
+36
View File
@@ -1,6 +1,9 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::Debug;
use nym_crypto::asymmetric::x25519::KeyRecoveryError;
use thiserror::Error;
use crate::context::KKTStatus;
@@ -40,10 +43,19 @@ pub enum KKTError {
#[error("{}", info)]
X25519Error { info: &'static str },
#[error("{}", info)]
AEADError { info: &'static str },
#[error("Generic libcrux error")]
LibcruxError,
}
impl From<KeyRecoveryError> for KKTError {
fn from(err: KeyRecoveryError) -> Self {
err.into()
}
}
impl From<libcrux_kem::Error> for KKTError {
fn from(err: libcrux_kem::Error) -> Self {
match err {
@@ -83,3 +95,27 @@ impl From<libcrux_ecdh::Error> for KKTError {
}
}
}
impl From<libcrux_chacha20poly1305::AeadError> for KKTError {
fn from(err: libcrux_chacha20poly1305::AeadError) -> Self {
KKTError::KEMError {
info: match err {
libcrux_chacha20poly1305::AeadError::PlaintextTooLarge => {
"Plaintext is longer than u32::MAX"
}
libcrux_chacha20poly1305::AeadError::CiphertextTooLarge => {
"Ciphertext is longer than u32::MAX"
}
libcrux_chacha20poly1305::AeadError::AadTooLarge => "Aad is longer than u32::MAX",
libcrux_chacha20poly1305::AeadError::CiphertextTooShort => {
"The provided destination ciphertext does not fit the ciphertext and tag"
}
libcrux_chacha20poly1305::AeadError::PlaintextTooShort => {
"The provided destination plaintext is too short to fit the decrypted plaintext"
}
libcrux_chacha20poly1305::AeadError::InvalidCiphertext => {
"The ciphertext is not a valid encryption under the given key and nonce."
}
},
}
}
}
+15
View File
@@ -7,7 +7,22 @@ use classic_mceliece_rust::keypair_boxed;
use libcrux_kem::{Algorithm, key_gen};
use libcrux_sha3;
use nym_crypto::asymmetric::ed25519;
use rand::{CryptoRng, RngCore};
pub fn generate_keypair_ed25519<R>(rng: &mut R, index: Option<u32>) -> ed25519::KeyPair
where
R: RngCore + CryptoRng,
{
let mut secret_initiator: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_initiator);
ed25519::KeyPair::from_secret(secret_initiator, index.unwrap_or(0))
}
pub fn generate_keypair_x25519() -> (nym_sphinx::PrivateKey, nym_sphinx::PublicKey) {
let private_key = nym_sphinx::PrivateKey::random();
let public_key = nym_sphinx::PublicKey::from(&private_key);
(private_key, public_key)
}
// (decapsulation_key, encapsulation_key)
pub fn generate_keypair_libcrux<R>(
+229 -192
View File
@@ -14,6 +14,7 @@ use rand::{CryptoRng, RngCore};
use crate::{
ciphersuite::{Ciphersuite, EncapsulationKey},
context::{KKTContext, KKTMode},
encryption::{decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_kkt_frame},
error::KKTError,
frame::KKTFrame,
};
@@ -24,7 +25,11 @@ pub use crate::session::{
responder_ingest_message, responder_process,
};
/// Request a KEM public key from a responder (OneWay mode).
use nym_crypto::asymmetric::x25519;
use crate::encryption::{KKTSessionSecret, encrypt_initial_kkt_frame};
/// Perform an *Encrypted* request for a KEM public key from a responder (OneWay mode).
///
/// This is the client-side operation that initiates a KKT exchange.
/// The request will be signed with the provided signing key.
@@ -33,17 +38,20 @@ pub use crate::session::{
/// * `rng` - Random number generator
/// * `ciphersuite` - Negotiated ciphersuite (KEM, hash, signature algorithms)
/// * `signing_key` - Client's Ed25519 signing key for authentication
/// * `responder_dh_public_key` - Responder's long-term x25519 Diffie-Hellman public key
///
/// # Returns
/// * `KKTSessionSecret` - Session Secret Key to use when decrypting responses
/// * `KKTContext` - Context to use when validating the response
/// * `KKTFrame` - Signed request frame to send to responder
/// * `Vec<u8>` - Contains the client's ephemeral public key and encrypted and signed bytes to send to responder
///
/// # Example
/// ```ignore
/// let (context, request_frame) = request_kem_key(
/// let (session_secret, context, request_frame) = request_kem_key(
/// &mut rng,
/// ciphersuite,
/// client_signing_key,
/// responder_dh_public_key,
/// )?;
/// // Send request_frame to gateway
/// ```
@@ -51,13 +59,21 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
rng: &mut R,
ciphersuite: Ciphersuite,
signing_key: &ed25519::PrivateKey,
) -> Result<(KKTContext, KKTFrame), KKTError> {
responder_dh_public_key: &nym_sphinx::PublicKey,
) -> Result<(KKTSessionSecret, KKTContext, Vec<u8>), KKTError> {
// OneWay mode: client only wants responder's KEM key
// None: client doesn't send their own KEM key
initiator_process(rng, KKTMode::OneWay, ciphersuite, signing_key, None)
let (initiator_context, initiator_frame) =
initiator_process(rng, KKTMode::OneWay, ciphersuite, signing_key, None)?;
// Generate the session's shared secret and encrypt the Intitiator's request
let (session_secret, encrypted_request_bytes) =
encrypt_initial_kkt_frame(rng, responder_dh_public_key, &initiator_frame)?;
Ok((session_secret, initiator_context, encrypted_request_bytes))
}
/// Validate a KKT response and extract the responder's KEM public key.
/// Decrypt, validate an *Encrypted* KKT response and extract the responder's KEM public key.
///
/// This is the client-side operation that processes the gateway's response.
/// It verifies the signature and validates the key hash against the expected value
@@ -65,6 +81,7 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
///
/// # Arguments
/// * `context` - Context from the initial request
/// * `session_secret` - Session Secret Key (generated with request)
/// * `responder_vk` - Responder's Ed25519 verification key (from directory)
/// * `expected_key_hash` - Expected hash of responder's KEM key (from directory)
/// * `response_bytes` - Serialized response frame from responder
@@ -76,7 +93,8 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
/// ```ignore
/// let gateway_kem_key = validate_kem_response(
/// &mut context,
/// gateway_verification_key,
/// &session_secret,
/// &gateway_verification_key,
/// &expected_hash_from_directory,
/// &response_bytes,
/// )?;
@@ -84,14 +102,24 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
/// ```
pub fn validate_kem_response<'a>(
context: &mut KKTContext,
session_secret: &KKTSessionSecret,
responder_vk: &ed25519::PublicKey,
expected_key_hash: &[u8],
response_bytes: &[u8],
encrypted_response_bytes: &[u8],
) -> Result<EncapsulationKey<'a>, KKTError> {
initiator_ingest_response(context, responder_vk, expected_key_hash, response_bytes)
let (responder_frame, responder_context) =
decrypt_kkt_frame(&session_secret, &encrypted_response_bytes, b"KKT_Response").unwrap();
initiator_ingest_response(
context,
&responder_frame,
&responder_context,
responder_vk,
&expected_key_hash,
)
}
/// Handle a KKT request and generate a signed response with the responder's KEM key.
/// Handle an *Encrypted* KKT request and generate a signed response with the responder's KEM key.
///
/// This is the gateway-side operation that processes a client's KKT request.
/// It validates the request signature (if authenticated) and responds with
@@ -116,240 +144,249 @@ pub fn validate_kem_response<'a>(
/// )?;
/// // Send response_frame back to client
/// ```
pub fn handle_kem_request<'a>(
request_frame: &KKTFrame,
pub fn handle_kem_request<'a, R>(
rng: &mut R,
encrypted_request_bytes: &[u8],
initiator_vk: Option<&ed25519::PublicKey>,
responder_signing_key: &ed25519::PrivateKey,
responder_dh_private_key: &nym_sphinx::PrivateKey,
responder_kem_key: &EncapsulationKey<'a>,
) -> Result<KKTFrame, KKTError> {
// Parse context from the request frame
let request_bytes = request_frame.to_bytes();
let (_, request_context) = KKTFrame::from_bytes(&request_bytes)?;
) -> Result<Vec<u8>, KKTError>
where
R: RngCore + CryptoRng,
{
// Compute the session's shared secret, decrypt and parse context from the request frame
let (session_secret, request_frame, initiator_context) =
decrypt_initial_kkt_frame(responder_dh_private_key, encrypted_request_bytes)?;
// Validate the request (verifies signature if initiator_vk provided)
let (mut response_context, _) = responder_ingest_message(
&request_context,
&initiator_context,
initiator_vk,
None, // Not checking initiator's KEM key in OneWay mode
request_frame,
&request_frame,
)?;
// Generate signed response with our KEM public key
responder_process(
let responder_frame = responder_process(
&mut response_context,
request_frame.session_id_ref(),
responder_signing_key,
responder_kem_key,
)
)?;
// Encrypt the responder's response with the session's shared secret
encrypt_kkt_frame(rng, &session_secret, &responder_frame, b"KKT_Response")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
ciphersuite::{HashFunction, KEM, SignatureScheme},
key_utils::{generate_keypair_libcrux, hash_encapsulation_key},
};
// #[cfg(test)]
// mod tests {
// use super::*;
// use crate::{
// ciphersuite::{HashFunction, KEM, SignatureScheme},
// key_utils::{generate_keypair_libcrux, hash_encapsulation_key},
// };
#[test]
fn test_kkt_wrappers_oneway_authenticated() {
let mut rng = rand::rng();
// #[test]
// fn test_kkt_wrappers_oneway_authenticated() {
// let mut rng = rand::rng();
// Generate Ed25519 keypairs for both parties
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
// // Generate Ed25519 keypairs for both parties
// let mut initiator_secret = [0u8; 32];
// rng.fill_bytes(&mut initiator_secret);
// let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// let mut responder_secret = [0u8; 32];
// rng.fill_bytes(&mut responder_secret);
// let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// Generate responder's KEM keypair (X25519 for testing)
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
// // Generate responder's KEM keypair (X25519 for testing)
// let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
// let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
// Create ciphersuite
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
HashFunction::Blake3,
SignatureScheme::Ed25519,
None,
)
.unwrap();
// // Create ciphersuite
// let ciphersuite = Ciphersuite::resolve_ciphersuite(
// KEM::X25519,
// HashFunction::Blake3,
// SignatureScheme::Ed25519,
// None,
// )
// .unwrap();
// Hash the KEM key (simulating directory storage)
let key_hash = hash_encapsulation_key(
&ciphersuite.hash_function(),
ciphersuite.hash_len(),
&responder_kem_key.encode(),
);
// // Hash the KEM key (simulating directory storage)
// let key_hash = hash_encapsulation_key(
// &ciphersuite.hash_function(),
// ciphersuite.hash_len(),
// &responder_kem_key.encode(),
// );
// Client: Request KEM key
let (mut context, request_frame) =
request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
// // Client: Request KEM key
// let (mut context, request_frame) =
// request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
// Gateway: Handle request
let response_frame = handle_kem_request(
&request_frame,
Some(initiator_keypair.public_key()), // Authenticated
responder_keypair.private_key(),
&responder_kem_key,
)
.unwrap();
// // Gateway: Handle request
// let response_frame = handle_kem_request(
// &request_frame,
// Some(initiator_keypair.public_key()), // Authenticated
// responder_keypair.private_key(),
// &responder_kem_key,
// )
// .unwrap();
// Client: Validate response
let obtained_key = validate_kem_response(
&mut context,
responder_keypair.public_key(),
&key_hash,
&response_frame.to_bytes(),
)
.unwrap();
// // Client: Validate response
// let obtained_key = validate_kem_response(
// &mut context,
// responder_keypair.public_key(),
// &key_hash,
// &response_frame.to_bytes(),
// )
// .unwrap();
// Verify we got the correct KEM key
assert_eq!(obtained_key.encode(), responder_kem_key.encode());
}
// // Verify we got the correct KEM key
// assert_eq!(obtained_key.encode(), responder_kem_key.encode());
// }
#[test]
fn test_kkt_wrappers_anonymous() {
let mut rng = rand::rng();
// #[test]
// fn test_kkt_wrappers_anonymous() {
// let mut rng = rand::rng();
// Only responder has keys
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// // Only responder has keys
// let mut responder_secret = [0u8; 32];
// rng.fill_bytes(&mut responder_secret);
// let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
// let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
// let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
HashFunction::Blake3,
SignatureScheme::Ed25519,
None,
)
.unwrap();
// let ciphersuite = Ciphersuite::resolve_ciphersuite(
// KEM::X25519,
// HashFunction::Blake3,
// SignatureScheme::Ed25519,
// None,
// )
// .unwrap();
let key_hash = hash_encapsulation_key(
&ciphersuite.hash_function(),
ciphersuite.hash_len(),
&responder_kem_key.encode(),
);
// let key_hash = hash_encapsulation_key(
// &ciphersuite.hash_function(),
// ciphersuite.hash_len(),
// &responder_kem_key.encode(),
// );
// Anonymous initiator
let (mut context, request_frame) =
anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
// // Anonymous initiator
// let (mut context, request_frame) =
// anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
// Gateway: Handle anonymous request
let response_frame = handle_kem_request(
&request_frame,
None, // Anonymous - no verification key
responder_keypair.private_key(),
&responder_kem_key,
)
.unwrap();
// // Gateway: Handle anonymous request
// let response_frame = handle_kem_request(
// &request_frame,
// None, // Anonymous - no verification key
// responder_keypair.private_key(),
// &responder_kem_key,
// )
// .unwrap();
// Initiator: Validate response
let obtained_key = validate_kem_response(
&mut context,
responder_keypair.public_key(),
&key_hash,
&response_frame.to_bytes(),
)
.unwrap();
// // Initiator: Validate response
// let obtained_key = validate_kem_response(
// &mut context,
// responder_keypair.public_key(),
// &key_hash,
// &response_frame.to_bytes(),
// )
// .unwrap();
assert_eq!(obtained_key.encode(), responder_kem_key.encode());
}
// assert_eq!(obtained_key.encode(), responder_kem_key.encode());
// }
#[test]
fn test_invalid_signature_rejected() {
let mut rng = rand::rng();
// #[test]
// fn test_invalid_signature_rejected() {
// let mut rng = rand::rng();
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
// let mut initiator_secret = [0u8; 32];
// rng.fill_bytes(&mut initiator_secret);
// let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// let mut responder_secret = [0u8; 32];
// rng.fill_bytes(&mut responder_secret);
// let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// Different keypair for wrong signature
let mut wrong_secret = [0u8; 32];
rng.fill_bytes(&mut wrong_secret);
let wrong_keypair = ed25519::KeyPair::from_secret(wrong_secret, 2);
// // Different keypair for wrong signature
// let mut wrong_secret = [0u8; 32];
// rng.fill_bytes(&mut wrong_secret);
// let wrong_keypair = ed25519::KeyPair::from_secret(wrong_secret, 2);
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
// let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
// let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
HashFunction::Blake3,
SignatureScheme::Ed25519,
None,
)
.unwrap();
// let ciphersuite = Ciphersuite::resolve_ciphersuite(
// KEM::X25519,
// HashFunction::Blake3,
// SignatureScheme::Ed25519,
// None,
// )
// .unwrap();
let (_context, request_frame) =
request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
// let (_context, request_frame) =
// request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
// Gateway handles request but we provide WRONG verification key
let result = handle_kem_request(
&request_frame,
Some(wrong_keypair.public_key()), // Wrong key!
responder_keypair.private_key(),
&responder_kem_key,
);
// // Gateway handles request but we provide WRONG verification key
// let result = handle_kem_request(
// &request_frame,
// Some(wrong_keypair.public_key()), // Wrong key!
// responder_keypair.private_key(),
// &responder_kem_key,
// );
// Should fail signature verification
assert!(result.is_err());
}
// // Should fail signature verification
// assert!(result.is_err());
// }
#[test]
fn test_hash_mismatch_rejected() {
let mut rng = rand::rng();
// #[test]
// fn test_hash_mismatch_rejected() {
// let mut rng = rand::rng();
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
// let mut initiator_secret = [0u8; 32];
// rng.fill_bytes(&mut initiator_secret);
// let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// let mut responder_secret = [0u8; 32];
// rng.fill_bytes(&mut responder_secret);
// let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
// let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
// let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
HashFunction::Blake3,
SignatureScheme::Ed25519,
None,
)
.unwrap();
// let ciphersuite = Ciphersuite::resolve_ciphersuite(
// KEM::X25519,
// HashFunction::Blake3,
// SignatureScheme::Ed25519,
// None,
// )
// .unwrap();
// Use WRONG hash
let wrong_hash = [0u8; 32];
// // Use WRONG hash
// let wrong_hash = [0u8; 32];
let (mut context, request_frame) =
request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
// let (mut context, request_frame) =
// request_kem_key(&mut rng, ciphersuite, initiator_keypair.private_key()).unwrap();
let response_frame = handle_kem_request(
&request_frame,
Some(initiator_keypair.public_key()),
responder_keypair.private_key(),
&responder_kem_key,
)
.unwrap();
// let response_frame = handle_kem_request(
// &request_frame,
// Some(initiator_keypair.public_key()),
// responder_keypair.private_key(),
// &responder_kem_key,
// )
// .unwrap();
// Client validates with WRONG hash
let result = validate_kem_response(
&mut context,
responder_keypair.public_key(),
&wrong_hash, // Wrong!
&response_frame.to_bytes(),
);
// // Client validates with WRONG hash
// let result = validate_kem_response(
// &mut context,
// responder_keypair.public_key(),
// &wrong_hash, // Wrong!
// &response_frame.to_bytes(),
// );
// Should fail hash validation
assert!(result.is_err());
}
}
// // Should fail hash validation
// assert!(result.is_err());
// }
// }
+272 -17
View File
@@ -3,15 +3,13 @@
pub mod ciphersuite;
pub mod context;
// pub mod encryption;
pub mod encryption;
pub mod error;
pub mod frame;
pub mod key_utils;
pub mod kkt;
pub mod session;
// pub mod psq;
// This must be less than 4 bits
pub const KKT_VERSION: u8 = 1;
const _: () = assert!(KKT_VERSION < 1 << 4);
@@ -23,8 +21,15 @@ mod test {
use crate::{
ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM},
encryption::{
decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame,
encrypt_kkt_frame,
},
frame::KKTFrame,
key_utils::{generate_keypair_libcrux, generate_keypair_mceliece, hash_encapsulation_key},
key_utils::{
generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_mceliece,
generate_keypair_x25519, hash_encapsulation_key,
},
session::{
anonymous_initiator_process, initiator_ingest_response, initiator_process,
responder_ingest_message, responder_process,
@@ -36,13 +41,9 @@ mod test {
let mut rng = rand::rng();
// generate ed25519 keys
let mut secret_initiator: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_initiator);
let initiator_ed25519_keypair = ed25519::KeyPair::from_secret(secret_initiator, 0);
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut secret_responder: [u8; 32] = [0u8; 32];
rng.fill_bytes(&mut secret_responder);
let responder_ed25519_keypair = ed25519::KeyPair::from_secret(secret_responder, 1);
for kem in [KEM::MlKem768, KEM::XWing, KEM::X25519, KEM::McEliece] {
for hash_function in [
HashFunction::Blake3,
@@ -125,15 +126,18 @@ mod test {
let r_bytes = r_frame.to_bytes();
let obtained_key = initiator_ingest_response(
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,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
assert_eq!(obtained_key.encode(), r_kem_key_bytes)
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
// Initiator, OneWay
{
@@ -170,11 +174,14 @@ mod test {
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,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
@@ -216,15 +223,263 @@ mod test {
let r_bytes = r_frame.to_bytes();
let obtained_key = initiator_ingest_response(
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,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
&r_bytes,
)
.unwrap();
assert_eq!(obtained_key.encode(), r_kem_key_bytes)
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
}
}
}
#[test]
fn test_kkt_psq_e2e_encrypted() {
let mut rng = rand::rng();
// generate ed25519 keys
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
// generate responder x25519 keys
let responder_x25519_keypair = generate_keypair_x25519();
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_libcrux(&mut rng, kem).unwrap().1,
),
EncapsulationKey::MlKem768(
generate_keypair_libcrux(&mut rng, kem).unwrap().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,
);
// Anonymous Initiator, OneWay
{
let (mut i_context, i_frame) =
anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
// encryption - initiator frame
let (i_session_secret, i_bytes) =
encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame)
.unwrap();
// decryption - initiator frame
let (r_session_secret, i_frame_r, i_context_r) =
decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap();
let (mut r_context, _) =
responder_ingest_message(&i_context_r, None, None, &i_frame_r).unwrap();
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
.unwrap();
// encryption - responder frame
let r_bytes =
encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, b"KKT_Response")
.unwrap();
// decryption - responder frame
let (i_frame_r, i_context_r) =
decrypt_kkt_frame(&i_session_secret, &r_bytes, b"KKT_Response").unwrap();
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
// Initiator, OneWay
{
let (mut i_context, i_frame) = initiator_process(
&mut rng,
crate::context::KKTMode::OneWay,
ciphersuite,
initiator_ed25519_keypair.private_key(),
None,
)
.unwrap();
// encryption - initiator frame
let (i_session_secret, i_bytes) =
encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame)
.unwrap();
// decryption - initiator frame
let (r_session_secret, i_frame_r, r_context) =
decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap();
let (mut r_context, r_obtained_key) = responder_ingest_message(
&r_context,
Some(initiator_ed25519_keypair.public_key()),
None,
&i_frame_r,
)
.unwrap();
assert!(r_obtained_key.is_none());
let r_frame = responder_process(
&mut r_context,
i_frame_r.session_id_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
.unwrap();
// encryption - responder frame
let r_bytes =
encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, b"KKT_Response")
.unwrap();
// decryption - responder frame
let (i_frame_r, i_context_r) =
decrypt_kkt_frame(&i_session_secret, &r_bytes, b"KKT_Response").unwrap();
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
// Initiator, Mutual
{
let (mut i_context, i_frame) = initiator_process(
&mut rng,
crate::context::KKTMode::Mutual,
ciphersuite,
initiator_ed25519_keypair.private_key(),
Some(&initiator_kem_public_key),
)
.unwrap();
// encryption - initiator frame
let (i_session_secret, i_bytes) =
encrypt_initial_kkt_frame(&mut rng, &responder_x25519_keypair.1, &i_frame)
.unwrap();
// decryption - initiator frame
let (r_session_secret, i_frame_r, i_context_r) =
decrypt_initial_kkt_frame(&responder_x25519_keypair.0, &i_bytes).unwrap();
let (mut r_context, r_obtained_key) = responder_ingest_message(
&i_context_r,
Some(initiator_ed25519_keypair.public_key()),
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_ref(),
responder_ed25519_keypair.private_key(),
&responder_kem_public_key,
)
.unwrap();
// encryption - responder frame
let r_bytes =
encrypt_kkt_frame(&mut rng, &r_session_secret, &r_frame, b"KKT_Response")
.unwrap();
// decryption - responder frame
let (i_frame_r, i_context_r) =
decrypt_kkt_frame(&i_session_secret, &r_bytes, b"KKT_Response").unwrap();
let i_obtained_key = initiator_ingest_response(
&mut i_context,
&i_frame_r,
&i_context_r,
responder_ed25519_keypair.public_key(),
&r_dir_hash,
)
.unwrap();
assert_eq!(i_obtained_key.encode(), r_kem_key_bytes)
}
}
}
+7 -9
View File
@@ -76,13 +76,11 @@ where
pub fn initiator_ingest_response<'a>(
own_context: &mut KKTContext,
remote_frame: &KKTFrame,
remote_context: &KKTContext,
remote_verification_key: &ed25519::PublicKey,
expected_hash: &[u8],
message_bytes: &[u8],
) -> Result<EncapsulationKey<'a>, KKTError> {
// sizes have to be correct
let (frame, remote_context) = KKTFrame::from_bytes(message_bytes)?;
check_compatibility(own_context, &remote_context)?;
match remote_context.status() {
KKTStatus::Ok => {
@@ -90,21 +88,21 @@ pub fn initiator_ingest_response<'a>(
remote_context.full_message_len() - remote_context.signature_len(),
);
bytes_to_verify.extend_from_slice(&remote_context.encode()?);
bytes_to_verify.extend_from_slice(frame.body_ref());
bytes_to_verify.extend_from_slice(frame.session_id_ref());
bytes_to_verify.extend_from_slice(remote_frame.body_ref());
bytes_to_verify.extend_from_slice(remote_frame.session_id_ref());
match Signature::from_bytes(frame.signature_ref()) {
match Signature::from_bytes(remote_frame.signature_ref()) {
Ok(sig) => match remote_verification_key.verify(bytes_to_verify, &sig) {
Ok(()) => {
let received_encapsulation_key = EncapsulationKey::decode(
own_context.ciphersuite().kem(),
frame.body_ref(),
remote_frame.body_ref(),
)?;
match validate_encapsulation_key(
&own_context.ciphersuite().hash_function(),
own_context.ciphersuite().hash_len(),
frame.body_ref(),
remote_frame.body_ref(),
expected_hash,
) {
true => Ok(received_encapsulation_key),
+7
View File
@@ -3,6 +3,7 @@
use crate::{noise_protocol::NoiseError, replay::ReplayError};
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
use nym_kkt::error::KKTError;
use thiserror::Error;
#[derive(Error, Debug)]
@@ -79,3 +80,9 @@ pub enum LpError {
#[error("Ed25519 key conversion error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
}
impl From<KKTError> for LpError {
fn from(err: KKTError) -> Self {
err.into()
}
}
+126 -107
View File
@@ -59,11 +59,13 @@
//! ).unwrap();
//! ```
use crate::LpError;
use crate::keypair::{PrivateKey, PublicKey};
use crate::message::{KKTRequestData, KKTResponseData};
use nym_crypto::asymmetric::ed25519;
use crate::{LpError, keypair};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_kkt::ciphersuite::{Ciphersuite, EncapsulationKey};
use nym_kkt::context::KKTContext;
use nym_kkt::encryption::KKTSessionSecret;
use nym_kkt::frame::KKTFrame;
use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response};
@@ -85,14 +87,15 @@ use nym_kkt::kkt::{handle_kem_request, request_kem_key, validate_kem_response};
pub fn create_request(
ciphersuite: Ciphersuite,
signing_key: &ed25519::PrivateKey,
) -> Result<(KKTContext, KKTRequestData), LpError> {
responder_dh_public_key: &nym_sphinx::PublicKey,
) -> Result<(KKTSessionSecret, KKTContext, KKTRequestData), LpError> {
// Note: Uses rand 0.9's thread_rng() to match nym-kkt's rand version
let mut rng = rand09::rng();
let (context, frame) = request_kem_key(&mut rng, ciphersuite, signing_key)
.map_err(|e| LpError::KKTError(e.to_string()))?;
let (session_secret, context, request_bytes) =
request_kem_key(&mut rng, ciphersuite, signing_key, responder_dh_public_key)
.map_err(|e| LpError::KKTError(e.to_string()))?;
let request_bytes = frame.to_bytes();
Ok((context, KKTRequestData(request_bytes)))
Ok((session_secret, context, KKTRequestData(request_bytes)))
}
/// Processes a KKT response and extracts the responder's KEM public key.
@@ -116,17 +119,18 @@ pub fn create_request(
/// - Key hash doesn't match expected value
pub fn process_response<'a>(
mut context: KKTContext,
session_secret: KKTSessionSecret,
responder_vk: &ed25519::PublicKey,
expected_key_hash: &[u8],
response_data: &KKTResponseData,
) -> Result<EncapsulationKey<'a>, LpError> {
validate_kem_response(
Ok(validate_kem_response(
&mut context,
&session_secret,
responder_vk,
expected_key_hash,
&response_data.0,
)
.map_err(|e| LpError::KKTError(e.to_string()))
)?)
}
/// Handles a KKT request and generates a signed response with the responder's KEM key.
@@ -153,22 +157,22 @@ pub fn handle_request<'a>(
request_data: &KKTRequestData,
initiator_vk: Option<&ed25519::PublicKey>,
responder_signing_key: &ed25519::PrivateKey,
responder_dh_private_key: &nym_sphinx::PrivateKey,
responder_kem_key: &EncapsulationKey<'a>,
) -> Result<KKTResponseData, LpError> {
// Deserialize request frame
let (request_frame, _) = KKTFrame::from_bytes(&request_data.0)
.map_err(|e| LpError::KKTError(format!("Failed to parse KKT request: {}", e)))?;
// Note: Uses rand 0.9's thread_rng() to match nym-kkt's rand version
let mut rng = rand09::rng();
// Handle the request and generate response
let response_frame = handle_kem_request(
&request_frame,
let response_bytes = handle_kem_request(
&mut rng,
&request_data.0,
initiator_vk,
responder_signing_key,
responder_dh_private_key,
responder_kem_key,
)
.map_err(|e| LpError::KKTError(e.to_string()))?;
)?;
let response_bytes = response_frame.to_bytes();
Ok(KKTResponseData(response_bytes))
}
@@ -176,7 +180,10 @@ pub fn handle_request<'a>(
mod tests {
use super::*;
use nym_kkt::ciphersuite::{HashFunction, KEM, SignatureScheme};
use nym_kkt::key_utils::{generate_keypair_libcrux, hash_encapsulation_key};
use nym_kkt::key_utils::{
generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_x25519,
hash_encapsulation_key,
};
use rand09::RngCore;
#[test]
@@ -184,13 +191,10 @@ mod tests {
let mut rng = rand09::rng();
// Generate Ed25519 keypairs for both parties
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519();
// Generate responder's KEM keypair (X25519 for testing)
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
@@ -213,14 +217,19 @@ mod tests {
);
// Client: Create request
let (context, request_data) =
create_request(ciphersuite, initiator_keypair.private_key()).unwrap();
let (session_secret, context, request_data) = create_request(
ciphersuite,
initiator_ed25519_keypair.private_key(),
&responder_x25519_pk,
)
.unwrap();
// Gateway: Handle request
let response_data = handle_request(
&request_data,
Some(initiator_keypair.public_key()),
responder_keypair.private_key(),
Some(initiator_ed25519_keypair.public_key()),
responder_ed25519_keypair.private_key(),
&responder_x25519_sk,
&responder_kem_key,
)
.unwrap();
@@ -228,7 +237,8 @@ mod tests {
// Client: Process response
let obtained_key = process_response(
context,
responder_keypair.public_key(),
session_secret,
responder_ed25519_keypair.public_key(),
&key_hash,
&response_data,
)
@@ -238,70 +248,68 @@ mod tests {
assert_eq!(obtained_key.encode(), responder_kem_key.encode());
}
#[test]
fn test_kkt_roundtrip_anonymous() {
let mut rng = rand09::rng();
// #[test]
// fn test_kkt_roundtrip_anonymous() {
// let mut rng = rand09::rng();
// Only responder has keys (anonymous initiator)
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
// // Only responder has keys (anonymous initiator)
// let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
// let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519();
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
HashFunction::Blake3,
SignatureScheme::Ed25519,
None,
)
.unwrap();
// let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
// let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
let key_hash = hash_encapsulation_key(
&ciphersuite.hash_function(),
ciphersuite.hash_len(),
&responder_kem_key.encode(),
);
// let ciphersuite = Ciphersuite::resolve_ciphersuite(
// KEM::X25519,
// HashFunction::Blake3,
// SignatureScheme::Ed25519,
// None,
// )
// .unwrap();
// Anonymous initiator - use anonymous_initiator_process directly
use nym_kkt::kkt::anonymous_initiator_process;
let (mut context, request_frame) =
anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
let request_data = KKTRequestData(request_frame.to_bytes());
// let key_hash = hash_encapsulation_key(
// &ciphersuite.hash_function(),
// ciphersuite.hash_len(),
// &responder_kem_key.encode(),
// );
// Gateway: Handle anonymous request
let response_data = handle_request(
&request_data,
None, // Anonymous - no verification key
responder_keypair.private_key(),
&responder_kem_key,
)
.unwrap();
// // Anonymous initiator - use anonymous_initiator_process directly
// use nym_kkt::kkt::anonymous_initiator_process;
// let (mut context, request_frame) =
// anonymous_initiator_process(&mut rng, ciphersuite).unwrap();
// let request_data = KKTRequestData(request_frame.to_bytes());
// Initiator: Validate response
let obtained_key = validate_kem_response(
&mut context,
responder_keypair.public_key(),
&key_hash,
&response_data.0,
)
.unwrap();
// // Gateway: Handle anonymous request
// let response_data = handle_request(
// &request_data,
// None, // Anonymous - no verification key
// responder_ed25519_keypair.private_key(),
// &responder_x25519_sk,
// &responder_kem_key,
// )
// .unwrap();
assert_eq!(obtained_key.encode(), responder_kem_key.encode());
}
// // Initiator: Validate response
// let obtained_key = validate_kem_response(
// &mut context,
// responder_ed25519_keypair.public_key(),
// &key_hash,
// &response_data.0,
// )
// .unwrap();
// assert_eq!(obtained_key.encode(), responder_kem_key.encode());
// }
#[test]
fn test_invalid_signature_rejected() {
let mut rng = rand09::rng();
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519();
// Different keypair for wrong signature
let mut wrong_secret = [0u8; 32];
@@ -319,14 +327,19 @@ mod tests {
)
.unwrap();
let (_context, request_data) =
create_request(ciphersuite, initiator_keypair.private_key()).unwrap();
let (session_secret, _context, request_data) = create_request(
ciphersuite,
initiator_ed25519_keypair.private_key(),
&responder_x25519_pk,
)
.unwrap();
// Gateway handles request but we provide WRONG verification key
let result = handle_request(
&request_data,
Some(wrong_keypair.public_key()), // Wrong key!
responder_keypair.private_key(),
responder_ed25519_keypair.private_key(),
&responder_x25519_sk,
&responder_kem_key,
);
@@ -343,13 +356,10 @@ mod tests {
fn test_hash_mismatch_rejected() {
let mut rng = rand09::rng();
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519();
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
@@ -365,13 +375,18 @@ mod tests {
// Use WRONG hash
let wrong_hash = [0u8; 32];
let (context, request_data) =
create_request(ciphersuite, initiator_keypair.private_key()).unwrap();
let (session_secret, context, request_data) = create_request(
ciphersuite,
initiator_ed25519_keypair.private_key(),
&responder_x25519_pk,
)
.unwrap();
let response_data = handle_request(
&request_data,
Some(initiator_keypair.public_key()),
responder_keypair.private_key(),
Some(initiator_ed25519_keypair.public_key()),
responder_ed25519_keypair.private_key(),
&responder_x25519_sk,
&responder_kem_key,
)
.unwrap();
@@ -379,7 +394,8 @@ mod tests {
// Client validates with WRONG hash
let result = process_response(
context,
responder_keypair.public_key(),
session_secret,
responder_ed25519_keypair.public_key(),
&wrong_hash, // Wrong!
&response_data,
);
@@ -397,9 +413,9 @@ mod tests {
fn test_malformed_request_rejected() {
let mut rng = rand09::rng();
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519();
let (_, responder_kem_pk) = generate_keypair_libcrux(&mut rng, KEM::X25519).unwrap();
let responder_kem_key = EncapsulationKey::X25519(responder_kem_pk);
@@ -410,7 +426,8 @@ mod tests {
let result = handle_request(
&malformed_request,
None,
responder_keypair.private_key(),
responder_ed25519_keypair.private_key(),
&responder_x25519_sk,
&responder_kem_key,
);
@@ -427,13 +444,10 @@ mod tests {
fn test_malformed_response_rejected() {
let mut rng = rand09::rng();
let mut initiator_secret = [0u8; 32];
rng.fill_bytes(&mut initiator_secret);
let initiator_keypair = ed25519::KeyPair::from_secret(initiator_secret, 0);
let initiator_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(0));
let responder_ed25519_keypair = generate_keypair_ed25519(&mut rng, Some(1));
let mut responder_secret = [0u8; 32];
rng.fill_bytes(&mut responder_secret);
let responder_keypair = ed25519::KeyPair::from_secret(responder_secret, 1);
let (responder_x25519_sk, responder_x25519_pk) = generate_keypair_x25519();
let ciphersuite = Ciphersuite::resolve_ciphersuite(
KEM::X25519,
@@ -443,8 +457,12 @@ mod tests {
)
.unwrap();
let (context, _request_data) =
create_request(ciphersuite, initiator_keypair.private_key()).unwrap();
let (session_secret, context, _request_data) = create_request(
ciphersuite,
initiator_ed25519_keypair.private_key(),
&responder_x25519_pk,
)
.unwrap();
// Create malformed response data
let malformed_response = KKTResponseData(vec![0xFF; 100]);
@@ -452,7 +470,8 @@ mod tests {
let result = process_response(
context,
responder_keypair.public_key(),
session_secret,
responder_ed25519_keypair.public_key(),
&key_hash,
&malformed_response,
);
+26 -20
View File
@@ -15,6 +15,7 @@ use crate::replay::ReceivingKeyCounterValidator;
use crate::{LpError, LpMessage, LpPacket};
use nym_crypto::asymmetric::ed25519;
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey};
use nym_kkt::kkt::handle_kem_request;
use parking_lot::Mutex;
use snow::Builder;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
@@ -44,6 +45,7 @@ pub enum KKTState {
InitiatorWaiting {
/// KKT context for verifying the response
context: nym_kkt::context::KKTContext,
session_secret: nym_kkt::encryption::KKTSessionSecret,
},
/// KKT exchange completed (initiator received and validated KEM key).
@@ -61,7 +63,7 @@ impl std::fmt::Debug for KKTState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotStarted => write!(f, "KKTState::NotStarted"),
Self::InitiatorWaiting { context } => f
Self::InitiatorWaiting { context, .. } => f
.debug_struct("KKTState::InitiatorWaiting")
.field("context", context)
.finish(),
@@ -429,13 +431,19 @@ impl LpSession {
};
let mut rng = rand09::rng();
match request_kem_key(&mut rng, ciphersuite, &self.local_ed25519_private) {
Ok((context, request_frame)) => {
match request_kem_key(
&mut rng,
ciphersuite,
&self.local_ed25519_private,
&self.remote_x25519_public,
) {
Ok((session_secret, context, request_bytes)) => {
// Store context for response validation
*kkt_state = KKTState::InitiatorWaiting { context };
*kkt_state = KKTState::InitiatorWaiting {
context,
session_secret,
};
// Serialize KKT frame to bytes
let request_bytes = request_frame.to_bytes();
Some(Ok(LpMessage::KKTRequest(crate::message::KKTRequestData(
request_bytes,
))))
@@ -480,9 +488,12 @@ impl LpSession {
let mut kkt_state = self.kkt_state.lock();
// Extract context from waiting state
let mut context = match &*kkt_state {
KKTState::InitiatorWaiting { context } => *context,
// Extract session secret and context from waiting state
let (mut context, session_secret) = match &*kkt_state {
KKTState::InitiatorWaiting {
context,
session_secret,
} => (*context, *session_secret),
_ => {
return Err(LpError::Internal(
"KKT response received in invalid state".to_string(),
@@ -515,6 +526,7 @@ impl LpSession {
// Validate response and extract KEM key
let kem_pk = validate_kem_response(
&mut context,
&session_secret,
&self.remote_ed25519_public,
hash_ref,
response_bytes,
@@ -548,20 +560,15 @@ impl LpSession {
request_bytes: &[u8],
responder_kem_pk: &EncapsulationKey,
) -> Result<LpMessage, LpError> {
use nym_kkt::{frame::KKTFrame, kkt::handle_kem_request};
let mut kkt_state = self.kkt_state.lock();
// Deserialize request frame
let (request_frame, _) = KKTFrame::from_bytes(request_bytes).map_err(|e| {
LpError::Internal(format!("KKT request deserialization failed: {:?}", e))
})?;
// Handle request and create signed response
let response_frame = handle_kem_request(
&request_frame,
let response_bytes = handle_kem_request(
&mut rand09::rng(),
&request_bytes,
Some(&self.remote_ed25519_public), // Verify initiator signature
&self.local_ed25519_private, // Sign response
&self.local_ed25519_private,
&self.local_x25519_private, // Sign response
responder_kem_pk,
)
.map_err(|e| LpError::Internal(format!("KKT request handling failed: {:?}", e)))?;
@@ -571,7 +578,6 @@ impl LpSession {
*kkt_state = KKTState::ResponderProcessed;
// Serialize response frame
let response_bytes = response_frame.to_bytes();
Ok(LpMessage::KKTResponse(crate::message::KKTResponseData(
response_bytes,