mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-10 00:26:59 +00:00
Switch FMP handshake from Noise IK to XX with version negotiation
Replace the 2-message IK handshake with a 3-message XX handshake for FMP link establishment. XX requires no prior knowledge of the peer's static key — both identities are revealed during the handshake (responder in msg2, initiator in msg3). This is the foundation for the forklift upgrade that enables rolling protocol upgrades. Changes: - Noise XX state machine alongside IK/XK (8 unit tests) - Protocol negotiation payload codec: format byte, packed version min/max, 64-bit feature bitfield, TLV extensions (11 unit tests) - FMP wire format version 0→1, msg3 header/builder, TCP stream framing - FMP handshake switched to XX: PeerConnection 3-message flow, handle_msg1 simplified (no identity), handle_msg2 sends msg3 and promotes initiator, new handle_msg3 promotes responder with restart/rekey/cross-connection detection - Rekey handshake switched to XX with negotiation payload hash chain fix (decrypt-and-discard in complete_rekey_msg2/msg3) - Negotiation payload in msg2/msg3 (FMP version [1,1], features=0) - Debug logging for handshake promotion paths - Integration test convergence timeouts adjusted for extra round-trip Squashed commits: - Add Noise XX state machine alongside IK/XK - Add protocol negotiation payload codec - FMP wire format prep: version 1, msg3 header support - Switch FMP handshake from Noise IK to XX - Increase convergence timeouts for XX 3-message handshake - Fix negotiation hash chain desync in rekey handshake
This commit is contained in:
+236
-63
@@ -14,39 +14,48 @@
|
||||
//! | Phase | Type | Size | Description |
|
||||
//! |-------|-----------------|------------|--------------------------------|
|
||||
//! | 0x0 | Encrypted frame | 32+ bytes | Post-handshake encrypted data |
|
||||
//! | 0x1 | Noise IK msg1 | 114 bytes | Handshake initiation |
|
||||
//! | 0x2 | Noise IK msg2 | 69 bytes | Handshake response |
|
||||
//! | 0x1 | Noise XX msg1 | 41 bytes | Handshake initiation |
|
||||
//! | 0x2 | Noise XX msg2 | 118+ bytes | Handshake response |
|
||||
//! | 0x3 | Noise XX msg3 | 85+ bytes | Handshake completion |
|
||||
|
||||
use crate::noise::{HANDSHAKE_MSG1_SIZE, HANDSHAKE_MSG2_SIZE, TAG_SIZE};
|
||||
use crate::utils::index::SessionIndex;
|
||||
use crate::noise::{XX_HANDSHAKE_MSG1_SIZE, XX_HANDSHAKE_MSG2_SIZE, XX_HANDSHAKE_MSG3_SIZE, TAG_SIZE};
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
/// FMP protocol version (4 high bits of byte 0).
|
||||
pub const FMP_VERSION: u8 = 0;
|
||||
pub const FMP_VERSION: u8 = 1;
|
||||
|
||||
/// Phase value for established (encrypted) frames.
|
||||
pub const PHASE_ESTABLISHED: u8 = 0x0;
|
||||
|
||||
/// Phase value for Noise IK message 1 (handshake initiation).
|
||||
/// Phase value for handshake message 1 (initiation).
|
||||
pub const PHASE_MSG1: u8 = 0x1;
|
||||
|
||||
/// Phase value for Noise IK message 2 (handshake response).
|
||||
/// Phase value for handshake message 2 (response).
|
||||
pub const PHASE_MSG2: u8 = 0x2;
|
||||
|
||||
/// Phase value for handshake message 3 (completion, XX only).
|
||||
pub const PHASE_MSG3: u8 = 0x3;
|
||||
|
||||
/// Size of the common packet prefix (all packet types).
|
||||
pub const COMMON_PREFIX_SIZE: usize = 4;
|
||||
|
||||
/// Size of the full established frame header (prefix + receiver_idx + counter).
|
||||
pub const ESTABLISHED_HEADER_SIZE: usize = 16;
|
||||
|
||||
/// Size of Noise IK message 1 wire packet: prefix + sender_idx + noise_msg1.
|
||||
pub const MSG1_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + HANDSHAKE_MSG1_SIZE; // 114 bytes
|
||||
/// Size of handshake msg1 wire packet: prefix + sender_idx + noise_msg1.
|
||||
pub const MSG1_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + XX_HANDSHAKE_MSG1_SIZE; // 41 bytes
|
||||
|
||||
/// Size of Noise IK message 2 wire packet: prefix + sender_idx + receiver_idx + noise_msg2.
|
||||
pub const MSG2_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + 4 + HANDSHAKE_MSG2_SIZE; // 69 bytes
|
||||
/// Minimum size of handshake msg2 wire packet: prefix + sender_idx + receiver_idx + noise_msg2.
|
||||
/// Actual size may be larger due to optional negotiation payload.
|
||||
pub const MSG2_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + 4 + XX_HANDSHAKE_MSG2_SIZE; // 118 bytes
|
||||
|
||||
/// Minimum size of handshake msg3 wire packet: prefix + sender_idx + receiver_idx + noise_msg3.
|
||||
/// Actual size may be larger due to optional negotiation payload.
|
||||
pub const MSG3_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + 4 + XX_HANDSHAKE_MSG3_SIZE; // 85 bytes
|
||||
|
||||
/// Minimum size for encrypted frame: header + tag (no plaintext).
|
||||
pub const ENCRYPTED_MIN_SIZE: usize = ESTABLISHED_HEADER_SIZE + TAG_SIZE; // 32 bytes
|
||||
@@ -164,7 +173,8 @@ impl EncryptedHeader {
|
||||
let payload_len = u16::from_le_bytes([data[2], data[3]]);
|
||||
let receiver_idx = SessionIndex::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||
let counter = u64::from_le_bytes([
|
||||
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
|
||||
data[8], data[9], data[10], data[11],
|
||||
data[12], data[13], data[14], data[15],
|
||||
]);
|
||||
|
||||
let mut header_bytes = [0u8; ESTABLISHED_HEADER_SIZE];
|
||||
@@ -195,11 +205,11 @@ impl EncryptedHeader {
|
||||
// Msg1 Header
|
||||
// ============================================================================
|
||||
|
||||
/// Parsed Noise IK message 1 header (phase 0x1).
|
||||
/// Parsed handshake message 1 header (phase 0x1).
|
||||
///
|
||||
/// Wire format (114 bytes):
|
||||
/// Wire format (41 bytes, Noise XX):
|
||||
/// ```text
|
||||
/// [0x01][0x00][payload_len:2 LE][sender_idx:4 LE][noise_msg1:106]
|
||||
/// [0x11][0x00][payload_len:2 LE][sender_idx:4 LE][noise_msg1:33]
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Msg1Header {
|
||||
@@ -249,12 +259,13 @@ impl Msg1Header {
|
||||
// Msg2 Header
|
||||
// ============================================================================
|
||||
|
||||
/// Parsed Noise IK message 2 header (phase 0x2).
|
||||
/// Parsed handshake message 2 header (phase 0x2).
|
||||
///
|
||||
/// Wire format (69 bytes):
|
||||
/// Wire format (118+ bytes, Noise XX):
|
||||
/// ```text
|
||||
/// [0x02][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg2:57]
|
||||
/// [0x12][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg2:106+]
|
||||
/// ```
|
||||
/// Size is variable due to optional negotiation payload appended after base XX msg2.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Msg2Header {
|
||||
/// Session index chosen by the responder.
|
||||
@@ -268,9 +279,10 @@ pub struct Msg2Header {
|
||||
impl Msg2Header {
|
||||
/// Parse a msg2 header from packet data.
|
||||
///
|
||||
/// Returns None if the packet has wrong size or version/phase.
|
||||
/// Returns None if the packet is too short or has wrong version/phase.
|
||||
/// Accepts variable size (base + optional negotiation payload).
|
||||
pub fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.len() != MSG2_WIRE_SIZE {
|
||||
if data.len() < MSG2_WIRE_SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -296,11 +308,83 @@ impl Msg2Header {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the Noise msg2 payload from the original packet.
|
||||
/// Get the Noise msg2 payload from the original packet (variable length).
|
||||
#[cfg(test)]
|
||||
pub fn noise_msg2<'a>(&self, data: &'a [u8]) -> &'a [u8] {
|
||||
&data[self.noise_msg2_offset..]
|
||||
}
|
||||
|
||||
/// Get the total noise payload length (base + optional negotiation).
|
||||
#[allow(dead_code)]
|
||||
pub fn noise_payload_len(&self, data: &[u8]) -> usize {
|
||||
data.len() - self.noise_msg2_offset
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Msg3 Header
|
||||
// ============================================================================
|
||||
|
||||
/// Parsed handshake message 3 header (phase 0x3, XX pattern).
|
||||
///
|
||||
/// Wire format (85+ bytes, Noise XX):
|
||||
/// ```text
|
||||
/// [0x13][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg3:73+]
|
||||
/// ```
|
||||
/// Size is variable due to optional negotiation payload appended after base XX msg3.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Msg3Header {
|
||||
/// Session index chosen by the initiator (echo of msg1 sender_idx).
|
||||
pub sender_idx: SessionIndex,
|
||||
/// Echo of the responder's sender_idx from msg2.
|
||||
pub receiver_idx: SessionIndex,
|
||||
/// Offset where Noise msg3 payload begins.
|
||||
pub noise_msg3_offset: usize,
|
||||
}
|
||||
|
||||
impl Msg3Header {
|
||||
/// Parse a msg3 header from packet data.
|
||||
///
|
||||
/// Returns None if the packet is too short or has wrong version/phase.
|
||||
/// Accepts variable size (base + optional negotiation payload).
|
||||
pub fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < MSG3_WIRE_SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let version = data[0] >> 4;
|
||||
let phase = data[0] & 0x0F;
|
||||
|
||||
if version != FMP_VERSION || phase != PHASE_MSG3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// flags must be zero during handshake
|
||||
if data[1] != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let sender_idx = SessionIndex::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||
let receiver_idx = SessionIndex::from_le_bytes([data[8], data[9], data[10], data[11]]);
|
||||
|
||||
Some(Self {
|
||||
sender_idx,
|
||||
receiver_idx,
|
||||
noise_msg3_offset: COMMON_PREFIX_SIZE + 4 + 4, // 12
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the Noise msg3 payload from the original packet (variable length).
|
||||
#[cfg(test)]
|
||||
pub fn noise_msg3<'a>(&self, data: &'a [u8]) -> &'a [u8] {
|
||||
&data[self.noise_msg3_offset..]
|
||||
}
|
||||
|
||||
/// Get the total noise payload length (base + optional negotiation).
|
||||
#[allow(dead_code)]
|
||||
pub fn noise_payload_len(&self, data: &[u8]) -> usize {
|
||||
data.len() - self.noise_msg3_offset
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -309,9 +393,9 @@ impl Msg2Header {
|
||||
|
||||
/// Build a wire-format msg1 packet.
|
||||
///
|
||||
/// Format: `[0x01][0x00][payload_len:2 LE][sender_idx:4 LE][noise_msg1:106]`
|
||||
/// Format: `[0x11][0x00][payload_len:2 LE][sender_idx:4 LE][noise_msg1:33]`
|
||||
pub fn build_msg1(sender_idx: SessionIndex, noise_msg1: &[u8]) -> Vec<u8> {
|
||||
debug_assert_eq!(noise_msg1.len(), HANDSHAKE_MSG1_SIZE);
|
||||
debug_assert_eq!(noise_msg1.len(), XX_HANDSHAKE_MSG1_SIZE);
|
||||
|
||||
let payload_len = (4 + noise_msg1.len()) as u16; // sender_idx + noise_msg1
|
||||
|
||||
@@ -326,17 +410,15 @@ pub fn build_msg1(sender_idx: SessionIndex, noise_msg1: &[u8]) -> Vec<u8> {
|
||||
|
||||
/// Build a wire-format msg2 packet.
|
||||
///
|
||||
/// Format: `[0x02][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg2:57]`
|
||||
pub fn build_msg2(
|
||||
sender_idx: SessionIndex,
|
||||
receiver_idx: SessionIndex,
|
||||
noise_msg2: &[u8],
|
||||
) -> Vec<u8> {
|
||||
debug_assert_eq!(noise_msg2.len(), HANDSHAKE_MSG2_SIZE);
|
||||
/// Format: `[0x12][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg2:106+]`
|
||||
/// The noise_msg2 may include an optional negotiation payload beyond the base XX msg2.
|
||||
pub fn build_msg2(sender_idx: SessionIndex, receiver_idx: SessionIndex, noise_msg2: &[u8]) -> Vec<u8> {
|
||||
debug_assert!(noise_msg2.len() >= XX_HANDSHAKE_MSG2_SIZE);
|
||||
|
||||
let payload_len = (4 + 4 + noise_msg2.len()) as u16; // sender + receiver + noise
|
||||
let total = COMMON_PREFIX_SIZE + 4 + 4 + noise_msg2.len();
|
||||
|
||||
let mut packet = Vec::with_capacity(MSG2_WIRE_SIZE);
|
||||
let mut packet = Vec::with_capacity(total);
|
||||
packet.push(CommonPrefix::ver_phase_byte(FMP_VERSION, PHASE_MSG2));
|
||||
packet.push(0x00); // flags must be zero
|
||||
packet.extend_from_slice(&payload_len.to_le_bytes());
|
||||
@@ -346,6 +428,26 @@ pub fn build_msg2(
|
||||
packet
|
||||
}
|
||||
|
||||
/// Build a wire-format msg3 packet (XX handshake completion).
|
||||
///
|
||||
/// Format: `[0x13][0x00][payload_len:2 LE][sender_idx:4 LE][receiver_idx:4 LE][noise_msg3:73+]`
|
||||
/// The noise_msg3 may include an optional negotiation payload beyond the base XX msg3.
|
||||
pub fn build_msg3(sender_idx: SessionIndex, receiver_idx: SessionIndex, noise_msg3: &[u8]) -> Vec<u8> {
|
||||
debug_assert!(noise_msg3.len() >= XX_HANDSHAKE_MSG3_SIZE);
|
||||
|
||||
let payload_len = (4 + 4 + noise_msg3.len()) as u16; // sender + receiver + noise
|
||||
let total = COMMON_PREFIX_SIZE + 4 + 4 + noise_msg3.len();
|
||||
|
||||
let mut packet = Vec::with_capacity(total);
|
||||
packet.push(CommonPrefix::ver_phase_byte(FMP_VERSION, PHASE_MSG3));
|
||||
packet.push(0x00); // flags must be zero
|
||||
packet.extend_from_slice(&payload_len.to_le_bytes());
|
||||
packet.extend_from_slice(&sender_idx.to_le_bytes());
|
||||
packet.extend_from_slice(&receiver_idx.to_le_bytes());
|
||||
packet.extend_from_slice(noise_msg3);
|
||||
packet
|
||||
}
|
||||
|
||||
/// Build the 16-byte outer header for an established frame.
|
||||
///
|
||||
/// Returns the header bytes (for use as AAD) separately from the construction.
|
||||
@@ -414,9 +516,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_common_prefix_parse() {
|
||||
let data = [0x00, 0x04, 0x20, 0x00]; // ver=0, phase=0, flags=SP, payload_len=32
|
||||
let data = [0x10, 0x04, 0x20, 0x00]; // ver=1, phase=0, flags=SP, payload_len=32
|
||||
let prefix = CommonPrefix::parse(&data).unwrap();
|
||||
assert_eq!(prefix.version, 0);
|
||||
assert_eq!(prefix.version, 1);
|
||||
assert_eq!(prefix.phase, 0);
|
||||
assert_eq!(prefix.flags, FLAG_SP);
|
||||
assert_eq!(prefix.payload_len, 32);
|
||||
@@ -439,7 +541,7 @@ mod tests {
|
||||
let packet = build_encrypted(&header, &ciphertext);
|
||||
|
||||
assert_eq!(packet.len(), ESTABLISHED_HEADER_SIZE + 48);
|
||||
assert_eq!(packet[0], 0x00); // ver=0, phase=0
|
||||
assert_eq!(packet[0], 0x10); // ver=1, phase=0
|
||||
|
||||
let parsed = EncryptedHeader::parse(&packet).expect("should parse");
|
||||
assert_eq!(parsed.receiver_idx, receiver_idx);
|
||||
@@ -459,26 +561,26 @@ mod tests {
|
||||
#[test]
|
||||
fn test_encrypted_header_wrong_phase() {
|
||||
let mut packet = vec![0x00; ENCRYPTED_MIN_SIZE];
|
||||
packet[0] = 0x01; // phase 1 (msg1), not established
|
||||
packet[0] = 0x11; // ver=1, phase 1 (msg1), not established
|
||||
assert!(EncryptedHeader::parse(&packet).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypted_header_wrong_version() {
|
||||
let mut packet = vec![0x00; ENCRYPTED_MIN_SIZE];
|
||||
packet[0] = 0x10; // version 1, phase 0
|
||||
packet[0] = 0x00; // version 0 (old), phase 0
|
||||
assert!(EncryptedHeader::parse(&packet).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg1_header_parse() {
|
||||
let sender_idx = SessionIndex::new(0xABCDEF01);
|
||||
let noise_msg1 = vec![0xbb; HANDSHAKE_MSG1_SIZE];
|
||||
let noise_msg1 = vec![0xbb; XX_HANDSHAKE_MSG1_SIZE];
|
||||
|
||||
let packet = build_msg1(sender_idx, &noise_msg1);
|
||||
|
||||
assert_eq!(packet.len(), MSG1_WIRE_SIZE);
|
||||
assert_eq!(packet[0], 0x01); // ver=0, phase=1
|
||||
assert_eq!(packet[0], 0x11); // ver=1, phase=1
|
||||
|
||||
let header = Msg1Header::parse(&packet).expect("should parse");
|
||||
assert_eq!(header.sender_idx, sender_idx);
|
||||
@@ -488,23 +590,23 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_msg1_header_wrong_size() {
|
||||
let packet = vec![0x01; MSG1_WIRE_SIZE - 1];
|
||||
let packet = vec![0x11; MSG1_WIRE_SIZE - 1];
|
||||
assert!(Msg1Header::parse(&packet).is_none());
|
||||
|
||||
let packet = vec![0x01; MSG1_WIRE_SIZE + 1];
|
||||
let packet = vec![0x11; MSG1_WIRE_SIZE + 1];
|
||||
assert!(Msg1Header::parse(&packet).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg1_header_wrong_phase() {
|
||||
let mut packet = vec![0x00; MSG1_WIRE_SIZE];
|
||||
packet[0] = 0x02; // phase 2, not phase 1
|
||||
packet[0] = 0x12; // ver=1, phase 2, not phase 1
|
||||
assert!(Msg1Header::parse(&packet).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg1_header_nonzero_flags() {
|
||||
let mut packet = build_msg1(SessionIndex::new(1), &[0u8; HANDSHAKE_MSG1_SIZE]);
|
||||
let mut packet = build_msg1(SessionIndex::new(1), &[0u8; XX_HANDSHAKE_MSG1_SIZE]);
|
||||
packet[1] = 0x01; // flags must be zero during handshake
|
||||
assert!(Msg1Header::parse(&packet).is_none());
|
||||
}
|
||||
@@ -513,12 +615,12 @@ mod tests {
|
||||
fn test_msg2_header_parse() {
|
||||
let sender_idx = SessionIndex::new(0x11223344);
|
||||
let receiver_idx = SessionIndex::new(0x55667788);
|
||||
let noise_msg2 = vec![0xcc; HANDSHAKE_MSG2_SIZE];
|
||||
let noise_msg2 = vec![0xcc; XX_HANDSHAKE_MSG2_SIZE];
|
||||
|
||||
let packet = build_msg2(sender_idx, receiver_idx, &noise_msg2);
|
||||
|
||||
assert_eq!(packet.len(), MSG2_WIRE_SIZE);
|
||||
assert_eq!(packet[0], 0x02); // ver=0, phase=2
|
||||
assert_eq!(packet[0], 0x12); // ver=1, phase=2
|
||||
|
||||
let header = Msg2Header::parse(&packet).expect("should parse");
|
||||
assert_eq!(header.sender_idx, sender_idx);
|
||||
@@ -529,24 +631,29 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_msg2_header_wrong_size() {
|
||||
let packet = vec![0x02; MSG2_WIRE_SIZE - 1];
|
||||
let packet = vec![0x12; MSG2_WIRE_SIZE - 1];
|
||||
assert!(Msg2Header::parse(&packet).is_none());
|
||||
|
||||
let packet = vec![0x02; MSG2_WIRE_SIZE + 1];
|
||||
assert!(Msg2Header::parse(&packet).is_none());
|
||||
// Larger than minimum is now accepted (variable-length negotiation payload)
|
||||
let mut packet = vec![0x12; MSG2_WIRE_SIZE + 10];
|
||||
packet[0] = 0x12; // ver=1, phase=2
|
||||
packet[1] = 0x00;
|
||||
let header = Msg2Header::parse(&packet);
|
||||
assert!(header.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg2_header_wrong_phase() {
|
||||
let mut packet = vec![0x00; MSG2_WIRE_SIZE];
|
||||
packet[0] = 0x00; // phase 0, not phase 2
|
||||
packet[0] = 0x10; // ver=1, phase 0, not phase 2
|
||||
assert!(Msg2Header::parse(&packet).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wire_sizes() {
|
||||
assert_eq!(MSG1_WIRE_SIZE, 114); // 4 + 4 + 106
|
||||
assert_eq!(MSG2_WIRE_SIZE, 69); // 4 + 4 + 4 + 57
|
||||
assert_eq!(MSG1_WIRE_SIZE, 41); // 4 + 4 + 33 (XX msg1)
|
||||
assert_eq!(MSG2_WIRE_SIZE, 118); // 4 + 4 + 4 + 106 (XX msg2 minimum)
|
||||
assert_eq!(MSG3_WIRE_SIZE, 85); // 4 + 4 + 4 + 73 (XX msg3 minimum)
|
||||
assert_eq!(ENCRYPTED_MIN_SIZE, 32); // 16 + 16
|
||||
assert_eq!(COMMON_PREFIX_SIZE, 4);
|
||||
assert_eq!(ESTABLISHED_HEADER_SIZE, 16);
|
||||
@@ -557,7 +664,7 @@ mod tests {
|
||||
fn test_roundtrip_indices() {
|
||||
let idx = SessionIndex::new(0xDEADBEEF);
|
||||
|
||||
let msg1 = build_msg1(idx, &[0u8; HANDSHAKE_MSG1_SIZE]);
|
||||
let msg1 = build_msg1(idx, &[0u8; XX_HANDSHAKE_MSG1_SIZE]);
|
||||
let parsed = Msg1Header::parse(&msg1).unwrap();
|
||||
assert_eq!(parsed.sender_idx.as_u32(), 0xDEADBEEF);
|
||||
|
||||
@@ -588,17 +695,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_flags_byte() {
|
||||
let header =
|
||||
build_established_header(SessionIndex::new(1), 0, FLAG_KEY_EPOCH | FLAG_SP, 100);
|
||||
let header = build_established_header(
|
||||
SessionIndex::new(1),
|
||||
0,
|
||||
FLAG_KEY_EPOCH | FLAG_SP,
|
||||
100,
|
||||
);
|
||||
assert_eq!(header[1], 0x05); // bits 0 and 2 set
|
||||
|
||||
let parsed = EncryptedHeader::parse(&[
|
||||
header[0], header[1], header[2], header[3], header[4], header[5], header[6], header[7],
|
||||
header[8], header[9], header[10], header[11], header[12], header[13], header[14],
|
||||
header[15], // minimum: TAG_SIZE bytes of ciphertext
|
||||
header[0], header[1], header[2], header[3],
|
||||
header[4], header[5], header[6], header[7],
|
||||
header[8], header[9], header[10], header[11],
|
||||
header[12], header[13], header[14], header[15],
|
||||
// minimum: TAG_SIZE bytes of ciphertext
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
])
|
||||
.unwrap();
|
||||
]).unwrap();
|
||||
assert_eq!(parsed.flags & FLAG_KEY_EPOCH, FLAG_KEY_EPOCH);
|
||||
assert_eq!(parsed.flags & FLAG_CE, 0);
|
||||
assert_eq!(parsed.flags & FLAG_SP, FLAG_SP);
|
||||
@@ -606,10 +718,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_payload_len_in_msg1() {
|
||||
let packet = build_msg1(SessionIndex::new(1), &[0u8; HANDSHAKE_MSG1_SIZE]);
|
||||
let packet = build_msg1(SessionIndex::new(1), &[0u8; XX_HANDSHAKE_MSG1_SIZE]);
|
||||
let prefix = CommonPrefix::parse(&packet).unwrap();
|
||||
// payload_len = sender_idx(4) + noise_msg1(106) = 110
|
||||
assert_eq!(prefix.payload_len, 110);
|
||||
// payload_len = sender_idx(4) + noise_msg1(33) = 37
|
||||
assert_eq!(prefix.payload_len, 37);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -617,10 +729,71 @@ mod tests {
|
||||
let packet = build_msg2(
|
||||
SessionIndex::new(1),
|
||||
SessionIndex::new(2),
|
||||
&[0u8; HANDSHAKE_MSG2_SIZE],
|
||||
&[0u8; XX_HANDSHAKE_MSG2_SIZE],
|
||||
);
|
||||
let prefix = CommonPrefix::parse(&packet).unwrap();
|
||||
// payload_len = sender_idx(4) + receiver_idx(4) + noise_msg2(57) = 65
|
||||
assert_eq!(prefix.payload_len, 65);
|
||||
// payload_len = sender_idx(4) + receiver_idx(4) + noise_msg2(106) = 114
|
||||
assert_eq!(prefix.payload_len, 114);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg3_header_parse() {
|
||||
let sender_idx = SessionIndex::new(0xAABBCCDD);
|
||||
let receiver_idx = SessionIndex::new(0x11223344);
|
||||
let noise_msg3 = vec![0xdd; XX_HANDSHAKE_MSG3_SIZE];
|
||||
|
||||
let packet = build_msg3(sender_idx, receiver_idx, &noise_msg3);
|
||||
|
||||
assert_eq!(packet.len(), MSG3_WIRE_SIZE);
|
||||
assert_eq!(packet[0], 0x13); // ver=1, phase=3
|
||||
|
||||
let header = Msg3Header::parse(&packet).expect("should parse");
|
||||
assert_eq!(header.sender_idx, sender_idx);
|
||||
assert_eq!(header.receiver_idx, receiver_idx);
|
||||
assert_eq!(header.noise_msg3_offset, 12);
|
||||
assert_eq!(header.noise_msg3(&packet), &noise_msg3[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg3_header_wrong_size() {
|
||||
let packet = vec![0x13; MSG3_WIRE_SIZE - 1];
|
||||
assert!(Msg3Header::parse(&packet).is_none());
|
||||
|
||||
// Larger than minimum is now accepted (variable-length negotiation payload)
|
||||
let mut packet = vec![0x13; MSG3_WIRE_SIZE + 10];
|
||||
packet[0] = 0x13; // ver=1, phase=3
|
||||
packet[1] = 0x00;
|
||||
let header = Msg3Header::parse(&packet);
|
||||
assert!(header.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg3_header_wrong_phase() {
|
||||
let mut packet = vec![0x00; MSG3_WIRE_SIZE];
|
||||
packet[0] = 0x12; // ver=1, phase 2, not phase 3
|
||||
assert!(Msg3Header::parse(&packet).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msg3_header_nonzero_flags() {
|
||||
let mut packet = build_msg3(
|
||||
SessionIndex::new(1),
|
||||
SessionIndex::new(2),
|
||||
&[0u8; XX_HANDSHAKE_MSG3_SIZE],
|
||||
);
|
||||
packet[1] = 0x01; // flags must be zero during handshake
|
||||
assert!(Msg3Header::parse(&packet).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_payload_len_in_msg3() {
|
||||
let packet = build_msg3(
|
||||
SessionIndex::new(1),
|
||||
SessionIndex::new(2),
|
||||
&[0u8; XX_HANDSHAKE_MSG3_SIZE],
|
||||
);
|
||||
let prefix = CommonPrefix::parse(&packet).unwrap();
|
||||
// payload_len = sender_idx(4) + receiver_idx(4) + noise_msg3(73) = 81
|
||||
assert_eq!(prefix.payload_len, 81);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user