Files
nym/common/nym-lp/src/codec.rs
T
2026-02-27 08:06:29 +00:00

287 lines
10 KiB
Rust

// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::LpError;
use crate::packet::{EncryptedLpPacket, InnerHeader, LpHeader, LpMessage, LpPacket};
use bytes::BytesMut;
use libcrux_psq::Channel;
// needs to be equal or above to the actual overhead
pub(crate) const SANE_ENC_OVERHEAD: usize = 32;
// needs to be equal or below the actual overhead
pub(crate) const SANE_DEC_OVERHEAD: usize = 24;
pub(crate) fn encrypt_data(
plaintext: &[u8],
transport: &mut libcrux_psq::session::Transport,
) -> Result<Vec<u8>, LpError> {
let mut ciphertext = vec![0u8; plaintext.len() + SANE_ENC_OVERHEAD];
let n = transport.write_message(plaintext, &mut ciphertext)?;
if plaintext.len() + SANE_ENC_OVERHEAD != n {
ciphertext.truncate(n);
}
Ok(ciphertext)
}
pub(crate) fn decrypt_data(
ciphertext: &[u8],
transport: &mut libcrux_psq::session::Transport,
) -> Result<Vec<u8>, LpError> {
if ciphertext.len() < SANE_DEC_OVERHEAD {
return Err(LpError::InsufficientBufferSize);
}
let mut plaintext = vec![0u8; ciphertext.len() - SANE_DEC_OVERHEAD];
let (_, n) = transport.read_message(ciphertext, &mut plaintext)?;
if n != ciphertext.len() - SANE_DEC_OVERHEAD {
plaintext.truncate(n);
}
Ok(plaintext)
}
pub(crate) fn encrypt_lp_packet(
packet: LpPacket,
transport: &mut libcrux_psq::session::Transport,
) -> Result<EncryptedLpPacket, LpError> {
let mut plaintext = BytesMut::with_capacity(InnerHeader::SIZE + packet.message().len());
packet.header().inner.encode(&mut plaintext);
packet.message().encode_content(&mut plaintext);
let ciphertext = encrypt_data(plaintext.as_ref(), transport)?;
Ok(EncryptedLpPacket::new(packet.header().outer, ciphertext))
}
pub(crate) fn decrypt_lp_packet(
packet: EncryptedLpPacket,
transport: &mut libcrux_psq::session::Transport,
) -> Result<LpPacket, LpError> {
if packet.ciphertext().len() < InnerHeader::SIZE + SANE_DEC_OVERHEAD {
return Err(LpError::InsufficientBufferSize);
}
let plaintext = decrypt_data(packet.ciphertext(), transport)?;
let inner_header = InnerHeader::parse(&plaintext)?;
let payload = &plaintext[InnerHeader::SIZE..];
let message = LpMessage::decode_content(payload, inner_header.message_type)?;
Ok(LpPacket::new(
LpHeader {
outer: packet.outer_header(),
inner: inner_header,
},
message,
))
}
#[cfg(test)]
mod tests {
use crate::LpError;
use crate::codec::{decrypt_data, decrypt_lp_packet, encrypt_data, encrypt_lp_packet};
use crate::packet::{EncryptedLpPacket, LpHeader, LpMessage, LpPacket, MessageType};
use crate::peer::mock_peers;
use crate::psq::initiator::{build_psq_ciphersuite, build_psq_principal};
use crate::psq::{PSQ_MSG2_SIZE, psq_msg1_size, responder};
use libcrux_psq::{Channel, IntoSession};
use nym_kkt_ciphersuite::KEM;
use nym_test_utils::helpers::u64_seeded_rng_09;
fn mock_transport() -> (
libcrux_psq::session::Transport,
libcrux_psq::session::Transport,
) {
let kem = KEM::MlKem768;
let rng1 = u64_seeded_rng_09(1);
let rng2 = u64_seeded_rng_09(2);
let (init, resp) = mock_peers();
let remote_resp = resp.as_remote();
let encapsulation_key = resp
.kem_keypairs
.as_ref()
.unwrap()
.encapsulation_key(kem)
.unwrap();
let initiator_ciphersuite =
build_psq_ciphersuite(&init, &remote_resp, &encapsulation_key).unwrap();
let mut psq_initiator = build_psq_principal(rng1, 1, initiator_ciphersuite).unwrap();
let responder_ciphersuite = responder::build_psq_ciphersuite(&resp, kem).unwrap();
let mut psq_responder =
responder::build_psq_principal(rng2, 1, responder_ciphersuite).unwrap();
// Send first message
let mut buf = vec![0u8; psq_msg1_size(kem)];
let mut payload_buf_responder = vec![0u8; 4096];
let mut payload_buf_initiator = vec![0u8; 4096];
let len_i = psq_initiator.write_message(&[], &mut buf).unwrap();
assert_eq!(len_i, buf.len());
// Read first message
let (_, _) = psq_responder
.read_message(&buf, &mut payload_buf_responder)
.unwrap();
// Respond
let mut buf = [0u8; PSQ_MSG2_SIZE];
let len_r = psq_responder.write_message(&[], &mut buf).unwrap();
assert_eq!(len_r, buf.len());
// Finalize on registration initiator
let (len_i_deserialized, _) = psq_initiator
.read_message(&buf, &mut payload_buf_initiator)
.unwrap();
// We read the same amount of data.
assert_eq!(len_r, len_i_deserialized);
// Ready for transport mode
assert!(psq_initiator.is_handshake_finished());
assert!(psq_responder.is_handshake_finished());
let transport_initiator = psq_initiator
.into_session()
.unwrap()
.transport_channel()
.unwrap();
let transport_responder = psq_responder
.into_session()
.unwrap()
.transport_channel()
.unwrap();
(transport_initiator, transport_responder)
}
#[test]
fn basic_plain_encryption_test() {
let (mut init_transport, mut resp_transport) = mock_transport();
for msg_size in [1usize, 10, 100, 1000, 10000, 65535] {
let message1 = vec![42u8; msg_size];
let mut ciphertext = vec![0u8; msg_size + 64];
let written_init1 = init_transport
.write_message(&message1, &mut ciphertext)
.unwrap();
let init_ciphertext_overhead = written_init1 - msg_size;
let ciphertext_content = &ciphertext[..written_init1];
let mut plaintext = vec![0u8; msg_size + 64];
let (read_resp1, written_resp1) = resp_transport
.read_message(ciphertext_content, &mut plaintext)
.unwrap();
let resp_plaintext_overhead = ciphertext_content.len() - written_resp1;
assert_eq!(
written_init1, read_resp1,
"should work for message {msg_size}"
);
let message1_content = &plaintext[..written_resp1];
assert_eq!(
message1_content, &message1,
"should work for message {msg_size}"
);
// reverse the communication
let message2 = vec![43u8; msg_size];
let mut ciphertext2 = vec![0u8; msg_size + 64];
let written_resp2 = resp_transport
.write_message(&message2, &mut ciphertext2)
.unwrap();
let resp_ciphertext_overhead = written_resp2 - msg_size;
let ciphertext_content2 = &ciphertext2[..written_resp2];
let mut plaintext2 = vec![0u8; msg_size + 64];
let (read_init2, written_init2) = init_transport
.read_message(ciphertext_content2, &mut plaintext2)
.unwrap();
let init_plaintext_overhead = ciphertext_content2.len() - written_init2;
assert_eq!(
written_resp2, read_init2,
"should work for message {msg_size}"
);
let message2_content = &plaintext2[..written_init2];
assert_eq!(
message2_content, &message2,
"should work for message {msg_size}"
);
// check consistent overheads
// enc/enc
assert_eq!(init_ciphertext_overhead, resp_ciphertext_overhead);
// dec/dec
assert_eq!(resp_plaintext_overhead, init_plaintext_overhead);
// enc/dec
assert_eq!(init_ciphertext_overhead, resp_plaintext_overhead);
}
}
#[test]
fn basic_encryption() {
let (mut init_transport, mut resp_transport) = mock_transport();
// happy path
let msg = b"foomp".to_vec();
let ciphertext = encrypt_data(&msg, &mut init_transport).unwrap();
let plaintext = decrypt_data(&ciphertext, &mut resp_transport).unwrap();
assert_eq!(msg, plaintext);
// incomplete ciphertext
let msg2 = b"foomp".to_vec();
let ciphertext2 = encrypt_data(&msg2, &mut init_transport).unwrap();
let len = ciphertext2.len();
let dec_err = decrypt_data(&ciphertext2[..len - 1], &mut resp_transport).unwrap_err();
assert!(matches!(dec_err, LpError::PSQSessionFailure { .. }));
// too small buffer
let msg3 = b"foomp".to_vec();
let ciphertext3 = encrypt_data(&msg3, &mut resp_transport).unwrap();
let dec_err = decrypt_data(&ciphertext3[..10], &mut init_transport).unwrap_err();
assert!(matches!(dec_err, LpError::InsufficientBufferSize));
}
#[test]
fn basic_packet_encryption() {
let (mut init_transport, mut resp_transport) = mock_transport();
// happy path
let packet = LpPacket::new(LpHeader::new(123, 0, 1, MessageType::Busy), LpMessage::Busy);
let ciphertext = encrypt_lp_packet(packet.clone(), &mut init_transport).unwrap();
assert_eq!(packet.header().outer, ciphertext.outer_header());
let plaintext = decrypt_lp_packet(ciphertext, &mut resp_transport).unwrap();
assert_eq!(packet, plaintext);
// incomplete ciphertext
let packet = LpPacket::new(LpHeader::new(123, 1, 1, MessageType::Busy), LpMessage::Busy);
let ciphertext2 = encrypt_lp_packet(packet, &mut init_transport).unwrap();
let l = ciphertext2.ciphertext().len();
let malformed_content = ciphertext2.ciphertext()[..l - 1].to_vec();
let malformed = EncryptedLpPacket::new(ciphertext2.outer_header(), malformed_content);
let dec_err = decrypt_lp_packet(malformed, &mut resp_transport).unwrap_err();
assert!(matches!(dec_err, LpError::PSQSessionFailure { .. }));
// too small buffer
let packet = LpPacket::new(LpHeader::new(123, 1, 1, MessageType::Busy), LpMessage::Busy);
let ciphertext3 = encrypt_lp_packet(packet, &mut resp_transport).unwrap();
let malformed = EncryptedLpPacket::new(ciphertext3.outer_header(), vec![]);
let dec_err = decrypt_lp_packet(malformed, &mut init_transport).unwrap_err();
assert!(matches!(dec_err, LpError::InsufficientBufferSize));
}
}