mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-12 09:33:23 +00:00
Replace Noise IK with Noise XK at the FSP session layer
The session-layer handshake now uses the 3-message XK pattern instead of the 2-message IK pattern, providing stronger initiator identity hiding. The initiator static key is deferred to msg3 and encrypted under the es+ee DH chain, so eavesdroppers cannot identify the initiator from the handshake. XK pattern: -> e, es (msg1) / <- e, ee + epoch (msg2) / -> s, se + epoch (msg3) Key changes: - Add XK handshake methods alongside existing IK methods in noise module - Add SessionMsg3 wire format and FSP_PHASE_MSG3 (0x03) prefix - Replace Responding state with AwaitingMsg3 in session state machine - Rewrite session handlers: handle_session_setup defers identity to msg3, handle_session_ack processes msg2 and sends msg3, new handle_session_msg3 completes the responder handshake and registers identity - Link-layer (FMP) continues to use Noise IK unchanged - Add comprehensive XK unit tests and update all integration tests
This commit is contained in:
+3
-3
@@ -38,9 +38,9 @@ pub use filter::FilterAnnounce;
|
||||
pub use discovery::{LookupRequest, LookupResponse};
|
||||
pub use session::{
|
||||
CoordsRequired, FspFlags, FspInnerFlags, MtuExceeded, PathBroken, PathMtuNotification,
|
||||
SessionAck, SessionFlags, SessionMessageType, SessionReceiverReport, SessionSenderReport,
|
||||
SessionSetup, COORDS_REQUIRED_SIZE, MTU_EXCEEDED_SIZE, PATH_MTU_NOTIFICATION_SIZE,
|
||||
SESSION_RECEIVER_REPORT_SIZE, SESSION_SENDER_REPORT_SIZE,
|
||||
SessionAck, SessionFlags, SessionMessageType, SessionMsg3, SessionReceiverReport,
|
||||
SessionSenderReport, SessionSetup, COORDS_REQUIRED_SIZE, MTU_EXCEEDED_SIZE,
|
||||
PATH_MTU_NOTIFICATION_SIZE, SESSION_RECEIVER_REPORT_SIZE, SESSION_SENDER_REPORT_SIZE,
|
||||
};
|
||||
pub(crate) use session::{coords_wire_size, decode_optional_coords, encode_coords};
|
||||
|
||||
|
||||
@@ -563,6 +563,97 @@ impl SessionAck {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Msg3 (XK Handshake Message 3)
|
||||
// ============================================================================
|
||||
|
||||
/// XK handshake message 3 (initiator -> responder).
|
||||
///
|
||||
/// Carries the initiator's encrypted static key and epoch. Sent by the
|
||||
/// initiator after receiving msg2. The responder learns the initiator's
|
||||
/// identity from this message.
|
||||
///
|
||||
/// ## Wire Format
|
||||
///
|
||||
/// | Offset | Field | Size | Description |
|
||||
/// |--------|------------------|---------|-------------------------------------|
|
||||
/// | 0 | flags | 1 byte | Reserved |
|
||||
/// | 1 | handshake_len | 2 bytes | u16 LE, Noise payload length |
|
||||
/// | 3 | handshake_payload| variable| Noise XK msg3 (73 bytes typical) |
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SessionMsg3 {
|
||||
/// Reserved flags byte.
|
||||
pub flags: u8,
|
||||
/// Noise XK handshake message 3.
|
||||
pub handshake_payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SessionMsg3 {
|
||||
/// Create a new SessionMsg3 with the given handshake payload.
|
||||
pub fn new(handshake_payload: Vec<u8>) -> Self {
|
||||
Self {
|
||||
flags: 0,
|
||||
handshake_payload,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode as wire format (4-byte FSP prefix + flags + handshake).
|
||||
///
|
||||
/// The 4-byte prefix: `[ver_phase:1][flags:1][payload_len:2 LE]`
|
||||
/// where ver_phase = 0x03 (version 0, phase MSG3).
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
// Build body first to compute payload_len
|
||||
let mut body = Vec::new();
|
||||
body.push(self.flags);
|
||||
let hs_len = self.handshake_payload.len() as u16;
|
||||
body.extend_from_slice(&hs_len.to_le_bytes());
|
||||
body.extend_from_slice(&self.handshake_payload);
|
||||
|
||||
// Prepend 4-byte FSP common prefix
|
||||
let payload_len = body.len() as u16;
|
||||
let mut buf = Vec::with_capacity(4 + body.len());
|
||||
buf.push(0x03); // version 0, phase 0x3 (MSG3)
|
||||
buf.push(0x00); // flags (must be zero for handshake)
|
||||
buf.extend_from_slice(&payload_len.to_le_bytes());
|
||||
buf.extend_from_slice(&body);
|
||||
buf
|
||||
}
|
||||
|
||||
/// Decode from wire format (after 4-byte FSP prefix has been consumed).
|
||||
pub fn decode(payload: &[u8]) -> Result<Self, ProtocolError> {
|
||||
if payload.is_empty() {
|
||||
return Err(ProtocolError::MessageTooShort {
|
||||
expected: 1,
|
||||
got: 0,
|
||||
});
|
||||
}
|
||||
let flags = payload[0];
|
||||
let mut offset = 1;
|
||||
|
||||
if payload.len() < offset + 2 {
|
||||
return Err(ProtocolError::MessageTooShort {
|
||||
expected: offset + 2,
|
||||
got: payload.len(),
|
||||
});
|
||||
}
|
||||
let hs_len = u16::from_le_bytes([payload[offset], payload[offset + 1]]) as usize;
|
||||
offset += 2;
|
||||
|
||||
if payload.len() < offset + hs_len {
|
||||
return Err(ProtocolError::MessageTooShort {
|
||||
expected: offset + hs_len,
|
||||
got: payload.len(),
|
||||
});
|
||||
}
|
||||
let handshake_payload = payload[offset..offset + hs_len].to_vec();
|
||||
|
||||
Ok(Self {
|
||||
flags,
|
||||
handshake_payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session-Layer MMP Reports
|
||||
// ============================================================================
|
||||
@@ -1549,4 +1640,38 @@ mod tests {
|
||||
fn test_mtu_exceeded_display() {
|
||||
assert_eq!(format!("{}", SessionMessageType::MtuExceeded), "MtuExceeded");
|
||||
}
|
||||
|
||||
// ===== SessionMsg3 Tests =====
|
||||
|
||||
#[test]
|
||||
fn test_session_msg3_encode_decode() {
|
||||
let handshake = vec![0xCC; 73]; // typical XK msg3
|
||||
let msg3 = SessionMsg3::new(handshake.clone());
|
||||
|
||||
let encoded = msg3.encode();
|
||||
// Verify FSP prefix: ver_phase=0x03 (version 0, phase MSG3)
|
||||
assert_eq!(encoded[0], 0x03);
|
||||
assert_eq!(encoded[1], 0x00); // flags = 0 for handshake
|
||||
let payload_len = u16::from_le_bytes([encoded[2], encoded[3]]);
|
||||
assert_eq!(payload_len as usize, encoded.len() - 4);
|
||||
|
||||
// Decode (skip 4-byte FSP prefix)
|
||||
let decoded = SessionMsg3::decode(&encoded[4..]).unwrap();
|
||||
assert_eq!(decoded.flags, 0);
|
||||
assert_eq!(decoded.handshake_payload, handshake);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_session_msg3_decode_too_short() {
|
||||
assert!(SessionMsg3::decode(&[]).is_err());
|
||||
assert!(SessionMsg3::decode(&[0x00]).is_err()); // flags only, no hs_len
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_session_msg3_empty_handshake() {
|
||||
let msg3 = SessionMsg3::new(vec![]);
|
||||
let encoded = msg3.encode();
|
||||
let decoded = SessionMsg3::decode(&encoded[4..]).unwrap();
|
||||
assert!(decoded.handshake_payload.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user