Simplify, clean up

This commit is contained in:
durch
2025-11-11 16:53:23 +01:00
parent 1f6daa7fd3
commit 6d0e4f65f2
9 changed files with 98 additions and 81 deletions
+1
View File
@@ -247,6 +247,7 @@ criterion = "0.5"
csv = "1.3.1"
ctr = "0.9.1"
cupid = "0.6.1"
curve25519-dalek = "4.1.3"
dashmap = "5.5.3"
# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore
defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" }
+16 -16
View File
@@ -1,7 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::message::{ClientHelloData, LpMessage, MessageType};
use crate::message::{ClientHelloData, EncryptedDataPayload, HandshakeData, LpMessage, MessageType};
use crate::packet::{LpHeader, LpPacket, TRAILER_LEN};
use crate::LpError;
use bytes::BytesMut;
@@ -43,18 +43,18 @@ pub fn parse_lp_packet(src: &[u8]) -> Result<LpPacket, LpError> {
if message_size != 0 {
return Err(LpError::InvalidPayloadSize {
expected: 0,
actual: message_size,
actual: message_size,
});
}
LpMessage::Busy
}
MessageType::Handshake => {
// No size validation needed here for Handshake, it's variable
LpMessage::Handshake(payload_slice.to_vec())
LpMessage::Handshake(HandshakeData(payload_slice.to_vec()))
}
MessageType::EncryptedData => {
// No size validation needed here for EncryptedData, it's variable
LpMessage::EncryptedData(payload_slice.to_vec())
LpMessage::EncryptedData(EncryptedDataPayload(payload_slice.to_vec()))
}
MessageType::ClientHello => {
// ClientHello has structured data
@@ -105,7 +105,7 @@ mod tests {
// Import standalone functions
use super::{parse_lp_packet, serialize_lp_packet};
// Keep necessary imports
use crate::message::{LpMessage, MessageType};
use crate::message::{EncryptedDataPayload, HandshakeData, LpMessage, MessageType};
use crate::packet::{LpHeader, LpPacket, TRAILER_LEN};
use crate::LpError;
use bytes::BytesMut;
@@ -120,6 +120,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 42,
counter: 123,
},
@@ -150,10 +151,11 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 42,
counter: 123,
},
message: LpMessage::Handshake(payload.clone()),
message: LpMessage::Handshake(HandshakeData(payload.clone())),
trailer: [0; TRAILER_LEN],
};
@@ -171,7 +173,7 @@ mod tests {
// Verify message type and data
match decoded.message {
LpMessage::Handshake(decoded_payload) => {
assert_eq!(decoded_payload, payload);
assert_eq!(decoded_payload, HandshakeData(payload));
}
_ => panic!("Expected Handshake message"),
}
@@ -187,10 +189,11 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 42,
counter: 123,
},
message: LpMessage::EncryptedData(payload.clone()),
message: LpMessage::EncryptedData(EncryptedDataPayload(payload.clone())),
trailer: [0; TRAILER_LEN],
};
@@ -208,7 +211,7 @@ mod tests {
// Verify message type and data
match decoded.message {
LpMessage::EncryptedData(decoded_payload) => {
assert_eq!(decoded_payload, payload);
assert_eq!(decoded_payload, EncryptedDataPayload(payload));
}
_ => panic!("Expected EncryptedData message"),
}
@@ -387,11 +390,9 @@ mod tests {
// Create ClientHelloData
let client_key = [42u8; 32];
let protocol_version = 1u8;
let salt = [99u8; 32];
let hello_data = ClientHelloData {
client_lp_public_key: client_key,
protocol_version,
salt,
};
@@ -399,6 +400,7 @@ mod tests {
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 42,
counter: 123,
},
@@ -421,7 +423,6 @@ mod tests {
match decoded.message {
LpMessage::ClientHello(decoded_data) => {
assert_eq!(decoded_data.client_lp_public_key, client_key);
assert_eq!(decoded_data.protocol_version, protocol_version);
assert_eq!(decoded_data.salt, salt);
}
_ => panic!("Expected ClientHello message"),
@@ -437,12 +438,13 @@ mod tests {
// Create ClientHelloData with fresh salt
let client_key = [7u8; 32];
let hello_data = ClientHelloData::new_with_fresh_salt(client_key, 1);
let hello_data = ClientHelloData::new_with_fresh_salt(client_key);
// Create a ClientHello message packet
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 100,
counter: 200,
},
@@ -460,7 +462,6 @@ mod tests {
match decoded.message {
LpMessage::ClientHello(decoded_data) => {
assert_eq!(decoded_data.client_lp_public_key, client_key);
assert_eq!(decoded_data.protocol_version, 1);
assert_eq!(decoded_data.salt, hello_data.salt);
// Verify timestamp can be extracted
@@ -531,13 +532,13 @@ mod tests {
let hello_data = ClientHelloData {
client_lp_public_key: [version; 32],
protocol_version: version,
salt: [version.wrapping_add(1); 32],
};
let packet = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: version as u32,
counter: version as u64,
},
@@ -550,7 +551,6 @@ mod tests {
match decoded.message {
LpMessage::ClientHello(decoded_data) => {
assert_eq!(decoded_data.protocol_version, version);
assert_eq!(decoded_data.client_lp_public_key, [version; 32]);
}
_ => panic!("Expected ClientHello message for version {}", version),
+6
View File
@@ -124,6 +124,7 @@ mod tests {
let packet1 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 42, // Matches session's sending_index assumption for this test
counter: 0,
},
@@ -152,6 +153,7 @@ mod tests {
let packet2 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 42,
counter: 0, // Same counter as before (replay)
},
@@ -181,6 +183,7 @@ mod tests {
let packet3 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 42,
counter: 1, // Incremented counter
},
@@ -241,6 +244,7 @@ mod tests {
let packet1 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: lp_id,
counter: 0,
},
@@ -273,6 +277,7 @@ mod tests {
let packet2 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: lp_id,
counter: 1,
},
@@ -300,6 +305,7 @@ mod tests {
let packet3 = LpPacket {
header: LpHeader {
protocol_version: 1,
reserved: 0,
session_id: lp_id,
counter: 0, // Replay of first packet
},
+25 -21
View File
@@ -10,8 +10,6 @@ use serde::{Deserialize, Serialize};
pub struct ClientHelloData {
/// Client's LP x25519 public key (32 bytes)
pub client_lp_public_key: [u8; 32],
/// Protocol version for future compatibility
pub protocol_version: u8,
/// Salt for PSK derivation (32 bytes: 8-byte timestamp + 24-byte nonce)
pub salt: [u8; 32],
}
@@ -24,7 +22,7 @@ impl ClientHelloData {
/// # Arguments
/// * `client_lp_public_key` - Client's x25519 public key
/// * `protocol_version` - Protocol version number
pub fn new_with_fresh_salt(client_lp_public_key: [u8; 32], protocol_version: u8) -> Self {
pub fn new_with_fresh_salt(client_lp_public_key: [u8; 32]) -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
// Generate salt: timestamp + nonce
@@ -43,7 +41,6 @@ impl ClientHelloData {
Self {
client_lp_public_key,
protocol_version,
salt,
}
}
@@ -89,11 +86,17 @@ impl MessageType {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandshakeData(pub Vec<u8>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptedDataPayload(pub Vec<u8>);
#[derive(Debug, Clone)]
pub enum LpMessage {
Busy,
Handshake(Vec<u8>),
EncryptedData(Vec<u8>),
Handshake(HandshakeData),
EncryptedData(EncryptedDataPayload),
ClientHello(ClientHelloData),
}
@@ -112,17 +115,17 @@ impl LpMessage {
pub fn payload(&self) -> &[u8] {
match self {
LpMessage::Busy => &[],
LpMessage::Handshake(payload) => payload,
LpMessage::EncryptedData(payload) => payload,
LpMessage::ClientHello(_) => &[], // Structured data, serialized in encode_content
LpMessage::Handshake(payload) => payload.0.as_slice(),
LpMessage::EncryptedData(payload) => payload.0.as_slice(),
LpMessage::ClientHello(_) => unimplemented!(), // Structured data, serialized in encode_content
}
}
pub fn is_empty(&self) -> bool {
match self {
LpMessage::Busy => true,
LpMessage::Handshake(payload) => payload.is_empty(),
LpMessage::EncryptedData(payload) => payload.is_empty(),
LpMessage::Handshake(payload) => payload.0.is_empty(),
LpMessage::EncryptedData(payload) => payload.0.is_empty(),
LpMessage::ClientHello(_) => false, // Always has data
}
}
@@ -130,8 +133,8 @@ impl LpMessage {
pub fn len(&self) -> usize {
match self {
LpMessage::Busy => 0,
LpMessage::Handshake(payload) => payload.len(),
LpMessage::EncryptedData(payload) => payload.len(),
LpMessage::Handshake(payload) => payload.0.len(),
LpMessage::EncryptedData(payload) => payload.0.len(),
LpMessage::ClientHello(_) => 65, // 32 bytes key + 1 byte version + 32 bytes salt
}
}
@@ -149,10 +152,10 @@ impl LpMessage {
match self {
LpMessage::Busy => { /* No content */ }
LpMessage::Handshake(payload) => {
dst.put_slice(payload);
dst.put_slice(&payload.0);
}
LpMessage::EncryptedData(payload) => {
dst.put_slice(payload);
dst.put_slice(&payload.0);
}
LpMessage::ClientHello(data) => {
// Serialize ClientHelloData using bincode
@@ -172,10 +175,11 @@ mod tests {
#[test]
fn encoding() {
let message = LpMessage::EncryptedData(vec![11u8; 124]);
let message = LpMessage::EncryptedData(EncryptedDataPayload(vec![11u8; 124]));
let resp_header = LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 0,
counter: 0,
};
@@ -195,7 +199,7 @@ mod tests {
// Verify correct data in message
match &packet.message {
LpMessage::EncryptedData(data) => {
assert_eq!(*data, vec![11u8; 124]);
assert_eq!(*data, EncryptedDataPayload(vec![11u8; 124]));
}
_ => panic!("Wrong message type"),
}
@@ -204,8 +208,8 @@ mod tests {
#[test]
fn test_client_hello_salt_generation() {
let client_key = [1u8; 32];
let hello1 = ClientHelloData::new_with_fresh_salt(client_key, 1);
let hello2 = ClientHelloData::new_with_fresh_salt(client_key, 1);
let hello1 = ClientHelloData::new_with_fresh_salt(client_key);
let hello2 = ClientHelloData::new_with_fresh_salt(client_key);
// Different salts should be generated
assert_ne!(hello1.salt, hello2.salt);
@@ -219,7 +223,7 @@ mod tests {
#[test]
fn test_client_hello_timestamp_extraction() {
let client_key = [2u8; 32];
let hello = ClientHelloData::new_with_fresh_salt(client_key, 1);
let hello = ClientHelloData::new_with_fresh_salt(client_key);
let timestamp = hello.extract_timestamp();
let now = std::time::SystemTime::now()
@@ -234,7 +238,7 @@ mod tests {
#[test]
fn test_client_hello_salt_format() {
let client_key = [3u8; 32];
let hello = ClientHelloData::new_with_fresh_salt(client_key, 1);
let hello = ClientHelloData::new_with_fresh_salt(client_key);
// First 8 bytes should be non-zero timestamp
let timestamp_bytes = &hello.salt[..8];
+3 -1
View File
@@ -126,7 +126,7 @@ impl LpPacket {
#[derive(Debug, Clone)]
pub struct LpHeader {
pub protocol_version: u8,
pub reserved: u16,
pub session_id: u32,
pub counter: u64,
}
@@ -139,6 +139,7 @@ impl LpHeader {
pub fn new(session_id: u32, counter: u64) -> Self {
Self {
protocol_version: 1,
reserved: 0,
session_id,
counter,
}
@@ -176,6 +177,7 @@ impl LpHeader {
Ok(LpHeader {
protocol_version,
reserved: 0,
session_id,
counter,
})
+5 -4
View File
@@ -6,6 +6,7 @@
//! This module implements session management functionality, including replay protection
//! and Noise protocol state handling.
use crate::message::{EncryptedDataPayload, HandshakeData};
use crate::noise_protocol::{NoiseError, NoiseProtocol, ReadResult};
use crate::packet::LpHeader;
use crate::replay::ReceivingKeyCounterValidator;
@@ -178,7 +179,7 @@ impl LpSession {
let mut noise_state = self.noise_state.lock();
if let Some(message) = noise_state.get_bytes_to_send() {
match message {
Ok(message) => Some(Ok(LpMessage::Handshake(message))),
Ok(message) => Some(Ok(LpMessage::Handshake(HandshakeData(message)))),
Err(e) => Some(Err(LpError::NoiseError(e))),
}
} else {
@@ -203,7 +204,7 @@ impl LpSession {
let mut noise_state = self.noise_state.lock();
match message {
LpMessage::Handshake(payload) => {
LpMessage::Handshake(HandshakeData(payload)) => {
// The sans-io NoiseProtocol::read_message expects only the payload.
noise_state.read_message(payload)
}
@@ -235,7 +236,7 @@ impl LpSession {
return Err(NoiseError::IncorrectStateError);
}
let payload = noise_state.write_message(payload)?;
Ok(LpMessage::EncryptedData(payload))
Ok(LpMessage::EncryptedData(EncryptedDataPayload(payload)))
}
/// Decrypts an incoming Noise message containing application data.
@@ -612,7 +613,7 @@ mod tests {
// Attempt to decrypt before handshake (using dummy ciphertext)
let dummy_ciphertext = vec![0u8; 32];
let result_decrypt =
initiator_session.decrypt_data(&LpMessage::EncryptedData(dummy_ciphertext));
initiator_session.decrypt_data(&LpMessage::EncryptedData(EncryptedDataPayload(dummy_ciphertext)));
assert!(result_decrypt.is_err());
match result_decrypt.unwrap_err() {
NoiseError::IncorrectStateError => {} // Expected error
+2 -1
View File
@@ -21,6 +21,7 @@ mod tests {
// Create the header
let header = LpHeader {
protocol_version,
reserved: 0u16, // reserved
session_id,
counter,
};
@@ -307,7 +308,7 @@ mod tests {
1,
lp_id,
counter_b,
LpMessage::EncryptedData(plaintext_b_to_a.to_vec()), // Using plaintext here, but content doesn't matter for replay check
LpMessage::EncryptedData(crate::message::EncryptedDataPayload(plaintext_b_to_a.to_vec())), // Using plaintext here, but content doesn't matter for replay check
);
let mut encoded_data_b_to_a_replay = BytesMut::new();
serialize_lp_packet(&message_b_to_a_replay, &mut encoded_data_b_to_a_replay)
+40 -37
View File
@@ -112,16 +112,22 @@ impl LpConnectionHandler {
// Derive LP keypair from gateway's ed25519 identity using proper conversion
// This creates a valid x25519 keypair for ECDH operations in Noise protocol
let x25519_private = self.state.local_identity.private_key().to_x25519();
let x25519_public = self.state.local_identity.public_key().to_x25519()
.map_err(|e| GatewayError::LpHandshakeError(
format!("Failed to convert ed25519 public key to x25519: {}", e)
))?;
let x25519_public = self
.state
.local_identity
.public_key()
.to_x25519()
.map_err(|e| {
GatewayError::LpHandshakeError(format!(
"Failed to convert ed25519 public key to x25519: {}",
e
))
})?;
let lp_private = LpPrivateKey::from_bytes(x25519_private.as_bytes());
let lp_public = PublicKey::from_bytes(x25519_public.as_bytes())
.map_err(|e| GatewayError::LpHandshakeError(
format!("Failed to create LP public key: {}", e)
))?;
let lp_public = PublicKey::from_bytes(x25519_public.as_bytes()).map_err(|e| {
GatewayError::LpHandshakeError(format!("Failed to create LP public key: {}", e))
})?;
let gateway_keypair = Keypair::from_keys(lp_private, lp_public);
@@ -279,14 +285,6 @@ impl LpConnectionHandler {
// Verify it's a ClientHello message
match packet.message() {
LpMessage::ClientHello(hello_data) => {
// Validate protocol version (currently only v1)
if hello_data.protocol_version != 1 {
return Err(GatewayError::LpProtocolError(format!(
"Unsupported protocol version: {}",
hello_data.protocol_version
)));
}
// Extract and validate timestamp (nym-110: replay protection)
let timestamp = hello_data.extract_timestamp();
Self::validate_timestamp(timestamp, self.state.lp_config.timestamp_tolerance_secs)?;
@@ -341,11 +339,9 @@ impl LpConnectionHandler {
}
// Decrypt the packet payload using the established session
let decrypted_bytes = session
.decrypt_data(packet.message())
.map_err(|e| {
GatewayError::LpProtocolError(format!("Failed to decrypt registration request: {}", e))
})?;
let decrypted_bytes = session.decrypt_data(packet.message()).map_err(|e| {
GatewayError::LpProtocolError(format!("Failed to decrypt registration request: {}", e))
})?;
// Deserialize the decrypted bytes into LpRegistrationRequest
bincode::deserialize(&decrypted_bytes).map_err(|e| {
@@ -368,9 +364,9 @@ impl LpConnectionHandler {
})?;
// Encrypt data first (this increments Noise internal counter)
let encrypted_message = session.encrypt_data(&data).map_err(|e| {
GatewayError::LpProtocolError(format!("Failed to encrypt data: {}", e))
})?;
let encrypted_message = session
.encrypt_data(&data)
.map_err(|e| GatewayError::LpProtocolError(format!("Failed to encrypt data: {}", e)))?;
// Create LP packet with encrypted message (this increments LP protocol counter)
let packet = session.next_packet(encrypted_message).map_err(|e| {
@@ -467,7 +463,10 @@ impl LpConnectionHandler {
"lp_connection_bytes_received_total",
self.stats.bytes_received as i64
);
inc_by!("lp_connection_bytes_sent_total", self.stats.bytes_sent as i64);
inc_by!(
"lp_connection_bytes_sent_total",
self.stats.bytes_sent as i64
);
// Track completion type
if graceful {
@@ -486,7 +485,7 @@ mod tests {
use bytes::BytesMut;
use nym_lp::codec::{parse_lp_packet, serialize_lp_packet};
use nym_lp::keypair::Keypair;
use nym_lp::message::{ClientHelloData, LpMessage};
use nym_lp::message::{ClientHelloData, EncryptedDataPayload, HandshakeData, LpMessage};
use nym_lp::packet::{LpHeader, LpPacket};
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -670,6 +669,7 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 42,
counter: 0,
},
@@ -732,6 +732,7 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 99,
counter: 5,
},
@@ -771,10 +772,11 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 100,
counter: 10,
},
LpMessage::Handshake(handshake_data),
LpMessage::Handshake(HandshakeData(handshake_data)),
);
handler.send_lp_packet(&packet).await
});
@@ -788,7 +790,7 @@ mod tests {
assert_eq!(received.header().session_id, 100);
assert_eq!(received.header().counter, 10);
match received.message() {
LpMessage::Handshake(data) => assert_eq!(data, &expected_data),
LpMessage::Handshake(data) => assert_eq!(data, &HandshakeData(expected_data)),
_ => panic!("Expected Handshake message"),
}
}
@@ -811,10 +813,11 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 200,
counter: 20,
},
LpMessage::EncryptedData(encrypted_payload),
LpMessage::EncryptedData(EncryptedDataPayload(encrypted_payload)),
);
handler.send_lp_packet(&packet).await
});
@@ -828,7 +831,7 @@ mod tests {
assert_eq!(received.header().session_id, 200);
assert_eq!(received.header().counter, 20);
match received.message() {
LpMessage::EncryptedData(data) => assert_eq!(data, &expected_payload),
LpMessage::EncryptedData(data) => assert_eq!(data, &EncryptedDataPayload(expected_payload)),
_ => panic!("Expected EncryptedData message"),
}
}
@@ -842,7 +845,7 @@ mod tests {
let addr = listener.local_addr().unwrap();
let client_key = [7u8; 32];
let hello_data = ClientHelloData::new_with_fresh_salt(client_key, 1);
let hello_data = ClientHelloData::new_with_fresh_salt(client_key);
let expected_salt = hello_data.salt; // Clone salt before moving hello_data
let server_task = tokio::spawn(async move {
@@ -853,6 +856,7 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 300,
counter: 30,
},
@@ -872,7 +876,6 @@ mod tests {
match received.message() {
LpMessage::ClientHello(data) => {
assert_eq!(data.client_lp_public_key, client_key);
assert_eq!(data.protocol_version, 1);
assert_eq!(data.salt, expected_salt);
}
_ => panic!("Expected ClientHello message"),
@@ -899,13 +902,12 @@ mod tests {
// Create and send valid ClientHello
let client_keypair = Keypair::default();
let hello_data = ClientHelloData::new_with_fresh_salt(
client_keypair.public_key().to_bytes(),
1, // protocol version
);
let hello_data =
ClientHelloData::new_with_fresh_salt(client_keypair.public_key().to_bytes());
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 0,
counter: 0,
},
@@ -944,7 +946,7 @@ mod tests {
// Create ClientHello with old timestamp
let client_keypair = Keypair::default();
let mut hello_data =
ClientHelloData::new_with_fresh_salt(client_keypair.public_key().to_bytes(), 1);
ClientHelloData::new_with_fresh_salt(client_keypair.public_key().to_bytes());
// Manually set timestamp to be very old (100 seconds ago)
let old_timestamp = SystemTime::now()
@@ -957,6 +959,7 @@ mod tests {
let packet = LpPacket::new(
LpHeader {
protocol_version: 1,
reserved: 0,
session_id: 0,
counter: 0,
},
@@ -235,7 +235,6 @@ impl LpRegistrationClient {
// Step 1: Generate ClientHelloData with fresh salt (timestamp + nonce)
let client_hello_data = nym_lp::ClientHelloData::new_with_fresh_salt(
self.local_keypair.public_key().to_bytes(),
1, // protocol_version
);
let salt = client_hello_data.salt;