upgrade kkt
This commit is contained in:
+42
-303
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.",
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user