Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8958126e8 | |||
| fe45b856b7 | |||
| 5c50b760a8 | |||
| aafa99ea47 | |||
| 4767a719f7 | |||
| 89d167de08 | |||
| 05d5f4ae83 | |||
| 2109beeef6 | |||
| d1a5342625 | |||
| f0645bad57 | |||
| 60f8fe09a7 |
Generated
+470
-365
File diff suppressed because it is too large
Load Diff
@@ -23,9 +23,15 @@ libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux" }
|
||||
|
||||
rand = "0.9.2"
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] }
|
||||
# classic-mceliece-rust = { git = "https://github.com/georgio/classic-mceliece-rust", features = ["mceliece460896f", "zeroize"] }
|
||||
classic-mceliece-rust = { version = "3.1.0", features = [
|
||||
"mceliece460896f",
|
||||
"zeroize"] }
|
||||
|
||||
|
||||
libcrux-psq = { git = "https://github.com/cryspen/libcrux", features = ["classic-mceliece"] }
|
||||
libcrux-ml-kem = { git = "https://github.com/cryspen/libcrux"}
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = "0.9.0"
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -12,7 +12,10 @@ use nym_kkt::{
|
||||
ciphersuite::{Ciphersuite, EncapsulationKey, HashFunction, KEM, SignatureScheme},
|
||||
context::KKTMode,
|
||||
frame::KKTFrame,
|
||||
key_utils::{generate_keypair_libcrux, generate_keypair_mceliece, hash_encapsulation_key},
|
||||
key_utils::{
|
||||
generate_keypair_libcrux, generate_keypair_mceliece, generate_keypair_mlkem,
|
||||
hash_encapsulation_key,
|
||||
},
|
||||
session::{
|
||||
anonymous_initiator_process, initiator_ingest_response, initiator_process,
|
||||
responder_ingest_message, responder_process,
|
||||
@@ -69,8 +72,8 @@ pub fn kkt_benchmark(c: &mut Criterion) {
|
||||
|
||||
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),
|
||||
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),
|
||||
|
||||
@@ -3,23 +3,27 @@
|
||||
|
||||
use crate::error::KKTError;
|
||||
use libcrux_kem::Algorithm;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use nym_kkt_ciphersuite::*;
|
||||
|
||||
pub enum EncapsulationKey<'a> {
|
||||
pub enum EncapsulationKey {
|
||||
MlKem768(libcrux_kem::PublicKey),
|
||||
XWing(libcrux_kem::PublicKey),
|
||||
X25519(libcrux_kem::PublicKey),
|
||||
McEliece(classic_mceliece_rust::PublicKey<'a>),
|
||||
McEliece(libcrux_psq::classic_mceliece::PublicKey),
|
||||
}
|
||||
|
||||
pub enum DecapsulationKey<'a> {
|
||||
MlKem768(libcrux_kem::PrivateKey),
|
||||
XWing(libcrux_kem::PrivateKey),
|
||||
X25519(libcrux_kem::PrivateKey),
|
||||
McEliece(classic_mceliece_rust::SecretKey<'a>),
|
||||
impl Debug for EncapsulationKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MlKem768(_) => f.debug_tuple("MlKem768").finish(),
|
||||
Self::XWing(_) => f.debug_tuple("XWing").finish(),
|
||||
Self::X25519(_) => f.debug_tuple("X25519").finish(),
|
||||
Self::McEliece(_) => f.debug_tuple("McEliece").finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> EncapsulationKey<'a> {
|
||||
impl EncapsulationKey {
|
||||
pub(crate) fn decode(kem: KEM, bytes: &[u8]) -> Result<Self, KKTError> {
|
||||
match kem {
|
||||
KEM::McEliece => {
|
||||
@@ -33,7 +37,7 @@ impl<'a> EncapsulationKey<'a> {
|
||||
// Size must be correct due to KKTFrame::from_bytes(message_bytes)?
|
||||
public_key_bytes.clone_from_slice(bytes);
|
||||
Ok(EncapsulationKey::McEliece(
|
||||
classic_mceliece_rust::PublicKey::from(public_key_bytes),
|
||||
libcrux_psq::classic_mceliece::PublicKey::from(public_key_bytes),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -54,10 +58,30 @@ impl<'a> EncapsulationKey<'a> {
|
||||
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
EncapsulationKey::XWing(public_key)
|
||||
| EncapsulationKey::MlKem768(public_key)
|
||||
| EncapsulationKey::X25519(public_key) => public_key.encode(),
|
||||
EncapsulationKey::McEliece(public_key) => Vec::from(public_key.as_array()),
|
||||
EncapsulationKey::XWing(public_key) | EncapsulationKey::X25519(public_key) => {
|
||||
public_key.encode()
|
||||
}
|
||||
EncapsulationKey::McEliece(public_key) => {
|
||||
let bytes_ref: &[u8] = public_key.as_ref();
|
||||
Vec::from(bytes_ref)
|
||||
}
|
||||
EncapsulationKey::MlKem768(public_key) => Vec::from(public_key.encode()),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub enum DecapsulationKey {
|
||||
MlKem768(libcrux_kem::MlKem768PrivateKey),
|
||||
XWing(libcrux_kem::PrivateKey),
|
||||
X25519(libcrux_kem::PrivateKey),
|
||||
McEliece(libcrux_psq::classic_mceliece::SecretKey),
|
||||
}
|
||||
impl Debug for DecapsulationKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MlKem768(_) => f.debug_tuple("MlKem768").finish(),
|
||||
Self::XWing(_) => f.debug_tuple("XWing").finish(),
|
||||
Self::X25519(_) => f.debug_tuple("X25519").finish(),
|
||||
Self::McEliece(_) => f.debug_tuple("McEliece").finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ pub enum KKTError {
|
||||
#[error("{}", info)]
|
||||
AEADError { info: &'static str },
|
||||
|
||||
#[error("{}", info)]
|
||||
DecodingError { info: &'static str },
|
||||
|
||||
#[error("Generic libcrux error")]
|
||||
LibcruxError,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::ciphersuite::HashFunction;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use classic_mceliece_rust::keypair_boxed;
|
||||
|
||||
use libcrux_kem::{MlKem768PrivateKey, MlKem768PublicKey};
|
||||
use nym_kkt_ciphersuite::{DEFAULT_HASH_LEN, KEMKeyDigests};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
@@ -38,9 +37,6 @@ where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
match kem {
|
||||
crate::ciphersuite::KEM::MlKem768 => {
|
||||
Ok(libcrux_kem::key_gen(libcrux_kem::Algorithm::MlKem768, rng)?)
|
||||
}
|
||||
crate::ciphersuite::KEM::XWing => Ok(libcrux_kem::key_gen(
|
||||
libcrux_kem::Algorithm::XWingKemDraft06,
|
||||
rng,
|
||||
@@ -53,19 +49,28 @@ where
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_keypair_mlkem<R>(rng: &mut R) -> (MlKem768PrivateKey, MlKem768PublicKey)
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
libcrux_ml_kem::mlkem768::rand::generate_key_pair(rng).into_parts()
|
||||
}
|
||||
|
||||
// (decapsulation_key, encapsulation_key)
|
||||
pub fn generate_keypair_mceliece<'a, R>(
|
||||
pub fn generate_keypair_mceliece<R>(
|
||||
rng: &mut R,
|
||||
) -> (
|
||||
classic_mceliece_rust::SecretKey<'a>,
|
||||
classic_mceliece_rust::PublicKey<'a>,
|
||||
libcrux_psq::classic_mceliece::SecretKey,
|
||||
libcrux_psq::classic_mceliece::PublicKey,
|
||||
)
|
||||
where
|
||||
// this is annoying because mceliece lib uses rand 0.8.5...
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let (encapsulation_key, decapsulation_key) = keypair_boxed(rng);
|
||||
(decapsulation_key, encapsulation_key)
|
||||
let kp = libcrux_psq::classic_mceliece::KeyPair::generate_key_pair(rng);
|
||||
|
||||
(kp.sk, kp.pk)
|
||||
}
|
||||
|
||||
pub fn hash_key_bytes(
|
||||
|
||||
@@ -98,13 +98,13 @@ pub fn request_kem_key<R: CryptoRng + RngCore>(
|
||||
/// )?;
|
||||
/// // Use gateway_kem_key for PSQ
|
||||
/// ```
|
||||
pub fn validate_kem_response<'a>(
|
||||
pub fn validate_kem_response(
|
||||
context: &mut KKTContext,
|
||||
session_secret: &KKTSessionSecret,
|
||||
responder_vk: &ed25519::PublicKey,
|
||||
expected_key_hash: &[u8],
|
||||
encrypted_response_bytes: &[u8],
|
||||
) -> Result<EncapsulationKey<'a>, KKTError> {
|
||||
) -> Result<EncapsulationKey, KKTError> {
|
||||
let (responder_frame, responder_context) =
|
||||
decrypt_kkt_response_frame(session_secret, encrypted_response_bytes)?;
|
||||
|
||||
@@ -159,7 +159,7 @@ pub fn handle_kem_request<'a, R>(
|
||||
initiator_vk: Option<&ed25519::PublicKey>,
|
||||
responder_signing_key: &ed25519::PrivateKey,
|
||||
responder_dh_private_key: &x25519::PrivateKey,
|
||||
responder_kem_key: &EncapsulationKey<'a>,
|
||||
responder_kem_key: &EncapsulationKey,
|
||||
) -> Result<Vec<u8>, KKTError>
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
|
||||
@@ -28,7 +28,7 @@ mod test {
|
||||
frame::KKTFrame,
|
||||
key_utils::{
|
||||
generate_keypair_ed25519, generate_keypair_libcrux, generate_keypair_mceliece,
|
||||
generate_keypair_x25519, hash_encapsulation_key,
|
||||
generate_keypair_mlkem, generate_keypair_x25519, hash_encapsulation_key,
|
||||
},
|
||||
session::{
|
||||
anonymous_initiator_process, initiator_ingest_response, initiator_process,
|
||||
@@ -63,12 +63,8 @@ mod test {
|
||||
|
||||
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,
|
||||
),
|
||||
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),
|
||||
@@ -269,12 +265,8 @@ mod test {
|
||||
|
||||
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,
|
||||
),
|
||||
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),
|
||||
|
||||
@@ -10,12 +10,12 @@ use crate::{
|
||||
key_utils::validate_encapsulation_key,
|
||||
};
|
||||
|
||||
pub fn initiator_process<'a, R>(
|
||||
pub fn initiator_process<R>(
|
||||
rng: &mut R,
|
||||
mode: KKTMode,
|
||||
ciphersuite: Ciphersuite,
|
||||
signing_key: &ed25519::PrivateKey,
|
||||
own_encapsulation_key: Option<&EncapsulationKey<'a>>,
|
||||
own_encapsulation_key: Option<&EncapsulationKey>,
|
||||
) -> Result<(KKTContext, KKTFrame), KKTError>
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
@@ -72,13 +72,13 @@ where
|
||||
Ok((context, KKTFrame::new(context_bytes, &[], session_id, &[])))
|
||||
}
|
||||
|
||||
pub fn initiator_ingest_response<'a>(
|
||||
pub fn initiator_ingest_response(
|
||||
own_context: &mut KKTContext,
|
||||
remote_frame: &KKTFrame,
|
||||
remote_context: &KKTContext,
|
||||
remote_verification_key: &ed25519::PublicKey,
|
||||
expected_hash: &[u8],
|
||||
) -> Result<EncapsulationKey<'a>, KKTError> {
|
||||
) -> Result<EncapsulationKey, KKTError> {
|
||||
check_compatibility(own_context, remote_context)?;
|
||||
match remote_context.status() {
|
||||
KKTStatus::Ok => {
|
||||
@@ -124,12 +124,12 @@ pub fn initiator_ingest_response<'a>(
|
||||
|
||||
// todo: figure out how to handle errors using status codes
|
||||
|
||||
pub fn responder_ingest_message<'a>(
|
||||
pub fn responder_ingest_message(
|
||||
remote_context: &KKTContext,
|
||||
remote_verification_key: Option<&ed25519::PublicKey>,
|
||||
expected_hash: Option<&[u8]>,
|
||||
remote_frame: &KKTFrame,
|
||||
) -> Result<(KKTContext, Option<EncapsulationKey<'a>>), KKTError> {
|
||||
) -> Result<(KKTContext, Option<EncapsulationKey>), KKTError> {
|
||||
let own_context = remote_context.derive_responder_header()?;
|
||||
|
||||
match remote_context.role() {
|
||||
@@ -200,11 +200,11 @@ pub fn responder_ingest_message<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn responder_process<'a>(
|
||||
pub fn responder_process(
|
||||
own_context: &mut KKTContext,
|
||||
session_id: KKTSessionId,
|
||||
signing_key: &ed25519::PrivateKey,
|
||||
encapsulation_key: &EncapsulationKey<'a>,
|
||||
encapsulation_key: &EncapsulationKey,
|
||||
) -> Result<KKTFrame, KKTError> {
|
||||
let body = encapsulation_key.encode();
|
||||
|
||||
|
||||
+92
-16
@@ -1,14 +1,16 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use libcrux_kem::{MlKem768PrivateKey, MlKem768PublicKey};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_kkt::ciphersuite::{HashFunction, KEM};
|
||||
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey, HashFunction, KEM};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Representation of a local Lewes Protocol peer
|
||||
/// encapsulating all the known information and keys.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct LpLocalPeer {
|
||||
/// Local Ed25519 keys for PSQ authentication
|
||||
pub(crate) ed25519: Arc<ed25519::KeyPair>,
|
||||
@@ -16,8 +18,14 @@ pub struct LpLocalPeer {
|
||||
/// Local x25519 keys (Noise static key)
|
||||
pub(crate) x25519: Arc<x25519::KeyPair>,
|
||||
|
||||
/// Local KEM key used for PSQ
|
||||
/// Local KEM key used for PSQ (x25519: to deprecate)
|
||||
pub(crate) kem_psq: Option<Arc<x25519::KeyPair>>,
|
||||
|
||||
/// Local MlKem keypair used for PSQ
|
||||
pub(crate) mlkem: Option<(Arc<DecapsulationKey>, Arc<EncapsulationKey>)>,
|
||||
|
||||
/// Local McEliece keypair used for PSQ
|
||||
pub(crate) mceliece: Option<(Arc<DecapsulationKey>, Arc<EncapsulationKey>)>,
|
||||
}
|
||||
|
||||
impl LpLocalPeer {
|
||||
@@ -26,15 +34,43 @@ impl LpLocalPeer {
|
||||
ed25519,
|
||||
x25519,
|
||||
kem_psq: None,
|
||||
mlkem: None,
|
||||
mceliece: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
// #[must_use]
|
||||
pub fn with_kem_psq_key(mut self, key: Arc<x25519::KeyPair>) -> Self {
|
||||
self.kem_psq = Some(key);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_mlkem_keypair(
|
||||
mut self,
|
||||
decapsulation_key: &MlKem768PrivateKey,
|
||||
encapsulation_key: &MlKem768PublicKey,
|
||||
) -> Self {
|
||||
self.mlkem = Some((
|
||||
Arc::new(DecapsulationKey::MlKem768(decapsulation_key.clone())),
|
||||
Arc::new(EncapsulationKey::MlKem768(
|
||||
libcrux_kem::PublicKey::MlKem768(encapsulation_key.clone()),
|
||||
)),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_mceliece_keypair(
|
||||
mut self,
|
||||
decapsulation_key: libcrux_psq::classic_mceliece::SecretKey,
|
||||
encapsulation_key: libcrux_psq::classic_mceliece::PublicKey,
|
||||
) -> Self {
|
||||
self.mceliece = Some((
|
||||
Arc::new(DecapsulationKey::McEliece(decapsulation_key)),
|
||||
Arc::new(EncapsulationKey::McEliece(encapsulation_key)),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ed25519(&self) -> &Arc<ed25519::KeyPair> {
|
||||
&self.ed25519
|
||||
}
|
||||
@@ -46,17 +82,28 @@ impl LpLocalPeer {
|
||||
/// Convert this `LpLocalPeer` into a valid `LpRemotePeer` that can be used within tests
|
||||
#[doc(hidden)]
|
||||
pub fn as_remote(&self) -> LpRemotePeer {
|
||||
let expected_kem_key_digests = match &self.kem_psq {
|
||||
None => HashMap::new(),
|
||||
Some(kem_keys) => {
|
||||
let hashes =
|
||||
nym_kkt::key_utils::produce_key_digests(kem_keys.public_key().as_bytes());
|
||||
let mut expected_kem_key_digests = HashMap::new();
|
||||
|
||||
if let Some(x25519_key) = &self.kem_psq {
|
||||
expected_kem_key_digests.insert(
|
||||
KEM::X25519,
|
||||
nym_kkt::key_utils::produce_key_digests(x25519_key.public_key().as_bytes()),
|
||||
);
|
||||
}
|
||||
if let Some(mlkem_key) = &self.mlkem {
|
||||
expected_kem_key_digests.insert(
|
||||
KEM::MlKem768,
|
||||
nym_kkt::key_utils::produce_key_digests(&mlkem_key.1.encode()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(mceliece_key) = &self.mceliece {
|
||||
expected_kem_key_digests.insert(
|
||||
KEM::McEliece,
|
||||
nym_kkt::key_utils::produce_key_digests(&mceliece_key.1.encode()),
|
||||
);
|
||||
}
|
||||
|
||||
let mut digests = HashMap::new();
|
||||
digests.insert(KEM::X25519, hashes);
|
||||
digests
|
||||
}
|
||||
};
|
||||
LpRemotePeer {
|
||||
ed25519_public: *self.ed25519.public_key(),
|
||||
x25519_public: *self.x25519.public_key(),
|
||||
@@ -66,7 +113,7 @@ impl LpLocalPeer {
|
||||
|
||||
// this is only exposed in tests as ideally we should be storing the proper types to begin with
|
||||
#[cfg(test)]
|
||||
pub fn encapsulate_kem_key(&self) -> Option<nym_kkt::ciphersuite::EncapsulationKey<'_>> {
|
||||
pub fn encapsulate_kem_key(&self) -> Option<nym_kkt::ciphersuite::EncapsulationKey> {
|
||||
let pk_bytes = self.kem_psq.as_ref()?.public_key().to_bytes();
|
||||
let libcrux_pk =
|
||||
libcrux_kem::PublicKey::decode(libcrux_kem::Algorithm::X25519, &pk_bytes).ok()?;
|
||||
@@ -75,6 +122,26 @@ impl LpLocalPeer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LpLocalPeer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LpLocalPeer")
|
||||
.field("ed25519", &self.ed25519)
|
||||
.field("x25519", &self.x25519)
|
||||
.field("kem_psq", &self.kem_psq)
|
||||
.field(
|
||||
"mlkem",
|
||||
&format!(
|
||||
"mlkem_public_key: {}",
|
||||
match &self.mlkem {
|
||||
Some(keypair) => format!("{:?}", keypair.1.as_ref()),
|
||||
None => "None".to_string(),
|
||||
}
|
||||
),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a remote Lewes Protocol peer
|
||||
/// encapsulating all the known information and keys.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -124,16 +191,25 @@ pub fn mock_peer() -> LpLocalPeer {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn random_peer<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> LpLocalPeer {
|
||||
pub fn random_peer<'a, R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> LpLocalPeer {
|
||||
use nym_kkt::key_utils::{generate_keypair_mceliece, generate_keypair_mlkem};
|
||||
|
||||
let ed25519 = Arc::new(ed25519::KeyPair::new(rng));
|
||||
let x25519 = Arc::new(ed25519.to_x25519());
|
||||
let kem_psq = Some(x25519.clone());
|
||||
|
||||
let mlkem_keypair = generate_keypair_mlkem(&mut rand09::rng());
|
||||
let mceliece_keypair = generate_keypair_mceliece(&mut rand09::rng());
|
||||
|
||||
LpLocalPeer {
|
||||
ed25519,
|
||||
x25519,
|
||||
kem_psq,
|
||||
mlkem: None,
|
||||
mceliece: None,
|
||||
}
|
||||
.with_mlkem_keypair(&mlkem_keypair.0, &mlkem_keypair.1)
|
||||
.with_mceliece_keypair(mceliece_keypair.0, mceliece_keypair.1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+57
-36
@@ -48,7 +48,9 @@
|
||||
|
||||
use crate::LpError;
|
||||
use libcrux_psq::v1::cred::{Authenticator, Ed25519};
|
||||
use libcrux_psq::v1::impls::MlKem768 as PsqMlKem768;
|
||||
use libcrux_psq::v1::impls::X25519 as PsqX25519;
|
||||
use libcrux_psq::v1::impls::XWingKemDraft06 as PsqXwing;
|
||||
use libcrux_psq::v1::psk_registration::{Initiator, InitiatorMsg, Responder};
|
||||
use libcrux_psq::v1::traits::{Ciphertext as PsqCiphertext, PSQ};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
@@ -290,64 +292,83 @@ pub fn psq_initiator_create_message(
|
||||
// Step 1: Classical ECDH for baseline security
|
||||
let ecdh_secret = local_x25519_private.diffie_hellman(remote_x25519_public);
|
||||
|
||||
// Step 2: PSQ v1 with Ed25519 authentication
|
||||
// Extract X25519 KEM key from EncapsulationKey
|
||||
let kem_pk = match remote_kem_public {
|
||||
EncapsulationKey::X25519(pk) => pk,
|
||||
_ => {
|
||||
return Err(LpError::KKTError(
|
||||
"Only X25519 KEM is currently supported for PSQ".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Convert nym Ed25519 keys to libcrux format
|
||||
type Ed25519VerificationKey = <Ed25519 as Authenticator>::VerificationKey;
|
||||
let ed25519_sk_bytes = client_ed25519_sk.to_bytes();
|
||||
let ed25519_pk_bytes = client_ed25519_pk.to_bytes();
|
||||
let ed25519_verification_key = Ed25519VerificationKey::from_bytes(ed25519_pk_bytes);
|
||||
|
||||
// Use PSQ v1 API with Ed25519 authentication
|
||||
let mut rng = rand09::rng();
|
||||
let (state, initiator_msg) = Initiator::send_initial_message::<Ed25519, PsqX25519>(
|
||||
session_context,
|
||||
Duration::from_secs(3600), // 1 hour expiry
|
||||
kem_pk,
|
||||
&ed25519_sk_bytes,
|
||||
&ed25519_verification_key,
|
||||
&mut rng,
|
||||
)
|
||||
.map_err(|e| {
|
||||
let init_msg_err = |e: libcrux_psq::v1::Error| {
|
||||
tracing::error!(
|
||||
"PSQ initiator failed - KEM encapsulation or signing error: {:?}",
|
||||
e
|
||||
);
|
||||
LpError::Internal(format!("PSQ v1 send_initial_message failed: {:?}", e))
|
||||
})?;
|
||||
};
|
||||
|
||||
// Extract PSQ shared secret (unregistered PSK) - this is K_pq
|
||||
let psq_psk = state.unregistered_psk();
|
||||
let serialisation_err =
|
||||
|e| LpError::Internal(format!("InitiatorMsg serialization failed: {:?}", e));
|
||||
|
||||
// pq_shared_secret is the raw K_pq from KEM encapsulation.
|
||||
// Store it for subsession derivation before it's combined with ECDH.
|
||||
let pq_shared_secret: [u8; 32] = *psq_psk;
|
||||
let ctx = session_context;
|
||||
let sk = &ed25519_sk_bytes;
|
||||
let vk = &ed25519_verification_key;
|
||||
let exp = Duration::from_secs(3600); // 1 hour expiry
|
||||
|
||||
// Step 2: PSQ v1 with Ed25519 authentication
|
||||
// Use PSQ v1 API with Ed25519 authentication
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
// psq_psk - this is K_pq
|
||||
let (pq_shared_secret, payload) = match remote_kem_public {
|
||||
EncapsulationKey::X25519(kem_pk) => {
|
||||
let (state, initiator_msg) = Initiator::send_initial_message::<Ed25519, PsqX25519>(
|
||||
ctx, exp, kem_pk, sk, vk, &mut rng,
|
||||
)
|
||||
.map_err(init_msg_err)?;
|
||||
|
||||
let msg_bytes = initiator_msg
|
||||
.tls_serialize_detached()
|
||||
.map_err(serialisation_err)?;
|
||||
(*state.unregistered_psk(), msg_bytes)
|
||||
}
|
||||
EncapsulationKey::MlKem768(kem_pk) => {
|
||||
let (state, initiator_msg) = Initiator::send_initial_message::<Ed25519, PsqMlKem768>(
|
||||
ctx, exp, kem_pk, sk, vk, &mut rng,
|
||||
)
|
||||
.map_err(init_msg_err)?;
|
||||
|
||||
let msg_bytes = initiator_msg
|
||||
.tls_serialize_detached()
|
||||
.map_err(serialisation_err)?;
|
||||
(*state.unregistered_psk(), msg_bytes)
|
||||
}
|
||||
EncapsulationKey::XWing(kem_pk) => {
|
||||
let (state, initiator_msg) = Initiator::send_initial_message::<Ed25519, PsqXwing>(
|
||||
ctx, exp, kem_pk, sk, vk, &mut rng,
|
||||
)
|
||||
.map_err(init_msg_err)?;
|
||||
|
||||
let msg_bytes = initiator_msg
|
||||
.tls_serialize_detached()
|
||||
.map_err(serialisation_err)?;
|
||||
(*state.unregistered_psk(), msg_bytes)
|
||||
}
|
||||
EncapsulationKey::McEliece(pk) => {
|
||||
todo!("unsupported mceliece for PSQ")
|
||||
}
|
||||
};
|
||||
|
||||
// Step 3: Combine ECDH + PSQ via Blake3 KDF
|
||||
let mut combined = Vec::with_capacity(64 + psq_psk.len());
|
||||
let mut combined = Vec::with_capacity(64 + pq_shared_secret.len());
|
||||
combined.extend_from_slice(&ecdh_secret);
|
||||
combined.extend_from_slice(psq_psk); // psq_psk is already a &[u8; 32]
|
||||
combined.extend_from_slice(&pq_shared_secret); // psq_psk is already a &[u8; 32]
|
||||
combined.extend_from_slice(salt);
|
||||
|
||||
let final_psk = nym_crypto::kdf::derive_key_blake3(PSK_PSQ_CONTEXT, &combined, &[]);
|
||||
|
||||
// Serialize InitiatorMsg with TLS encoding for transport
|
||||
let msg_bytes = initiator_msg
|
||||
.tls_serialize_detached()
|
||||
.map_err(|e| LpError::Internal(format!("InitiatorMsg serialization failed: {:?}", e)))?;
|
||||
|
||||
Ok(PsqInitiatorResult {
|
||||
psk: final_psk,
|
||||
payload: msg_bytes,
|
||||
payload,
|
||||
pq_shared_secret,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::replay::ReceivingKeyCounterValidator;
|
||||
use crate::{LpError, LpMessage, LpPacket};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_kkt::KKT_RESPONSE_AAD;
|
||||
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey};
|
||||
use nym_kkt::ciphersuite::{DecapsulationKey, EncapsulationKey, KEM};
|
||||
use nym_kkt::context::KKTContext;
|
||||
use nym_kkt::encryption::{
|
||||
KKTSessionSecret, decrypt_initial_kkt_frame, decrypt_kkt_frame, encrypt_initial_kkt_frame,
|
||||
@@ -32,6 +32,7 @@ use nym_kkt::session::{
|
||||
use parking_lot::Mutex;
|
||||
use rand::RngCore;
|
||||
use snow::Builder;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
@@ -90,7 +91,7 @@ pub enum KKTState {
|
||||
/// KKT exchange completed (initiator received and validated KEM key).
|
||||
Completed {
|
||||
/// Responder's KEM public key for PSQ encapsulation
|
||||
kem_pk: Box<EncapsulationKey<'static>>,
|
||||
kem_pk: Box<EncapsulationKey>,
|
||||
},
|
||||
|
||||
/// Responder processed a KKT request and sent response.
|
||||
@@ -303,6 +304,14 @@ impl LpSession {
|
||||
self.remote_peer.ed25519_public
|
||||
}
|
||||
|
||||
pub fn local_kem_encapsulation_key(&self, kem: &KEM) -> Option<Arc<EncapsulationKey>> {
|
||||
match kem {
|
||||
KEM::MlKem768 => self.local_peer.mlkem.as_ref().map(|x| x.1.clone()),
|
||||
KEM::McEliece => self.local_peer.mceliece.as_ref().map(|x| x.1.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the remote X25519 public key.
|
||||
///
|
||||
/// Used for tie-breaking in simultaneous subsession initiation.
|
||||
@@ -538,9 +547,7 @@ impl LpSession {
|
||||
|
||||
// TODO: missing Zeroize
|
||||
/// Convert local KEM PSQ keys to typed EncapsulationKey / EncapsulationKey
|
||||
pub fn encapsulated_kem_keys(
|
||||
&self,
|
||||
) -> Result<(DecapsulationKey<'_>, EncapsulationKey<'_>), LpError> {
|
||||
pub fn encapsulated_kem_keys(&self) -> Result<(DecapsulationKey, EncapsulationKey), LpError> {
|
||||
let kem_keys = self
|
||||
.local_peer
|
||||
.kem_psq
|
||||
@@ -599,7 +606,7 @@ impl LpSession {
|
||||
|
||||
// georgio this needs to be moved one level up (maybe chosen in LPSession)
|
||||
let ciphersuite = match Ciphersuite::resolve_ciphersuite(
|
||||
KEM::X25519,
|
||||
KEM::MlKem768,
|
||||
HashFunction::Blake3,
|
||||
SignatureScheme::Ed25519,
|
||||
None,
|
||||
@@ -743,11 +750,7 @@ impl LpSession {
|
||||
///
|
||||
/// * `Ok(LpMessage::KKTResponse)` - Signed KKT response ready to send
|
||||
/// * `Err(LpError)` - Signature verification failed or invalid request
|
||||
pub fn process_kkt_request(
|
||||
&self,
|
||||
request_bytes: &[u8],
|
||||
responder_kem_pk: &EncapsulationKey,
|
||||
) -> Result<LpMessage, LpError> {
|
||||
pub fn process_kkt_request(&self, request_bytes: &[u8]) -> Result<LpMessage, LpError> {
|
||||
let mut rng = rand09::rng();
|
||||
|
||||
let mut kkt_state = self.kkt_state.lock();
|
||||
@@ -758,33 +761,46 @@ impl LpSession {
|
||||
) {
|
||||
Ok((session_secret, request_frame, remote_context)) => {
|
||||
match responder_ingest_message(&remote_context, None, None, &request_frame) {
|
||||
Ok((mut context, _)) => match responder_process(
|
||||
&mut context,
|
||||
request_frame.session_id(),
|
||||
self.local_peer.ed25519().private_key(),
|
||||
responder_kem_pk,
|
||||
) {
|
||||
Ok(response_frame) => match encrypt_kkt_frame(
|
||||
&mut rng,
|
||||
&session_secret,
|
||||
&response_frame,
|
||||
KKT_RESPONSE_AAD,
|
||||
Ok((mut context, _)) => {
|
||||
let local_kem_key: Arc<EncapsulationKey> =
|
||||
match self.local_kem_encapsulation_key(&context.ciphersuite().kem()) {
|
||||
Some(key) => key.clone(),
|
||||
None => {
|
||||
return Err(LpError::Internal(format!(
|
||||
"KEM key for algorithm ({:?}) is not available.",
|
||||
&context.ciphersuite().kem()
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
match responder_process(
|
||||
&mut context,
|
||||
request_frame.session_id(),
|
||||
self.local_peer.ed25519().private_key(),
|
||||
&local_kem_key,
|
||||
) {
|
||||
Ok(response_bytes) => response_bytes,
|
||||
Ok(response_frame) => match encrypt_kkt_frame(
|
||||
&mut rng,
|
||||
&session_secret,
|
||||
&response_frame,
|
||||
KKT_RESPONSE_AAD,
|
||||
) {
|
||||
Ok(response_bytes) => response_bytes,
|
||||
Err(e) => {
|
||||
return Err(LpError::Internal(format!(
|
||||
"KKT response encryption failure : {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(LpError::Internal(format!(
|
||||
"KKT response encryption failure : {:?}",
|
||||
"KKT response generation failure : {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(LpError::Internal(format!(
|
||||
"KKT response generation failure : {:?}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(LpError::Internal(format!(
|
||||
"KKT request handling failure: {:?}",
|
||||
|
||||
@@ -360,22 +360,7 @@ impl LpStateMachine {
|
||||
// Packet message is already parsed, match on it directly
|
||||
match &packet.message {
|
||||
LpMessage::KKTRequest(kkt_request) if !session.is_initiator() => {
|
||||
// Responder processes KKT request
|
||||
// Convert X25519 public key to KEM format for KKT response
|
||||
use nym_kkt::ciphersuite::EncapsulationKey;
|
||||
|
||||
// Get local X25519 public key by deriving from private key
|
||||
let local_x25519_public = session.local_x25519_public();
|
||||
|
||||
// Convert to libcrux KEM public key
|
||||
match libcrux_kem::PublicKey::decode(
|
||||
libcrux_kem::Algorithm::X25519,
|
||||
local_x25519_public.as_bytes(),
|
||||
) {
|
||||
Ok(libcrux_public_key) => {
|
||||
let responder_kem_pk = EncapsulationKey::X25519(libcrux_public_key);
|
||||
|
||||
match session.process_kkt_request(&kkt_request.0, &responder_kem_pk) {
|
||||
match session.process_kkt_request(&kkt_request.0) {
|
||||
Ok(kkt_response_message) => {
|
||||
match session.next_packet(kkt_response_message) {
|
||||
Ok(response_packet) => {
|
||||
@@ -396,14 +381,8 @@ impl LpStateMachine {
|
||||
LpState::Closed { reason }
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let reason = format!("Failed to convert X25519 to KEM: {:?}", e);
|
||||
let err = LpError::Internal(reason.clone());
|
||||
result_action = Some(Err(err));
|
||||
LpState::Closed { reason }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
LpMessage::KKTResponse(kkt_response) if session.is_initiator() => {
|
||||
match session.process_kkt_response(&kkt_response.0) {
|
||||
|
||||
Reference in New Issue
Block a user