Change MessageType from u16 to u32
Breaking wire protocol change: MessageType field increased from 2 bytes to 4 bytes in LP packets. This future-proofs the message type space and aligns with other u32 fields. Changes: - message.rs: #[repr(u32)], from_u32(), to_u32() - error.rs: InvalidMessageType(u32) - codec.rs: All serialization/deserialization updated to 4-byte msg_type - Cleartext parsing: inner_bytes[4..8], content at [8..] - AEAD parsing: decrypted[4..8], content at [8..] - Serialization: 4 bytes for message type
This commit is contained in:
committed by
Jędrzej Stuczyński
parent
7ebacde1fc
commit
9eb8cc2de8
+33
-27
@@ -89,10 +89,10 @@ fn build_nonce(counter: u64) -> [u8; 12] {
|
||||
/// Used when decrypting outer-encrypted packets where the message type
|
||||
/// was encrypted along with the content.
|
||||
fn parse_message_from_type_and_content(
|
||||
msg_type_raw: u16,
|
||||
msg_type_raw: u32,
|
||||
content: &[u8],
|
||||
) -> Result<LpMessage, LpError> {
|
||||
let message_type = MessageType::from_u16(msg_type_raw)
|
||||
let message_type = MessageType::from_u32(msg_type_raw)
|
||||
.ok_or_else(|| LpError::invalid_message_type(msg_type_raw))?;
|
||||
|
||||
match message_type {
|
||||
@@ -195,7 +195,7 @@ pub fn parse_lp_header_only(src: &[u8]) -> Result<OuterHeader, LpError> {
|
||||
///
|
||||
/// Both cleartext and encrypted packets have the same structure:
|
||||
/// - Outer header (12B): receiver_idx(4) + counter(8) - always cleartext
|
||||
/// - Inner payload: proto(1) + reserved(3) + msg_type(2) + content - cleartext or encrypted
|
||||
/// - Inner payload: proto(1) + reserved(3) + msg_type(4) + content - cleartext or encrypted
|
||||
/// - Trailer (16B): zeros (cleartext) or AEAD tag (encrypted)
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -228,15 +228,20 @@ pub fn parse_lp_packet(src: &[u8], outer_key: Option<&OuterAeadKey>) -> Result<L
|
||||
match outer_key {
|
||||
None => {
|
||||
// Cleartext mode - parse inner directly
|
||||
// Inner format: proto(1) + reserved(3) + msg_type(2) + content
|
||||
if inner_bytes.len() < INNER_PREFIX_SIZE + 2 {
|
||||
// Inner format: proto(1) + reserved(3) + msg_type(4) + content
|
||||
if inner_bytes.len() < INNER_PREFIX_SIZE + 4 {
|
||||
return Err(LpError::InsufficientBufferSize);
|
||||
}
|
||||
|
||||
let protocol_version = inner_bytes[0];
|
||||
// reserved bytes [1..4] are ignored
|
||||
let msg_type = u16::from_le_bytes([inner_bytes[4], inner_bytes[5]]);
|
||||
let message_content = &inner_bytes[6..];
|
||||
let msg_type = u32::from_le_bytes([
|
||||
inner_bytes[4],
|
||||
inner_bytes[5],
|
||||
inner_bytes[6],
|
||||
inner_bytes[7],
|
||||
]);
|
||||
let message_content = &inner_bytes[8..];
|
||||
|
||||
let header = LpHeader {
|
||||
protocol_version,
|
||||
@@ -271,15 +276,16 @@ pub fn parse_lp_packet(src: &[u8], outer_key: Option<&OuterAeadKey>) -> Result<L
|
||||
.decrypt_in_place_detached(Nonce::from_slice(&nonce), aad, &mut decrypted, tag)
|
||||
.map_err(|_| LpError::AeadTagMismatch)?;
|
||||
|
||||
// Decrypted format: proto(1) + reserved(3) + msg_type(2) + content
|
||||
if decrypted.len() < INNER_PREFIX_SIZE + 2 {
|
||||
// Decrypted format: proto(1) + reserved(3) + msg_type(4) + content
|
||||
if decrypted.len() < INNER_PREFIX_SIZE + 4 {
|
||||
return Err(LpError::InsufficientBufferSize);
|
||||
}
|
||||
|
||||
let protocol_version = decrypted[0];
|
||||
// reserved bytes [1..4] are ignored
|
||||
let msg_type = u16::from_le_bytes([decrypted[4], decrypted[5]]);
|
||||
let message_content = &decrypted[6..];
|
||||
let msg_type =
|
||||
u32::from_le_bytes([decrypted[4], decrypted[5], decrypted[6], decrypted[7]]);
|
||||
let message_content = &decrypted[8..];
|
||||
|
||||
let header = LpHeader {
|
||||
protocol_version,
|
||||
@@ -305,7 +311,7 @@ pub fn parse_lp_packet(src: &[u8], outer_key: Option<&OuterAeadKey>) -> Result<L
|
||||
///
|
||||
/// Both cleartext and encrypted packets have the same structure:
|
||||
/// - Outer header (12B): receiver_idx(4) + counter(8) - always cleartext
|
||||
/// - Inner payload: proto(1) + reserved(3) + msg_type(2) + content - cleartext or encrypted
|
||||
/// - Inner payload: proto(1) + reserved(3) + msg_type(4) + content - cleartext or encrypted
|
||||
/// - Trailer (16B): zeros (cleartext) or AEAD tag (encrypted)
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -317,8 +323,8 @@ pub fn serialize_lp_packet(
|
||||
dst: &mut BytesMut,
|
||||
outer_key: Option<&OuterAeadKey>,
|
||||
) -> Result<(), LpError> {
|
||||
// Total size: outer_header(12) + inner_prefix(4) + msg_type(2) + content + trailer(16)
|
||||
let total_size = OUTER_HEADER_SIZE + INNER_PREFIX_SIZE + 2 + item.message.len() + TRAILER_LEN;
|
||||
// Total size: outer_header(12) + inner_prefix(4) + msg_type(4) + content + trailer(16)
|
||||
let total_size = OUTER_HEADER_SIZE + INNER_PREFIX_SIZE + 4 + item.message.len() + TRAILER_LEN;
|
||||
dst.reserve(total_size);
|
||||
|
||||
// 1. Write outer header (always cleartext) - 12 bytes
|
||||
@@ -332,8 +338,8 @@ pub fn serialize_lp_packet(
|
||||
dst.put_u8(item.header.protocol_version);
|
||||
dst.put_slice(&[0, 0, 0]); // reserved
|
||||
|
||||
// 3. Write message type (2B) + content
|
||||
dst.put_slice(&(item.message.typ() as u16).to_le_bytes());
|
||||
// 3. Write message type (4B) + content
|
||||
dst.put_slice(&(item.message.typ() as u32).to_le_bytes());
|
||||
item.message.encode_content(dst);
|
||||
|
||||
// 4. Write zeros trailer
|
||||
@@ -346,11 +352,11 @@ pub fn serialize_lp_packet(
|
||||
// AAD is the outer header (first 12 bytes)
|
||||
let aad = outer_header.encode();
|
||||
|
||||
// 2. Build plaintext: proto(1) + reserved(3) + msg_type(2) + content
|
||||
// 2. Build plaintext: proto(1) + reserved(3) + msg_type(4) + content
|
||||
let mut plaintext = BytesMut::new();
|
||||
plaintext.put_u8(item.header.protocol_version);
|
||||
plaintext.put_slice(&[0, 0, 0]); // reserved
|
||||
plaintext.put_slice(&(item.message.typ() as u16).to_le_bytes());
|
||||
plaintext.put_slice(&(item.message.typ() as u32).to_le_bytes());
|
||||
item.message.encode_content(&mut plaintext);
|
||||
|
||||
// 3. Copy plaintext to dst for in-place encryption
|
||||
@@ -380,7 +386,7 @@ pub fn serialize_lp_packet(
|
||||
|
||||
// Add a new error variant for invalid message types (Moved from previous impl LpError block)
|
||||
impl LpError {
|
||||
pub fn invalid_message_type(message_type: u16) -> Self {
|
||||
pub fn invalid_message_type(message_type: u32) -> Self {
|
||||
LpError::InvalidMessageType(message_type)
|
||||
}
|
||||
}
|
||||
@@ -549,7 +555,7 @@ mod tests {
|
||||
buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved
|
||||
buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index
|
||||
buf.extend_from_slice(&123u64.to_le_bytes()); // Counter
|
||||
buf.extend_from_slice(&MessageType::Handshake.to_u16().to_le_bytes()); // Handshake type
|
||||
buf.extend_from_slice(&MessageType::Handshake.to_u32().to_le_bytes()); // Handshake type
|
||||
buf.extend_from_slice(&[42; 40]); // 40 bytes of payload data
|
||||
|
||||
// Buffer length = 16 + 2 + 40 = 58. Min size = 16 + 2 + 16 = 34.
|
||||
@@ -569,7 +575,7 @@ mod tests {
|
||||
buf_too_short.extend_from_slice(&42u32.to_le_bytes()); // receiver_idx (outer header)
|
||||
buf_too_short.extend_from_slice(&123u64.to_le_bytes()); // counter (outer header)
|
||||
buf_too_short.extend_from_slice(&[1, 0, 0, 0]); // version + reserved (inner prefix)
|
||||
buf_too_short.extend_from_slice(&MessageType::Handshake.to_u16().to_le_bytes()); // msg type
|
||||
buf_too_short.extend_from_slice(&MessageType::Handshake.to_u32().to_le_bytes()); // msg type
|
||||
// No payload, no trailer. Length = 12+4+2=18. Min size = 34.
|
||||
let result_too_short = parse_lp_packet(&buf_too_short, None);
|
||||
assert!(result_too_short.is_err());
|
||||
@@ -583,7 +589,7 @@ mod tests {
|
||||
buf_partial_trailer.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved
|
||||
buf_partial_trailer.extend_from_slice(&42u32.to_le_bytes()); // Sender index
|
||||
buf_partial_trailer.extend_from_slice(&123u64.to_le_bytes()); // Counter
|
||||
buf_partial_trailer.extend_from_slice(&MessageType::Handshake.to_u16().to_le_bytes()); // Handshake type
|
||||
buf_partial_trailer.extend_from_slice(&MessageType::Handshake.to_u32().to_le_bytes()); // Handshake type
|
||||
let payload = vec![42u8; 20]; // Assume 20 byte payload
|
||||
buf_partial_trailer.extend_from_slice(&payload);
|
||||
buf_partial_trailer.extend_from_slice(&[0; TRAILER_LEN - 1]); // Missing last byte of trailer
|
||||
@@ -605,7 +611,7 @@ mod tests {
|
||||
buf_too_short.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved
|
||||
buf_too_short.extend_from_slice(&42u32.to_le_bytes()); // Sender index
|
||||
buf_too_short.extend_from_slice(&123u64.to_le_bytes()); // Counter
|
||||
buf_too_short.extend_from_slice(&MessageType::Busy.to_u16().to_le_bytes()); // Type
|
||||
buf_too_short.extend_from_slice(&MessageType::Busy.to_u32().to_le_bytes()); // Type
|
||||
buf_too_short.extend_from_slice(&[0; TRAILER_LEN - 1]); // Missing last byte of trailer
|
||||
// Length = 16 + 2 + 15 = 33. Min Size = 34.
|
||||
let result_too_short = parse_lp_packet(&buf_too_short, None);
|
||||
@@ -649,7 +655,7 @@ mod tests {
|
||||
buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved
|
||||
buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index
|
||||
buf.extend_from_slice(&123u64.to_le_bytes()); // Counter
|
||||
buf.extend_from_slice(&MessageType::Busy.to_u16().to_le_bytes()); // Busy type
|
||||
buf.extend_from_slice(&MessageType::Busy.to_u32().to_le_bytes()); // Busy type
|
||||
buf.extend_from_slice(&[42; 1]); // <<< Invalid 1-byte payload for Busy
|
||||
buf.extend_from_slice(&[0; TRAILER_LEN]); // Trailer
|
||||
|
||||
@@ -778,7 +784,7 @@ mod tests {
|
||||
buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved
|
||||
buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index
|
||||
buf.extend_from_slice(&123u64.to_le_bytes()); // Counter
|
||||
buf.extend_from_slice(&MessageType::ClientHello.to_u16().to_le_bytes()); // ClientHello type
|
||||
buf.extend_from_slice(&MessageType::ClientHello.to_u32().to_le_bytes()); // ClientHello type
|
||||
|
||||
// Add malformed bincode data (random bytes that won't deserialize to ClientHelloData)
|
||||
buf.extend_from_slice(&[0xFF; 50]); // Invalid bincode data
|
||||
@@ -801,7 +807,7 @@ mod tests {
|
||||
buf.extend_from_slice(&[1, 0, 0, 0]); // Version + reserved
|
||||
buf.extend_from_slice(&42u32.to_le_bytes()); // Sender index
|
||||
buf.extend_from_slice(&123u64.to_le_bytes()); // Counter
|
||||
buf.extend_from_slice(&MessageType::ClientHello.to_u16().to_le_bytes()); // ClientHello type
|
||||
buf.extend_from_slice(&MessageType::ClientHello.to_u32().to_le_bytes()); // ClientHello type
|
||||
|
||||
// Add incomplete bincode data (only partial ClientHelloData)
|
||||
buf.extend_from_slice(&[0; 20]); // Too few bytes for full ClientHelloData
|
||||
@@ -1296,7 +1302,7 @@ mod tests {
|
||||
buf.extend_from_slice(&42u32.to_le_bytes()); // receiver_idx
|
||||
buf.extend_from_slice(&123u64.to_le_bytes()); // counter
|
||||
buf.extend_from_slice(&[1, 0, 0, 0]); // version + reserved
|
||||
buf.extend_from_slice(&MessageType::SubsessionRequest.to_u16().to_le_bytes());
|
||||
buf.extend_from_slice(&MessageType::SubsessionRequest.to_u32().to_le_bytes());
|
||||
buf.extend_from_slice(&[0xFF]); // Invalid payload for SubsessionRequest
|
||||
buf.extend_from_slice(&[0; TRAILER_LEN]);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
//! Configuration for LP protocol.
|
||||
//!
|
||||
//! AIDEV-NOTE: LP security stack = KKT (key fetch) → PSQ (PQ PSK) → Noise (transport).
|
||||
//! LP security stack = KKT (key fetch) → PSQ (PQ PSK) → Noise (transport).
|
||||
//! KEM algorithm selection affects only PSQ layer. Noise always uses X25519 DH.
|
||||
//! Migration to PQ KEMs (MlKem768, XWing) requires only config change.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ pub enum LpError {
|
||||
InvalidPacketFormat(String),
|
||||
|
||||
#[error("Invalid message type: {0}")]
|
||||
InvalidMessageType(u16),
|
||||
InvalidMessageType(u32),
|
||||
|
||||
#[error("Payload too large: {0}")]
|
||||
PayloadTooLarge(usize),
|
||||
|
||||
@@ -68,7 +68,7 @@ impl ClientHelloData {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u16)]
|
||||
#[repr(u32)]
|
||||
pub enum MessageType {
|
||||
Busy = 0x0000,
|
||||
Handshake = 0x0001,
|
||||
@@ -94,12 +94,12 @@ pub enum MessageType {
|
||||
}
|
||||
|
||||
impl MessageType {
|
||||
pub(crate) fn from_u16(value: u16) -> Option<Self> {
|
||||
pub(crate) fn from_u32(value: u32) -> Option<Self> {
|
||||
MessageType::try_from(value).ok()
|
||||
}
|
||||
|
||||
pub fn to_u16(&self) -> u16 {
|
||||
u16::from(*self)
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
u32::from(*self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! This module implements identity-bound PSK derivation where both client and gateway
|
||||
//! derive the same PSK from their LP keypairs.
|
||||
//!
|
||||
//! AIDEV-NOTE: PSQ is embedded in Noise (not separate protocol) because:
|
||||
//! PSQ is embedded in Noise (not separate protocol) because:
|
||||
//! 1. Single round-trip: PSQ ciphertext piggybacks on Noise handshake messages
|
||||
//! 2. PSK binding: Noise XKpsk3 pattern authenticates both ECDH and PSQ-derived PSK
|
||||
//! 3. Simpler state machine: No separate PSQ negotiation phase needed
|
||||
@@ -146,7 +146,7 @@ pub fn derive_psk_with_psq_initiator(
|
||||
let ecdh_secret = local_x25519_private.diffie_hellman(remote_x25519_public);
|
||||
|
||||
// Step 2: PSQ encapsulation for post-quantum security
|
||||
// AIDEV-NOTE: KEM algorithm migration path:
|
||||
// KEM algorithm migration path:
|
||||
// - X25519: Current default for testing/compatibility (no HNDL resistance)
|
||||
// - MlKem768: Future production default (NIST PQ Level 3, HNDL resistant)
|
||||
// - XWing: Maximum security option (hybrid X25519 + ML-KEM)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
//! Lewes Protocol State Machine for managing connection lifecycle.
|
||||
//!
|
||||
//! AIDEV-NOTE: LP protocol flow (KKT → PSQ → Noise):
|
||||
//! LP protocol flow (KKT → PSQ → Noise):
|
||||
//! 1. KKTExchange: Client requests gateway's KEM public key (signed for MITM protection)
|
||||
//! 2. Handshaking: Noise XKpsk3 with PSQ-derived PSK embedded in handshake messages
|
||||
//! - PSQ ciphertext piggybacked on ClientHello (no extra round-trip)
|
||||
|
||||
@@ -453,7 +453,12 @@ impl LpConnectionHandler {
|
||||
// Get outer key before releasing borrow
|
||||
let outer_key = state_machine
|
||||
.session()
|
||||
.map_err(|e| GatewayError::LpProtocolError(format!("Session unavailable after processing: {}", e)))?
|
||||
.map_err(|e| {
|
||||
GatewayError::LpProtocolError(format!(
|
||||
"Session unavailable after processing: {}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.outer_aead_key();
|
||||
drop(state_entry);
|
||||
|
||||
@@ -1130,8 +1135,9 @@ mod tests {
|
||||
timestamp_tolerance_secs: 30,
|
||||
..Default::default()
|
||||
};
|
||||
let forward_semaphore =
|
||||
Arc::new(tokio::sync::Semaphore::new(lp_config.max_concurrent_forwards));
|
||||
let forward_semaphore = Arc::new(tokio::sync::Semaphore::new(
|
||||
lp_config.max_concurrent_forwards,
|
||||
));
|
||||
|
||||
LpHandlerState {
|
||||
lp_config,
|
||||
|
||||
@@ -361,7 +361,7 @@ pub struct LpHandlerState {
|
||||
/// Prevents file descriptor exhaustion when forwarding LP packets during
|
||||
/// telescope setup. When at capacity, forward requests return an error
|
||||
/// so clients can choose a different gateway.
|
||||
// AIDEV-NOTE: Connection limiting (not pooling) chosen for forward requests.
|
||||
// Connection limiting (not pooling) chosen for forward requests.
|
||||
//
|
||||
// Why not connection pooling?
|
||||
// 1. Forwarding is one-time per telescope setup (handshake only), not ongoing traffic.
|
||||
|
||||
@@ -636,7 +636,10 @@ impl NestedLpSession {
|
||||
///
|
||||
/// Returns `None` during early handshake before PSK derivation.
|
||||
fn get_recv_key(state_machine: &LpStateMachine) -> Option<OuterAeadKey> {
|
||||
state_machine.session().ok().and_then(|s| s.outer_aead_key())
|
||||
state_machine
|
||||
.session()
|
||||
.ok()
|
||||
.and_then(|s| s.outer_aead_key())
|
||||
}
|
||||
|
||||
/// Serializes an LP packet to bytes.
|
||||
|
||||
Reference in New Issue
Block a user