mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-06 03:28:24 +00:00
Remove IK/XK dead code, rename XX methods as sole pattern
- Delete PROTOCOL_NAME_IK, PROTOCOL_NAME_XK and all IK/XK methods - Remove Ik/Xk variants from NoisePattern enum - Rename XX methods to drop xx_ prefix (sole pattern) - Rename HandshakeMessageType variants from NoiseIKMsg1/NoiseIKMsg2 to Msg1/Msg2, add Msg3 variant (0x03) for XX 3-message flow - Fix doc comments with correct XX message sizes and descriptions - Fix stale SessionSetup/SessionAck handshake payload size comments
This commit is contained in:
@@ -736,7 +736,7 @@ impl Node {
|
||||
// Identity-based restart/rekey detection.
|
||||
//
|
||||
// Now that we know the initiator's identity from msg3, perform the
|
||||
// same checks that IK handle_msg1 used to do after decrypting msg1.
|
||||
// same checks that the old handle_msg1 used to do after decrypting msg1.
|
||||
if let Some(existing_peer) = self.peers.get(&peer_node_addr) {
|
||||
let new_epoch = conn.remote_epoch();
|
||||
let existing_epoch = existing_peer.remote_epoch();
|
||||
|
||||
@@ -20,7 +20,7 @@ const DRAIN_WINDOW_SECS: u64 = 10;
|
||||
const REKEY_DAMPENING_SECS: u64 = 30;
|
||||
|
||||
/// Delay FSP initiator cutover after handshake completion to allow
|
||||
/// XK msg3 to reach the responder before K-bit-flipped data arrives.
|
||||
/// XX msg3 to reach the responder before K-bit-flipped data arrives.
|
||||
const FSP_CUTOVER_DELAY_MS: u64 = 2000;
|
||||
|
||||
impl Node {
|
||||
@@ -162,10 +162,10 @@ impl Node {
|
||||
|
||||
// Create XX initiator handshake directly (no PeerConnection)
|
||||
let our_keypair = self.identity.keypair();
|
||||
let mut hs = HandshakeState::new_xx_initiator(our_keypair);
|
||||
let mut hs = HandshakeState::new_initiator(our_keypair);
|
||||
hs.set_local_epoch(self.startup_epoch);
|
||||
|
||||
let noise_msg1 = match hs.write_xx_message_1() {
|
||||
let noise_msg1 = match hs.write_message_1() {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
@@ -269,7 +269,7 @@ impl Node {
|
||||
/// For each established session:
|
||||
/// - If the initiator has a pending session, perform K-bit cutover
|
||||
/// - If the drain window has expired, clean up the previous session
|
||||
/// - If the rekey timer/counter fires, initiate a new XK handshake
|
||||
/// - If the rekey timer/counter fires, initiate a new XX handshake
|
||||
pub(in crate::node) async fn check_session_rekey(&mut self) {
|
||||
if !self.config.node.rekey.enabled {
|
||||
return;
|
||||
@@ -358,7 +358,7 @@ impl Node {
|
||||
|
||||
/// Initiate an FSP session rekey.
|
||||
///
|
||||
/// Creates a new XK handshake as initiator, sends SessionSetup msg1
|
||||
/// Creates a new XX handshake as initiator, sends SessionSetup msg1
|
||||
/// through the mesh, and stores the handshake state on the existing entry.
|
||||
async fn initiate_session_rekey(&mut self, dest_addr: &NodeAddr) {
|
||||
// Check route availability before paying crypto cost
|
||||
@@ -378,10 +378,10 @@ impl Node {
|
||||
|
||||
// Create Noise XX initiator handshake (rekey: no negotiation payload)
|
||||
let our_keypair = self.identity.keypair();
|
||||
let mut handshake = HandshakeState::new_xx_initiator(our_keypair);
|
||||
let mut handshake = HandshakeState::new_initiator(our_keypair);
|
||||
handshake.set_local_epoch(self.startup_epoch);
|
||||
|
||||
let msg1 = match handshake.write_xx_message_1() {
|
||||
let msg1 = match handshake.write_message_1() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::node::session_wire::{
|
||||
use crate::protocol::{coords_wire_size, encode_coords};
|
||||
use crate::upper::icmp::FIPS_OVERHEAD;
|
||||
use crate::node::{Node, NodeError};
|
||||
use crate::noise::{HandshakeState, XX_HANDSHAKE_MSG1_SIZE, XX_HANDSHAKE_MSG2_SIZE, XX_HANDSHAKE_MSG3_SIZE};
|
||||
use crate::noise::{HandshakeState, HANDSHAKE_MSG1_SIZE, HANDSHAKE_MSG2_SIZE, HANDSHAKE_MSG3_SIZE};
|
||||
use crate::protocol::NegotiationPayload;
|
||||
use crate::mmp::report::ReceiverReport;
|
||||
use crate::mmp::{MAX_SESSION_REPORT_INTERVAL_MS, MIN_SESSION_REPORT_INTERVAL_MS};
|
||||
@@ -153,7 +153,7 @@ impl Node {
|
||||
}
|
||||
};
|
||||
// Drop encrypted data if session is not yet established.
|
||||
// With XK, the responder must wait for msg3 before it can decrypt.
|
||||
// With XX, the responder must wait for msg3 before it can decrypt.
|
||||
if !entry.is_established() {
|
||||
debug!(
|
||||
src = %self.peer_display_name(src_addr),
|
||||
@@ -357,7 +357,7 @@ impl Node {
|
||||
/// Handle an incoming SessionSetup (Noise XX msg1).
|
||||
///
|
||||
/// The remote node wants to establish an end-to-end session with us.
|
||||
/// We create an XK responder handshake, process msg1, send SessionAck with msg2,
|
||||
/// We create an XX responder handshake, process msg1, send SessionAck with msg2,
|
||||
/// and transition to AwaitingMsg3.
|
||||
async fn handle_session_setup(&mut self, src_addr: &NodeAddr, inner: &[u8]) {
|
||||
let setup = match SessionSetup::decode(inner) {
|
||||
@@ -368,10 +368,10 @@ impl Node {
|
||||
}
|
||||
};
|
||||
|
||||
if setup.handshake_payload.len() != XX_HANDSHAKE_MSG1_SIZE {
|
||||
if setup.handshake_payload.len() != HANDSHAKE_MSG1_SIZE {
|
||||
debug!(
|
||||
len = setup.handshake_payload.len(),
|
||||
expected = XX_HANDSHAKE_MSG1_SIZE,
|
||||
expected = HANDSHAKE_MSG1_SIZE,
|
||||
"Invalid handshake payload size in SessionSetup"
|
||||
);
|
||||
return;
|
||||
@@ -443,16 +443,16 @@ impl Node {
|
||||
return;
|
||||
}
|
||||
let our_keypair = self.identity.keypair();
|
||||
let mut handshake = HandshakeState::new_xx_responder(our_keypair);
|
||||
let mut handshake = HandshakeState::new_responder(our_keypair);
|
||||
handshake.set_local_epoch(self.startup_epoch);
|
||||
|
||||
if let Err(e) = handshake.read_xx_message_1(&setup.handshake_payload) {
|
||||
if let Err(e) = handshake.read_message_1(&setup.handshake_payload) {
|
||||
debug!(error = %e, "Failed to process rekey XX msg1");
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate msg2
|
||||
let msg2 = match handshake.write_xx_message_2() {
|
||||
let msg2 = match handshake.write_message_2() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
debug!(error = %e, "Failed to generate rekey XX msg2");
|
||||
@@ -491,22 +491,22 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
// Create XK responder handshake and process msg1
|
||||
// Create XX responder handshake and process msg1
|
||||
let our_keypair = self.identity.keypair();
|
||||
let mut handshake = HandshakeState::new_xx_responder(our_keypair);
|
||||
let mut handshake = HandshakeState::new_responder(our_keypair);
|
||||
handshake.set_local_epoch(self.startup_epoch);
|
||||
|
||||
if let Err(e) = handshake.read_xx_message_1(&setup.handshake_payload) {
|
||||
if let Err(e) = handshake.read_message_1(&setup.handshake_payload) {
|
||||
debug!(error = %e, "Failed to process Noise XX msg1 in SessionSetup");
|
||||
return;
|
||||
}
|
||||
|
||||
// XK: responder does NOT learn initiator's identity until msg3
|
||||
// XX: responder does NOT learn initiator's identity until msg3
|
||||
// Use a placeholder pubkey from src_addr for the session entry.
|
||||
// The real pubkey will be registered when msg3 arrives.
|
||||
|
||||
// Generate msg2 with negotiation payload
|
||||
let mut msg2 = match handshake.write_xx_message_2() {
|
||||
let mut msg2 = match handshake.write_message_2() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
debug!(error = %e, "Failed to generate Noise XX msg2 for SessionAck");
|
||||
@@ -563,10 +563,10 @@ impl Node {
|
||||
}
|
||||
};
|
||||
|
||||
if ack.handshake_payload.len() < XX_HANDSHAKE_MSG2_SIZE {
|
||||
if ack.handshake_payload.len() < HANDSHAKE_MSG2_SIZE {
|
||||
debug!(
|
||||
len = ack.handshake_payload.len(),
|
||||
min = XX_HANDSHAKE_MSG2_SIZE,
|
||||
min = HANDSHAKE_MSG2_SIZE,
|
||||
"Handshake payload too short in SessionAck"
|
||||
);
|
||||
return;
|
||||
@@ -591,16 +591,16 @@ impl Node {
|
||||
}
|
||||
};
|
||||
|
||||
// Process XK msg2
|
||||
if let Err(e) = handshake.read_xx_message_2(&ack.handshake_payload) {
|
||||
// Process XX msg2
|
||||
if let Err(e) = handshake.read_message_2(&ack.handshake_payload) {
|
||||
debug!(error = %e, "Failed to process rekey XX msg2");
|
||||
entry.abandon_rekey();
|
||||
self.sessions.insert(*src_addr, entry);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate XK msg3
|
||||
let msg3 = match handshake.write_xx_message_3() {
|
||||
// Generate XX msg3
|
||||
let msg3 = match handshake.write_message_3() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
debug!(error = %e, "Failed to generate rekey XX msg3");
|
||||
@@ -641,7 +641,7 @@ impl Node {
|
||||
|
||||
debug!(
|
||||
src = %self.peer_display_name(src_addr),
|
||||
"FSP rekey: completed XK as initiator, pending cutover"
|
||||
"FSP rekey: completed XX as initiator, pending cutover"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -658,14 +658,14 @@ impl Node {
|
||||
};
|
||||
|
||||
// Split msg2 into base XX part and optional negotiation payload
|
||||
let (base_msg2, neg_bytes) = if ack.handshake_payload.len() > XX_HANDSHAKE_MSG2_SIZE {
|
||||
(&ack.handshake_payload[..XX_HANDSHAKE_MSG2_SIZE], Some(&ack.handshake_payload[XX_HANDSHAKE_MSG2_SIZE..]))
|
||||
let (base_msg2, neg_bytes) = if ack.handshake_payload.len() > HANDSHAKE_MSG2_SIZE {
|
||||
(&ack.handshake_payload[..HANDSHAKE_MSG2_SIZE], Some(&ack.handshake_payload[HANDSHAKE_MSG2_SIZE..]))
|
||||
} else {
|
||||
(ack.handshake_payload.as_slice(), None)
|
||||
};
|
||||
|
||||
// Process XX msg2 (learns responder's identity and epoch)
|
||||
if let Err(e) = handshake.read_xx_message_2(base_msg2) {
|
||||
if let Err(e) = handshake.read_message_2(base_msg2) {
|
||||
debug!(error = %e, "Failed to process Noise XX msg2 in SessionAck");
|
||||
return;
|
||||
}
|
||||
@@ -698,7 +698,7 @@ impl Node {
|
||||
}
|
||||
|
||||
// Generate XX msg3 with negotiation payload
|
||||
let mut msg3 = match handshake.write_xx_message_3() {
|
||||
let mut msg3 = match handshake.write_message_3() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
debug!(error = %e, "Failed to generate Noise XX msg3");
|
||||
@@ -732,7 +732,7 @@ impl Node {
|
||||
let session = match handshake.into_session() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
debug!(error = %e, "Failed to create session after XK msg3");
|
||||
debug!(error = %e, "Failed to create session after XX msg3");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -750,7 +750,7 @@ impl Node {
|
||||
// Flush any queued outbound packets for this destination
|
||||
self.flush_pending_packets(src_addr).await;
|
||||
|
||||
info!(src = %self.peer_display_name(src_addr), "Session established (initiator, XK)");
|
||||
info!(src = %self.peer_display_name(src_addr), "Session established (initiator, XX)");
|
||||
}
|
||||
|
||||
/// Handle an incoming SessionMsg3 (Noise XX msg3).
|
||||
@@ -767,10 +767,10 @@ impl Node {
|
||||
}
|
||||
};
|
||||
|
||||
if msg3.handshake_payload.len() < XX_HANDSHAKE_MSG3_SIZE {
|
||||
if msg3.handshake_payload.len() < HANDSHAKE_MSG3_SIZE {
|
||||
debug!(
|
||||
len = msg3.handshake_payload.len(),
|
||||
min = XX_HANDSHAKE_MSG3_SIZE,
|
||||
min = HANDSHAKE_MSG3_SIZE,
|
||||
"Handshake payload too short in SessionMsg3"
|
||||
);
|
||||
return;
|
||||
@@ -795,8 +795,8 @@ impl Node {
|
||||
}
|
||||
};
|
||||
|
||||
// Process XK msg3
|
||||
if let Err(e) = handshake.read_xx_message_3(&msg3.handshake_payload) {
|
||||
// Process XX msg3
|
||||
if let Err(e) = handshake.read_message_3(&msg3.handshake_payload) {
|
||||
debug!(error = %e, "Failed to process rekey XX msg3");
|
||||
entry.abandon_rekey();
|
||||
self.sessions.insert(*src_addr, entry);
|
||||
@@ -819,7 +819,7 @@ impl Node {
|
||||
|
||||
debug!(
|
||||
src = %self.peer_display_name(src_addr),
|
||||
"FSP rekey: completed XK as responder, pending cutover"
|
||||
"FSP rekey: completed XX as responder, pending cutover"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -836,14 +836,14 @@ impl Node {
|
||||
};
|
||||
|
||||
// Split msg3 into base XX part and optional negotiation payload
|
||||
let (base_msg3, neg_bytes) = if msg3.handshake_payload.len() > XX_HANDSHAKE_MSG3_SIZE {
|
||||
(&msg3.handshake_payload[..XX_HANDSHAKE_MSG3_SIZE], Some(&msg3.handshake_payload[XX_HANDSHAKE_MSG3_SIZE..]))
|
||||
let (base_msg3, neg_bytes) = if msg3.handshake_payload.len() > HANDSHAKE_MSG3_SIZE {
|
||||
(&msg3.handshake_payload[..HANDSHAKE_MSG3_SIZE], Some(&msg3.handshake_payload[HANDSHAKE_MSG3_SIZE..]))
|
||||
} else {
|
||||
(msg3.handshake_payload.as_slice(), None)
|
||||
};
|
||||
|
||||
// Process XX msg3 (learns initiator's identity and epoch)
|
||||
if let Err(e) = handshake.read_xx_message_3(base_msg3) {
|
||||
if let Err(e) = handshake.read_message_3(base_msg3) {
|
||||
debug!(error = %e, "Failed to process Noise XX msg3");
|
||||
return;
|
||||
}
|
||||
@@ -865,7 +865,7 @@ impl Node {
|
||||
let remote_pubkey = match handshake.remote_static() {
|
||||
Some(pk) => *pk,
|
||||
None => {
|
||||
debug!("No remote static key after processing XK msg3");
|
||||
debug!("No remote static key after processing XX msg3");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -877,7 +877,7 @@ impl Node {
|
||||
let session = match handshake.into_session() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
debug!(error = %e, "Failed to create session from XK handshake");
|
||||
debug!(error = %e, "Failed to create session from XX handshake");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -894,7 +894,7 @@ impl Node {
|
||||
// Flush any pending packets
|
||||
self.flush_pending_packets(src_addr).await;
|
||||
|
||||
info!(src = %self.peer_display_name(src_addr), "Session established (responder, XK)");
|
||||
info!(src = %self.peer_display_name(src_addr), "Session established (responder, XX)");
|
||||
}
|
||||
|
||||
// === Session-layer MMP report handlers ===
|
||||
@@ -1126,7 +1126,7 @@ impl Node {
|
||||
|
||||
// Trigger re-discovery to get fresh coordinates, but only if we have
|
||||
// the target's identity cached — otherwise we can't verify the
|
||||
// LookupResponse proof. This avoids a race when the XK responder
|
||||
// LookupResponse proof. This avoids a race when the XX responder
|
||||
// receives PathBroken before msg3 completes (identity unknown).
|
||||
if self.has_cached_identity(&msg.dest_addr) {
|
||||
self.maybe_initiate_lookup(&msg.dest_addr).await;
|
||||
@@ -1212,9 +1212,9 @@ impl Node {
|
||||
|
||||
// Create Noise XX initiator handshake
|
||||
let our_keypair = self.identity.keypair();
|
||||
let mut handshake = HandshakeState::new_xx_initiator(our_keypair);
|
||||
let mut handshake = HandshakeState::new_initiator(our_keypair);
|
||||
handshake.set_local_epoch(self.startup_epoch);
|
||||
let msg1 = handshake.write_xx_message_1().map_err(|e| NodeError::SendFailed {
|
||||
let msg1 = handshake.write_message_1().map_err(|e| NodeError::SendFailed {
|
||||
node_addr: dest_addr,
|
||||
reason: format!("Noise XX msg1 generation failed: {}", e),
|
||||
})?;
|
||||
|
||||
@@ -190,7 +190,7 @@ impl Node {
|
||||
/// Initiate a connection to a peer on a specific transport and address.
|
||||
///
|
||||
/// For connectionless transports (UDP, Ethernet): allocates a link, starts
|
||||
/// the Noise IK handshake, sends msg1, and registers the connection for
|
||||
/// the Noise XX handshake, sends msg1, and registers the connection for
|
||||
/// msg2 dispatch.
|
||||
///
|
||||
/// For connection-oriented transports (TCP, Tor): allocates a link and
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
//! End-to-end session state.
|
||||
//!
|
||||
//! Tracks Noise XK sessions between this node and remote endpoints.
|
||||
//! Sessions are established via a three-message XK handshake
|
||||
//! Tracks Noise XX sessions between this node and remote endpoints.
|
||||
//! Sessions are established via a three-message XX handshake
|
||||
//! (SessionSetup/SessionAck/SessionMsg3) carried inside SessionDatagram
|
||||
//! envelopes through the mesh.
|
||||
|
||||
@@ -15,9 +15,9 @@ use secp256k1::PublicKey;
|
||||
|
||||
/// State machine for an end-to-end session.
|
||||
pub(crate) enum EndToEndState {
|
||||
/// We initiated: sent SessionSetup with Noise XK msg1, awaiting SessionAck.
|
||||
/// We initiated: sent SessionSetup with Noise XX msg1, awaiting SessionAck.
|
||||
Initiating(HandshakeState),
|
||||
/// XK responder: processed msg1, sent msg2, awaiting msg3.
|
||||
/// XX responder: processed msg1, sent msg2, awaiting msg3.
|
||||
/// Transitions to Established when msg3 arrives.
|
||||
AwaitingMsg3(HandshakeState),
|
||||
/// Handshake complete, NoiseSession available for encrypt/decrypt.
|
||||
@@ -35,7 +35,7 @@ impl EndToEndState {
|
||||
matches!(self, EndToEndState::Initiating(_))
|
||||
}
|
||||
|
||||
/// Check if we are an XK responder awaiting msg3.
|
||||
/// Check if we are an XX responder awaiting msg3.
|
||||
pub(crate) fn is_awaiting_msg3(&self) -> bool {
|
||||
matches!(self, EndToEndState::AwaitingMsg3(_))
|
||||
}
|
||||
@@ -202,7 +202,7 @@ impl SessionEntry {
|
||||
self.state.as_ref().is_some_and(|s| s.is_initiating())
|
||||
}
|
||||
|
||||
/// Check if we are an XK responder awaiting msg3.
|
||||
/// Check if we are an XX responder awaiting msg3.
|
||||
pub(crate) fn is_awaiting_msg3(&self) -> bool {
|
||||
self.state.as_ref().is_some_and(|s| s.is_awaiting_msg3())
|
||||
}
|
||||
@@ -400,7 +400,7 @@ impl SessionEntry {
|
||||
self.rekey_state = None;
|
||||
}
|
||||
|
||||
/// Set the rekey handshake state (in-progress XK handshake).
|
||||
/// Set the rekey handshake state (in-progress XX handshake).
|
||||
pub(crate) fn set_rekey_state(&mut self, state: HandshakeState, is_initiator: bool) {
|
||||
self.rekey_state = Some(state);
|
||||
self.rekey_initiator = is_initiator;
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
//! |-------|--------|------------------|-----------------------------------|
|
||||
//! | 0x0 | 0 | Encrypted | Post-handshake encrypted data |
|
||||
//! | 0x0 | 1 | Plaintext error | CoordsRequired, PathBroken |
|
||||
//! | 0x1 | - | Handshake msg1 | SessionSetup (Noise XK msg1) |
|
||||
//! | 0x2 | - | Handshake msg2 | SessionAck (Noise XK msg2) |
|
||||
//! | 0x3 | - | Handshake msg3 | SessionMsg3 (Noise XK msg3) |
|
||||
//! | 0x1 | - | Handshake msg1 | SessionSetup (Noise XX msg1) |
|
||||
//! | 0x2 | - | Handshake msg2 | SessionAck (Noise XX msg2) |
|
||||
//! | 0x3 | - | Handshake msg3 | SessionMsg3 (Noise XX msg3) |
|
||||
|
||||
use crate::protocol::{ProtocolError, decode_optional_coords};
|
||||
use crate::tree::TreeCoordinate;
|
||||
@@ -45,13 +45,13 @@ pub const FSP_VERSION: u8 = 0;
|
||||
/// Phase value for established (encrypted or plaintext error) messages.
|
||||
pub const FSP_PHASE_ESTABLISHED: u8 = 0x0;
|
||||
|
||||
/// Phase value for SessionSetup (Noise IK message 1).
|
||||
/// Phase value for SessionSetup (Noise XX message 1).
|
||||
pub const FSP_PHASE_MSG1: u8 = 0x1;
|
||||
|
||||
/// Phase value for SessionAck (Noise handshake message 2).
|
||||
pub const FSP_PHASE_MSG2: u8 = 0x2;
|
||||
|
||||
/// Phase value for XK message 3 (initiator's encrypted static).
|
||||
/// Phase value for XX message 3 (initiator's encrypted static).
|
||||
pub const FSP_PHASE_MSG3: u8 = 0x3;
|
||||
|
||||
/// Size of the common packet prefix (all FSP message types).
|
||||
|
||||
@@ -253,13 +253,12 @@ async fn test_disconnect_clears_session() {
|
||||
let node1_addr = *nodes[1].node.node_addr();
|
||||
|
||||
// Inject a synthetic Established session entry into node 1's session table
|
||||
// to simulate the state after a completed XK handshake with node 0.
|
||||
// to simulate the state after a completed XX handshake with node 0.
|
||||
let remote_identity = Identity::generate();
|
||||
{
|
||||
let our_identity = nodes[1].node.identity();
|
||||
|
||||
let mut initiator =
|
||||
HandshakeState::new_initiator(our_identity.keypair(), remote_identity.pubkey_full());
|
||||
let mut initiator = HandshakeState::new_initiator(our_identity.keypair());
|
||||
let mut responder = HandshakeState::new_responder(remote_identity.keypair());
|
||||
let mut init_epoch = [0u8; 8];
|
||||
rand::Rng::fill_bytes(&mut rand::rng(), &mut init_epoch);
|
||||
@@ -271,6 +270,8 @@ async fn test_disconnect_clears_session() {
|
||||
responder.read_message_1(&msg1).unwrap();
|
||||
let msg2 = responder.write_message_2().unwrap();
|
||||
initiator.read_message_2(&msg2).unwrap();
|
||||
let msg3 = initiator.write_message_3().unwrap();
|
||||
responder.read_message_3(&msg3).unwrap();
|
||||
let session = initiator.into_session().unwrap();
|
||||
|
||||
let entry = SessionEntry::new(
|
||||
@@ -283,16 +284,8 @@ async fn test_disconnect_clears_session() {
|
||||
nodes[1].node.sessions.insert(node0_addr, entry);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
nodes[1].node.session_count(),
|
||||
1,
|
||||
"Session should exist before disconnect"
|
||||
);
|
||||
assert_eq!(
|
||||
nodes[1].node.peer_count(),
|
||||
1,
|
||||
"Peer should exist before disconnect"
|
||||
);
|
||||
assert_eq!(nodes[1].node.session_count(), 1, "Session should exist before disconnect");
|
||||
assert_eq!(nodes[1].node.peer_count(), 1, "Peer should exist before disconnect");
|
||||
|
||||
// Node 0 sends Disconnect to node 1.
|
||||
let disconnect = crate::protocol::Disconnect::new(DisconnectReason::Shutdown);
|
||||
@@ -307,8 +300,7 @@ async fn test_disconnect_clears_session() {
|
||||
|
||||
// Peer must be gone.
|
||||
assert_eq!(
|
||||
nodes[1].node.peer_count(),
|
||||
0,
|
||||
nodes[1].node.peer_count(), 0,
|
||||
"Peer should be removed after disconnect"
|
||||
);
|
||||
|
||||
@@ -316,8 +308,7 @@ async fn test_disconnect_clears_session() {
|
||||
// Before the fix, session_count() would still be 1 here because
|
||||
// remove_active_peer didn't remove self.sessions[node0_addr].
|
||||
assert_eq!(
|
||||
nodes[1].node.session_count(),
|
||||
0,
|
||||
nodes[1].node.session_count(), 0,
|
||||
"Session must be cleaned up when peer is removed (regression: issue #5)"
|
||||
);
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ async fn test_response_proof_verification_failure() {
|
||||
#[tokio::test]
|
||||
async fn test_response_identity_cache_miss() {
|
||||
// Verify that a response is discarded when the origin lacks the
|
||||
// target's pubkey in identity_cache (e.g., XK responder before msg3).
|
||||
// target's pubkey in identity_cache (e.g., XX responder before msg3).
|
||||
let mut node = make_node();
|
||||
let from = make_node_addr(0xAA);
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ async fn test_two_node_handshake_udp() {
|
||||
// Allocate session index for A's outbound
|
||||
let our_index_a = node_a.index_allocator.allocate().unwrap();
|
||||
|
||||
// Start handshake (generates Noise IK msg1)
|
||||
// Start handshake (generates Noise XX msg1)
|
||||
let our_keypair_a = node_a.identity.keypair();
|
||||
let noise_msg1 = conn_a.start_handshake(our_keypair_a, node_a.startup_epoch, 1000).unwrap();
|
||||
conn_a.set_our_index(our_index_a);
|
||||
|
||||
@@ -35,7 +35,7 @@ pub(super) fn make_peer_identity() -> PeerIdentity {
|
||||
PeerIdentity::from_pubkey(identity.pubkey())
|
||||
}
|
||||
|
||||
/// Create a PeerConnection with a completed Noise IK handshake.
|
||||
/// Create a PeerConnection with a completed Noise XX handshake.
|
||||
///
|
||||
/// Returns (connection, peer_identity) where the connection is outbound,
|
||||
/// in Complete state, with session, indices, and transport info set.
|
||||
|
||||
+22
-50
@@ -50,10 +50,7 @@ fn test_session_entry_new_initiating() {
|
||||
let identity_a = Identity::generate();
|
||||
let identity_b = Identity::generate();
|
||||
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
identity_a.keypair(),
|
||||
identity_b.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(identity_a.keypair());
|
||||
|
||||
let entry = crate::node::session::SessionEntry::new(
|
||||
*identity_b.node_addr(),
|
||||
@@ -77,10 +74,7 @@ fn test_session_entry_touch() {
|
||||
let identity_a = Identity::generate();
|
||||
let identity_b = Identity::generate();
|
||||
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
identity_a.keypair(),
|
||||
identity_b.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(identity_a.keypair());
|
||||
|
||||
let mut entry = crate::node::session::SessionEntry::new(
|
||||
*identity_b.node_addr(),
|
||||
@@ -102,10 +96,7 @@ fn test_session_table_operations() {
|
||||
let mut node = make_node();
|
||||
let identity_b = Identity::generate();
|
||||
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
node.identity().keypair(),
|
||||
identity_b.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(node.identity().keypair());
|
||||
|
||||
let dest_addr = *identity_b.node_addr();
|
||||
let entry = crate::node::session::SessionEntry::new(
|
||||
@@ -163,7 +154,7 @@ async fn test_session_direct_peer_handshake() {
|
||||
let count = process_available_packets(&mut nodes).await;
|
||||
assert!(count > 0, "Expected SessionSetup packet to arrive");
|
||||
|
||||
// Node 1 should now have a session in AwaitingMsg3 state (XK: identity not yet known)
|
||||
// Node 1 should now have a session in AwaitingMsg3 state (XX: identity not yet known)
|
||||
assert_eq!(nodes[1].node.session_count(), 1);
|
||||
assert!(nodes[1]
|
||||
.node
|
||||
@@ -213,7 +204,7 @@ async fn test_session_direct_peer_data_transfer() {
|
||||
let node1_addr = *nodes[1].node.node_addr();
|
||||
let node1_pubkey = nodes[1].node.identity().pubkey_full();
|
||||
|
||||
// Establish session (XK: 3 messages — Setup, Ack, Msg3)
|
||||
// Establish session (XX: 3 messages — Setup, Ack, Msg3)
|
||||
nodes[0]
|
||||
.node
|
||||
.initiate_session(node1_addr, node1_pubkey)
|
||||
@@ -286,7 +277,7 @@ async fn test_session_3node_forwarded_handshake() {
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
process_available_packets(&mut nodes).await;
|
||||
|
||||
// Node 2 should have an AwaitingMsg3 session (XK: identity not yet known)
|
||||
// Node 2 should have an AwaitingMsg3 session (XX: identity not yet known)
|
||||
assert!(
|
||||
nodes[2].node.get_session(&node0_addr).is_some(),
|
||||
"Node 2 should have a session entry for Node 0"
|
||||
@@ -388,7 +379,7 @@ async fn test_session_3node_forwarded_data() {
|
||||
process_available_packets(&mut nodes).await;
|
||||
}
|
||||
|
||||
// Node 2 should be Established (transitioned during XK handshake msg3)
|
||||
// Node 2 should be Established (transitioned during XX handshake msg3)
|
||||
assert!(nodes[2]
|
||||
.node
|
||||
.get_session(&node0_addr)
|
||||
@@ -637,7 +628,7 @@ async fn test_session_100_nodes() {
|
||||
drain_to_quiescence(&mut nodes).await;
|
||||
|
||||
// Reverse: responder → initiator
|
||||
// (Responder should already be Established after XK msg3)
|
||||
// (Responder should already be Established after XX msg3)
|
||||
let rev_payload = format!("rev-{}", pair_idx).into_bytes();
|
||||
let rev_ipv6 = build_ipv6_packet(&dst_fips, &src_fips, &rev_payload);
|
||||
match nodes[dst]
|
||||
@@ -890,7 +881,7 @@ async fn test_session_100_nodes() {
|
||||
);
|
||||
assert_eq!(
|
||||
send_reverse_err, 0,
|
||||
"All reverse sends should succeed (responder Established after XK msg3)"
|
||||
"All reverse sends should succeed (responder Established after XX msg3)"
|
||||
);
|
||||
assert_eq!(
|
||||
fwd_delivered, send_forward_ok,
|
||||
@@ -985,7 +976,7 @@ async fn test_tun_outbound_established_session() {
|
||||
let src_fips = crate::FipsAddress::from_node_addr(&node0_addr);
|
||||
let dst_fips = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||
|
||||
// Establish session (XK: 3 messages — Setup, Ack, Msg3)
|
||||
// Establish session (XX: 3 messages — Setup, Ack, Msg3)
|
||||
nodes[0].node.initiate_session(node1_addr, node1_pubkey).await.unwrap();
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
process_available_packets(&mut nodes).await; // Setup → Node 1
|
||||
@@ -1191,17 +1182,14 @@ async fn test_tun_outbound_pending_queue_flush() {
|
||||
// Unit tests: Session idle timeout
|
||||
// ============================================================================
|
||||
|
||||
/// Helper: complete a Noise IK handshake and return the initiator's NoiseSession.
|
||||
/// Helper: complete a Noise XX handshake and return the initiator's NoiseSession.
|
||||
fn make_noise_session(
|
||||
our_identity: &Identity,
|
||||
remote_identity: &Identity,
|
||||
) -> crate::noise::NoiseSession {
|
||||
use crate::noise::HandshakeState;
|
||||
|
||||
let mut initiator = HandshakeState::new_initiator(
|
||||
our_identity.keypair(),
|
||||
remote_identity.pubkey_full(),
|
||||
);
|
||||
let mut initiator = HandshakeState::new_initiator(our_identity.keypair());
|
||||
let mut responder = HandshakeState::new_responder(remote_identity.keypair());
|
||||
|
||||
// Set epochs for both sides (required for handshake message encryption)
|
||||
@@ -1216,6 +1204,8 @@ fn make_noise_session(
|
||||
responder.read_message_1(&msg1).unwrap();
|
||||
let msg2 = responder.write_message_2().unwrap();
|
||||
initiator.read_message_2(&msg2).unwrap();
|
||||
let msg3 = initiator.write_message_3().unwrap();
|
||||
responder.read_message_3(&msg3).unwrap();
|
||||
|
||||
initiator.into_session().unwrap()
|
||||
}
|
||||
@@ -1281,10 +1271,7 @@ fn test_purge_idle_sessions_ignores_initiating() {
|
||||
let remote = Identity::generate();
|
||||
let remote_addr = *remote.node_addr();
|
||||
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
node.identity().keypair(),
|
||||
remote.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(node.identity().keypair());
|
||||
let entry = crate::node::session::SessionEntry::new(
|
||||
remote_addr,
|
||||
remote.pubkey_full(),
|
||||
@@ -1401,10 +1388,7 @@ fn test_coords_warmup_counter_default_zero_on_new() {
|
||||
let identity_a = Identity::generate();
|
||||
let identity_b = Identity::generate();
|
||||
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
identity_a.keypair(),
|
||||
identity_b.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(identity_a.keypair());
|
||||
|
||||
let entry = crate::node::session::SessionEntry::new(
|
||||
*identity_b.node_addr(),
|
||||
@@ -1546,10 +1530,7 @@ fn test_session_entry_handshake_payload_storage() {
|
||||
let identity_a = Identity::generate();
|
||||
let identity_b = Identity::generate();
|
||||
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
identity_a.keypair(),
|
||||
identity_b.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(identity_a.keypair());
|
||||
|
||||
let mut entry = crate::node::session::SessionEntry::new(
|
||||
*identity_b.node_addr(),
|
||||
@@ -1581,10 +1562,7 @@ fn test_session_entry_resend_tracking() {
|
||||
let identity_a = Identity::generate();
|
||||
let identity_b = Identity::generate();
|
||||
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
identity_a.keypair(),
|
||||
identity_b.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(identity_a.keypair());
|
||||
|
||||
let mut entry = crate::node::session::SessionEntry::new(
|
||||
*identity_b.node_addr(),
|
||||
@@ -1615,10 +1593,7 @@ fn test_session_entry_clear_handshake_payload() {
|
||||
let identity_a = Identity::generate();
|
||||
let identity_b = Identity::generate();
|
||||
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
identity_a.keypair(),
|
||||
identity_b.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(identity_a.keypair());
|
||||
|
||||
let mut entry = crate::node::session::SessionEntry::new(
|
||||
*identity_b.node_addr(),
|
||||
@@ -1649,10 +1624,7 @@ async fn test_session_handshake_timeout() {
|
||||
let mut node = make_node();
|
||||
|
||||
let identity_b = Identity::generate();
|
||||
let handshake = HandshakeState::new_initiator(
|
||||
node.identity.keypair(),
|
||||
identity_b.pubkey_full(),
|
||||
);
|
||||
let handshake = HandshakeState::new_initiator(node.identity.keypair());
|
||||
|
||||
let dest_addr = *identity_b.node_addr();
|
||||
|
||||
@@ -1690,7 +1662,7 @@ async fn test_session_awaiting_msg3_timeout() {
|
||||
let identity_a = Identity::generate();
|
||||
let identity_b = Identity::generate();
|
||||
|
||||
let handshake = HandshakeState::new_xx_responder(
|
||||
let handshake = HandshakeState::new_responder(
|
||||
identity_b.keypair(),
|
||||
);
|
||||
|
||||
@@ -1733,7 +1705,7 @@ async fn test_tun_outbound_path_mtu_generates_ptb() {
|
||||
let src_fips = crate::FipsAddress::from_node_addr(&node0_addr);
|
||||
let dst_fips = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||
|
||||
// Establish session (XK: 3 messages — Setup, Ack, Msg3)
|
||||
// Establish session (XX: 3 messages — Setup, Ack, Msg3)
|
||||
nodes[0].node.initiate_session(node1_addr, node1_pubkey).await.unwrap();
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
process_available_packets(&mut nodes).await;
|
||||
|
||||
+16
-16
@@ -19,7 +19,7 @@
|
||||
//! | 0x3 | Noise XX msg3 | 85+ bytes | Handshake completion |
|
||||
|
||||
use crate::utils::index::SessionIndex;
|
||||
use crate::noise::{XX_HANDSHAKE_MSG1_SIZE, XX_HANDSHAKE_MSG2_SIZE, XX_HANDSHAKE_MSG3_SIZE, TAG_SIZE};
|
||||
use crate::noise::{HANDSHAKE_MSG1_SIZE, HANDSHAKE_MSG2_SIZE, HANDSHAKE_MSG3_SIZE, TAG_SIZE};
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
@@ -47,15 +47,15 @@ pub const COMMON_PREFIX_SIZE: usize = 4;
|
||||
pub const ESTABLISHED_HEADER_SIZE: usize = 16;
|
||||
|
||||
/// 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
|
||||
pub const MSG1_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + HANDSHAKE_MSG1_SIZE; // 41 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
|
||||
pub const MSG2_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + 4 + 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
|
||||
pub const MSG3_WIRE_SIZE: usize = COMMON_PREFIX_SIZE + 4 + 4 + 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
|
||||
@@ -395,7 +395,7 @@ impl Msg3Header {
|
||||
///
|
||||
/// 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(), XX_HANDSHAKE_MSG1_SIZE);
|
||||
debug_assert_eq!(noise_msg1.len(), HANDSHAKE_MSG1_SIZE);
|
||||
|
||||
let payload_len = (4 + noise_msg1.len()) as u16; // sender_idx + noise_msg1
|
||||
|
||||
@@ -413,7 +413,7 @@ pub fn build_msg1(sender_idx: SessionIndex, noise_msg1: &[u8]) -> Vec<u8> {
|
||||
/// 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);
|
||||
debug_assert!(noise_msg2.len() >= 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();
|
||||
@@ -433,7 +433,7 @@ pub fn build_msg2(sender_idx: SessionIndex, receiver_idx: SessionIndex, noise_ms
|
||||
/// 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);
|
||||
debug_assert!(noise_msg3.len() >= 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();
|
||||
@@ -575,7 +575,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_msg1_header_parse() {
|
||||
let sender_idx = SessionIndex::new(0xABCDEF01);
|
||||
let noise_msg1 = vec![0xbb; XX_HANDSHAKE_MSG1_SIZE];
|
||||
let noise_msg1 = vec![0xbb; HANDSHAKE_MSG1_SIZE];
|
||||
|
||||
let packet = build_msg1(sender_idx, &noise_msg1);
|
||||
|
||||
@@ -606,7 +606,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_msg1_header_nonzero_flags() {
|
||||
let mut packet = build_msg1(SessionIndex::new(1), &[0u8; XX_HANDSHAKE_MSG1_SIZE]);
|
||||
let mut packet = build_msg1(SessionIndex::new(1), &[0u8; HANDSHAKE_MSG1_SIZE]);
|
||||
packet[1] = 0x01; // flags must be zero during handshake
|
||||
assert!(Msg1Header::parse(&packet).is_none());
|
||||
}
|
||||
@@ -615,7 +615,7 @@ mod tests {
|
||||
fn test_msg2_header_parse() {
|
||||
let sender_idx = SessionIndex::new(0x11223344);
|
||||
let receiver_idx = SessionIndex::new(0x55667788);
|
||||
let noise_msg2 = vec![0xcc; XX_HANDSHAKE_MSG2_SIZE];
|
||||
let noise_msg2 = vec![0xcc; HANDSHAKE_MSG2_SIZE];
|
||||
|
||||
let packet = build_msg2(sender_idx, receiver_idx, &noise_msg2);
|
||||
|
||||
@@ -664,7 +664,7 @@ mod tests {
|
||||
fn test_roundtrip_indices() {
|
||||
let idx = SessionIndex::new(0xDEADBEEF);
|
||||
|
||||
let msg1 = build_msg1(idx, &[0u8; XX_HANDSHAKE_MSG1_SIZE]);
|
||||
let msg1 = build_msg1(idx, &[0u8; HANDSHAKE_MSG1_SIZE]);
|
||||
let parsed = Msg1Header::parse(&msg1).unwrap();
|
||||
assert_eq!(parsed.sender_idx.as_u32(), 0xDEADBEEF);
|
||||
|
||||
@@ -718,7 +718,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_payload_len_in_msg1() {
|
||||
let packet = build_msg1(SessionIndex::new(1), &[0u8; XX_HANDSHAKE_MSG1_SIZE]);
|
||||
let packet = build_msg1(SessionIndex::new(1), &[0u8; HANDSHAKE_MSG1_SIZE]);
|
||||
let prefix = CommonPrefix::parse(&packet).unwrap();
|
||||
// payload_len = sender_idx(4) + noise_msg1(33) = 37
|
||||
assert_eq!(prefix.payload_len, 37);
|
||||
@@ -729,7 +729,7 @@ mod tests {
|
||||
let packet = build_msg2(
|
||||
SessionIndex::new(1),
|
||||
SessionIndex::new(2),
|
||||
&[0u8; XX_HANDSHAKE_MSG2_SIZE],
|
||||
&[0u8; HANDSHAKE_MSG2_SIZE],
|
||||
);
|
||||
let prefix = CommonPrefix::parse(&packet).unwrap();
|
||||
// payload_len = sender_idx(4) + receiver_idx(4) + noise_msg2(106) = 114
|
||||
@@ -740,7 +740,7 @@ mod tests {
|
||||
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 noise_msg3 = vec![0xdd; HANDSHAKE_MSG3_SIZE];
|
||||
|
||||
let packet = build_msg3(sender_idx, receiver_idx, &noise_msg3);
|
||||
|
||||
@@ -779,7 +779,7 @@ mod tests {
|
||||
let mut packet = build_msg3(
|
||||
SessionIndex::new(1),
|
||||
SessionIndex::new(2),
|
||||
&[0u8; XX_HANDSHAKE_MSG3_SIZE],
|
||||
&[0u8; HANDSHAKE_MSG3_SIZE],
|
||||
);
|
||||
packet[1] = 0x01; // flags must be zero during handshake
|
||||
assert!(Msg3Header::parse(&packet).is_none());
|
||||
@@ -790,7 +790,7 @@ mod tests {
|
||||
let packet = build_msg3(
|
||||
SessionIndex::new(1),
|
||||
SessionIndex::new(2),
|
||||
&[0u8; XX_HANDSHAKE_MSG3_SIZE],
|
||||
&[0u8; HANDSHAKE_MSG3_SIZE],
|
||||
);
|
||||
let prefix = CommonPrefix::parse(&packet).unwrap();
|
||||
// payload_len = sender_idx(4) + receiver_idx(4) + noise_msg3(73) = 81
|
||||
|
||||
+47
-694
@@ -1,9 +1,7 @@
|
||||
use super::{
|
||||
CipherState, HandshakeProgress, HandshakeRole, NoiseError, NoisePattern, NoiseSession,
|
||||
EPOCH_ENCRYPTED_SIZE, EPOCH_SIZE, HANDSHAKE_MSG1_SIZE, HANDSHAKE_MSG2_SIZE,
|
||||
PROTOCOL_NAME_IK, PROTOCOL_NAME_XK, PROTOCOL_NAME_XX, PUBKEY_SIZE,
|
||||
XK_HANDSHAKE_MSG1_SIZE, XK_HANDSHAKE_MSG2_SIZE, XK_HANDSHAKE_MSG3_SIZE,
|
||||
XX_HANDSHAKE_MSG1_SIZE, XX_HANDSHAKE_MSG2_SIZE, XX_HANDSHAKE_MSG3_SIZE,
|
||||
HANDSHAKE_MSG3_SIZE, PROTOCOL_NAME_XX, PUBKEY_SIZE,
|
||||
};
|
||||
use hkdf::Hkdf;
|
||||
use rand::Rng;
|
||||
@@ -103,7 +101,7 @@ impl SymmetricState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handshake state for Noise IK, XK, and XX patterns.
|
||||
/// Handshake state for the Noise XX pattern.
|
||||
pub struct HandshakeState {
|
||||
/// Which Noise pattern is being used.
|
||||
pattern: NoisePattern,
|
||||
@@ -118,10 +116,6 @@ pub struct HandshakeState {
|
||||
/// Our ephemeral keypair (generated at handshake start).
|
||||
ephemeral_keypair: Option<Keypair>,
|
||||
/// Remote static public key.
|
||||
/// For IK initiator: known before handshake (from config).
|
||||
/// For IK responder: learned from message 1.
|
||||
/// For XK initiator: known before handshake (from config).
|
||||
/// For XK responder: learned from message 3.
|
||||
/// For XX initiator: learned from message 2.
|
||||
/// For XX responder: learned from message 3.
|
||||
remote_static: Option<PublicKey>,
|
||||
@@ -136,131 +130,46 @@ pub struct HandshakeState {
|
||||
}
|
||||
|
||||
impl HandshakeState {
|
||||
/// Normalize a compressed public key to even parity for pre-message hashing.
|
||||
/// Create a new XX handshake as initiator.
|
||||
///
|
||||
/// Nostr npubs encode x-only keys (no parity). The Noise IK pre-message
|
||||
/// mixes the responder's static key into the hash before any messages.
|
||||
/// Both sides must mix identical bytes. Since the initiator may only have
|
||||
/// the x-only key (from an npub), we normalize to even parity (0x02 prefix)
|
||||
/// so the hash chain matches regardless of the key's actual parity.
|
||||
///
|
||||
/// This does NOT affect ECDH operations (which use x-coordinate-only output)
|
||||
/// or the keys sent in handshake messages (which use actual parity).
|
||||
fn normalize_for_premessage(pubkey: &PublicKey) -> [u8; PUBKEY_SIZE] {
|
||||
let mut bytes = pubkey.serialize();
|
||||
bytes[0] = 0x02; // Force even parity
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Create a new IK handshake as initiator.
|
||||
///
|
||||
/// The initiator knows the responder's static key and will send first.
|
||||
/// Used by FMP (link layer).
|
||||
pub fn new_initiator(static_keypair: Keypair, remote_static: PublicKey) -> Self {
|
||||
/// XX: neither side knows the other's static key. No pre-message.
|
||||
pub fn new_initiator(static_keypair: Keypair) -> Self {
|
||||
let secp = Secp256k1::new();
|
||||
let mut state = Self {
|
||||
pattern: NoisePattern::Ik,
|
||||
Self {
|
||||
pattern: NoisePattern::Xx,
|
||||
role: HandshakeRole::Initiator,
|
||||
progress: HandshakeProgress::Initial,
|
||||
symmetric: SymmetricState::initialize(PROTOCOL_NAME_IK),
|
||||
symmetric: SymmetricState::initialize(PROTOCOL_NAME_XX),
|
||||
static_keypair,
|
||||
ephemeral_keypair: None,
|
||||
remote_static: Some(remote_static),
|
||||
remote_static: None,
|
||||
remote_ephemeral: None,
|
||||
secp,
|
||||
local_epoch: None,
|
||||
remote_epoch: None,
|
||||
};
|
||||
|
||||
// Mix in pre-message: <- s (responder's static is known)
|
||||
// Normalize to even parity so initiator and responder hash chains match
|
||||
// even when the initiator only has the x-only key (from npub).
|
||||
let normalized = Self::normalize_for_premessage(&remote_static);
|
||||
state.symmetric.mix_hash(&normalized);
|
||||
|
||||
state
|
||||
}
|
||||
// No pre-message: neither side's static is mixed into hash.
|
||||
}
|
||||
|
||||
/// Create a new IK handshake as responder.
|
||||
/// Create a new XX handshake as responder.
|
||||
///
|
||||
/// The responder does NOT know the initiator's static key - it will be
|
||||
/// learned from message 1. Used by FMP (link layer).
|
||||
/// XX: neither side knows the other's static key. No pre-message.
|
||||
pub fn new_responder(static_keypair: Keypair) -> Self {
|
||||
let secp = Secp256k1::new();
|
||||
let mut state = Self {
|
||||
pattern: NoisePattern::Ik,
|
||||
Self {
|
||||
pattern: NoisePattern::Xx,
|
||||
role: HandshakeRole::Responder,
|
||||
progress: HandshakeProgress::Initial,
|
||||
symmetric: SymmetricState::initialize(PROTOCOL_NAME_IK),
|
||||
symmetric: SymmetricState::initialize(PROTOCOL_NAME_XX),
|
||||
static_keypair,
|
||||
ephemeral_keypair: None,
|
||||
remote_static: None, // Will learn from message 1
|
||||
remote_static: None,
|
||||
remote_ephemeral: None,
|
||||
secp,
|
||||
local_epoch: None,
|
||||
remote_epoch: None,
|
||||
};
|
||||
|
||||
// Mix in pre-message: <- s (our static, since we're responder)
|
||||
// Normalize to even parity to match initiator's hash chain.
|
||||
let normalized = Self::normalize_for_premessage(&state.static_keypair.public_key());
|
||||
state.symmetric.mix_hash(&normalized);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
/// Create a new XK handshake as initiator.
|
||||
///
|
||||
/// The initiator knows the responder's static key. XK defers the
|
||||
/// initiator's static key reveal to msg3. Used by FSP (session layer).
|
||||
pub fn new_xk_initiator(static_keypair: Keypair, remote_static: PublicKey) -> Self {
|
||||
let secp = Secp256k1::new();
|
||||
let mut state = Self {
|
||||
pattern: NoisePattern::Xk,
|
||||
role: HandshakeRole::Initiator,
|
||||
progress: HandshakeProgress::Initial,
|
||||
symmetric: SymmetricState::initialize(PROTOCOL_NAME_XK),
|
||||
static_keypair,
|
||||
ephemeral_keypair: None,
|
||||
remote_static: Some(remote_static),
|
||||
remote_ephemeral: None,
|
||||
secp,
|
||||
local_epoch: None,
|
||||
remote_epoch: None,
|
||||
};
|
||||
|
||||
// Mix in pre-message: <- s (responder's static is known)
|
||||
let normalized = Self::normalize_for_premessage(&remote_static);
|
||||
state.symmetric.mix_hash(&normalized);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
/// Create a new XK handshake as responder.
|
||||
///
|
||||
/// The responder does NOT know the initiator's static key - it will be
|
||||
/// learned from message 3. Used by FSP (session layer).
|
||||
pub fn new_xk_responder(static_keypair: Keypair) -> Self {
|
||||
let secp = Secp256k1::new();
|
||||
let mut state = Self {
|
||||
pattern: NoisePattern::Xk,
|
||||
role: HandshakeRole::Responder,
|
||||
progress: HandshakeProgress::Initial,
|
||||
symmetric: SymmetricState::initialize(PROTOCOL_NAME_XK),
|
||||
static_keypair,
|
||||
ephemeral_keypair: None,
|
||||
remote_static: None, // Will learn from message 3
|
||||
remote_ephemeral: None,
|
||||
secp,
|
||||
local_epoch: None,
|
||||
remote_epoch: None,
|
||||
};
|
||||
|
||||
// Mix in pre-message: <- s (our static, since we're responder)
|
||||
let normalized = Self::normalize_for_premessage(&state.static_keypair.public_key());
|
||||
state.symmetric.mix_hash(&normalized);
|
||||
|
||||
state
|
||||
}
|
||||
// No pre-message: neither side's static is mixed into hash.
|
||||
}
|
||||
|
||||
/// Get our role.
|
||||
@@ -278,7 +187,7 @@ impl HandshakeState {
|
||||
self.progress == HandshakeProgress::Complete
|
||||
}
|
||||
|
||||
/// Get the remote static key (available after message 1 for responder).
|
||||
/// Get the remote static key (available after message 2 for initiator, message 3 for responder).
|
||||
pub fn remote_static(&self) -> Option<&PublicKey> {
|
||||
self.remote_static.as_ref()
|
||||
}
|
||||
@@ -326,12 +235,11 @@ impl HandshakeState {
|
||||
|
||||
/// Write message 1 (initiator only).
|
||||
///
|
||||
/// Message 1 contains:
|
||||
/// XX msg1: `-> e`
|
||||
/// - e: ephemeral public key (33 bytes)
|
||||
/// - encrypted s: our static public key encrypted (33 + 16 = 49 bytes)
|
||||
/// - encrypted epoch: startup epoch for restart detection (8 + 16 = 24 bytes)
|
||||
/// - No DH operations (responder's static is unknown)
|
||||
///
|
||||
/// Total: 106 bytes
|
||||
/// Total: 33 bytes
|
||||
pub fn write_message_1(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.role != HandshakeRole::Initiator {
|
||||
return Err(NoiseError::WrongState {
|
||||
@@ -346,9 +254,6 @@ impl HandshakeState {
|
||||
});
|
||||
}
|
||||
|
||||
let remote_static = self.remote_static.expect("initiator must have remote static");
|
||||
let epoch = self.local_epoch.expect("local epoch must be set before write_message_1");
|
||||
|
||||
// Generate ephemeral keypair
|
||||
self.generate_ephemeral();
|
||||
let ephemeral = self.ephemeral_keypair.as_ref().unwrap();
|
||||
@@ -360,23 +265,7 @@ impl HandshakeState {
|
||||
message.extend_from_slice(&e_pub);
|
||||
self.symmetric.mix_hash(&e_pub);
|
||||
|
||||
// -> es: DH(e, rs), mix into key
|
||||
let es = self.ecdh(&ephemeral.secret_key(), &remote_static);
|
||||
self.symmetric.mix_key(&es);
|
||||
|
||||
// -> s: encrypt our static and send
|
||||
let our_static = self.static_keypair.public_key().serialize();
|
||||
let encrypted_static = self.symmetric.encrypt_and_hash(&our_static)?;
|
||||
message.extend_from_slice(&encrypted_static);
|
||||
|
||||
// -> ss: DH(s, rs), mix into key
|
||||
let ss = self.ecdh(&self.static_keypair.secret_key(), &remote_static);
|
||||
self.symmetric.mix_key(&ss);
|
||||
|
||||
// -> epoch: encrypt startup epoch for restart detection
|
||||
let encrypted_epoch = self.symmetric.encrypt_and_hash(&epoch)?;
|
||||
debug_assert_eq!(encrypted_epoch.len(), EPOCH_ENCRYPTED_SIZE);
|
||||
message.extend_from_slice(&encrypted_epoch);
|
||||
// No DH here — responder's static is unknown in XX
|
||||
|
||||
self.progress = HandshakeProgress::Message1Done;
|
||||
|
||||
@@ -385,7 +274,7 @@ impl HandshakeState {
|
||||
|
||||
/// Read message 1 (responder only).
|
||||
///
|
||||
/// Processes the initiator's first message and learns their identity and epoch.
|
||||
/// Parses the initiator's ephemeral key. No identity learned.
|
||||
pub fn read_message_1(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
if self.role != HandshakeRole::Responder {
|
||||
return Err(NoiseError::WrongState {
|
||||
@@ -412,31 +301,7 @@ impl HandshakeState {
|
||||
self.remote_ephemeral = Some(re);
|
||||
self.symmetric.mix_hash(&message[..PUBKEY_SIZE]);
|
||||
|
||||
// -> es: DH(s, re), mix into key
|
||||
// (responder uses their static with initiator's ephemeral)
|
||||
let es = self.ecdh(&self.static_keypair.secret_key(), &re);
|
||||
self.symmetric.mix_key(&es);
|
||||
|
||||
// -> s: decrypt initiator's static
|
||||
let encrypted_static_end = PUBKEY_SIZE + PUBKEY_SIZE + super::TAG_SIZE;
|
||||
let encrypted_static = &message[PUBKEY_SIZE..encrypted_static_end];
|
||||
let decrypted_static = self.symmetric.decrypt_and_hash(encrypted_static)?;
|
||||
let rs =
|
||||
PublicKey::from_slice(&decrypted_static).map_err(|_| NoiseError::InvalidPublicKey)?;
|
||||
self.remote_static = Some(rs);
|
||||
|
||||
// -> ss: DH(s, rs), mix into key
|
||||
let ss = self.ecdh(&self.static_keypair.secret_key(), &rs);
|
||||
self.symmetric.mix_key(&ss);
|
||||
|
||||
// -> epoch: decrypt initiator's startup epoch
|
||||
let encrypted_epoch = &message[encrypted_static_end..];
|
||||
debug_assert_eq!(encrypted_epoch.len(), EPOCH_ENCRYPTED_SIZE);
|
||||
let decrypted_epoch = self.symmetric.decrypt_and_hash(encrypted_epoch)?;
|
||||
debug_assert_eq!(decrypted_epoch.len(), EPOCH_SIZE);
|
||||
let mut epoch = [0u8; EPOCH_SIZE];
|
||||
epoch.copy_from_slice(&decrypted_epoch);
|
||||
self.remote_epoch = Some(epoch);
|
||||
// No DH here — responder's static is not in pre-message for XX
|
||||
|
||||
self.progress = HandshakeProgress::Message1Done;
|
||||
|
||||
@@ -445,11 +310,14 @@ impl HandshakeState {
|
||||
|
||||
/// Write message 2 (responder only).
|
||||
///
|
||||
/// Message 2 contains:
|
||||
/// XX msg2: `<- e, ee, s, es` + encrypted epoch
|
||||
/// - e: ephemeral public key (33 bytes)
|
||||
/// - encrypted epoch: startup epoch for restart detection (8 + 16 = 24 bytes)
|
||||
/// - ee: DH(e_priv, re_pub), mix_key
|
||||
/// - s: encrypt_and_hash(s_pub) — encrypted static (49 bytes)
|
||||
/// - es: DH(s_priv, re_pub), mix_key
|
||||
/// - encrypted epoch (24 bytes)
|
||||
///
|
||||
/// Total: 57 bytes
|
||||
/// Total: 106 bytes
|
||||
pub fn write_message_2(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.role != HandshakeRole::Responder {
|
||||
return Err(NoiseError::WrongState {
|
||||
@@ -482,521 +350,6 @@ impl HandshakeState {
|
||||
let ee = self.ecdh(&ephemeral.secret_key(), &re);
|
||||
self.symmetric.mix_key(&ee);
|
||||
|
||||
// <- se: DH(s, re), mix into key
|
||||
let se = self.ecdh(&self.static_keypair.secret_key(), &re);
|
||||
self.symmetric.mix_key(&se);
|
||||
|
||||
// <- epoch: encrypt startup epoch for restart detection
|
||||
let encrypted_epoch = self.symmetric.encrypt_and_hash(&epoch)?;
|
||||
debug_assert_eq!(encrypted_epoch.len(), EPOCH_ENCRYPTED_SIZE);
|
||||
message.extend_from_slice(&encrypted_epoch);
|
||||
|
||||
self.progress = HandshakeProgress::Complete;
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Read message 2 (initiator only).
|
||||
///
|
||||
/// Processes the responder's message and completes the handshake.
|
||||
pub fn read_message_2(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
if self.role != HandshakeRole::Initiator {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "initiator".to_string(),
|
||||
got: "responder".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Message1Done {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Message1Done.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
if message.len() != HANDSHAKE_MSG2_SIZE {
|
||||
return Err(NoiseError::MessageTooShort {
|
||||
expected: HANDSHAKE_MSG2_SIZE,
|
||||
got: message.len(),
|
||||
});
|
||||
}
|
||||
|
||||
// <- e: parse remote ephemeral, mix into hash
|
||||
let e_pub = &message[..PUBKEY_SIZE];
|
||||
let re = PublicKey::from_slice(e_pub).map_err(|_| NoiseError::InvalidPublicKey)?;
|
||||
self.remote_ephemeral = Some(re);
|
||||
self.symmetric.mix_hash(e_pub);
|
||||
|
||||
// <- ee: DH(e, re), mix into key
|
||||
let ephemeral = self.ephemeral_keypair.as_ref().unwrap();
|
||||
let ee = self.ecdh(&ephemeral.secret_key(), &re);
|
||||
self.symmetric.mix_key(&ee);
|
||||
|
||||
// <- se: DH(e, rs), mix into key
|
||||
// (initiator uses their ephemeral with responder's static)
|
||||
let rs = self.remote_static.expect("initiator has remote static");
|
||||
let se = self.ecdh(&ephemeral.secret_key(), &rs);
|
||||
self.symmetric.mix_key(&se);
|
||||
|
||||
// <- epoch: decrypt responder's startup epoch
|
||||
let encrypted_epoch = &message[PUBKEY_SIZE..];
|
||||
debug_assert_eq!(encrypted_epoch.len(), EPOCH_ENCRYPTED_SIZE);
|
||||
let decrypted_epoch = self.symmetric.decrypt_and_hash(encrypted_epoch)?;
|
||||
debug_assert_eq!(decrypted_epoch.len(), EPOCH_SIZE);
|
||||
let mut epoch = [0u8; EPOCH_SIZE];
|
||||
epoch.copy_from_slice(&decrypted_epoch);
|
||||
self.remote_epoch = Some(epoch);
|
||||
|
||||
self.progress = HandshakeProgress::Complete;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// XK Pattern Methods (Session Layer)
|
||||
// ========================================================================
|
||||
|
||||
/// Write XK message 1 (initiator only).
|
||||
///
|
||||
/// XK msg1: `-> e, es`
|
||||
/// - e: ephemeral public key (33 bytes)
|
||||
/// - es: DH(e_priv, rs_pub), mix_key
|
||||
///
|
||||
/// Total: 33 bytes (ephemeral only — no static, no epoch)
|
||||
pub fn write_xk_message_1(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.role != HandshakeRole::Initiator {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "initiator".to_string(),
|
||||
got: "responder".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Initial {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Initial.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let remote_static = self.remote_static.expect("initiator must have remote static");
|
||||
|
||||
// Generate ephemeral keypair
|
||||
self.generate_ephemeral();
|
||||
let ephemeral = self.ephemeral_keypair.as_ref().unwrap();
|
||||
let e_pub = ephemeral.public_key().serialize();
|
||||
|
||||
let mut message = Vec::with_capacity(XK_HANDSHAKE_MSG1_SIZE);
|
||||
|
||||
// -> e: send ephemeral, mix into hash
|
||||
message.extend_from_slice(&e_pub);
|
||||
self.symmetric.mix_hash(&e_pub);
|
||||
|
||||
// -> es: DH(e, rs), mix into key
|
||||
let es = self.ecdh(&ephemeral.secret_key(), &remote_static);
|
||||
self.symmetric.mix_key(&es);
|
||||
|
||||
self.progress = HandshakeProgress::Message1Done;
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Read XK message 1 (responder only).
|
||||
///
|
||||
/// Processes the initiator's first message. Does NOT learn initiator's
|
||||
/// identity (that comes in msg3).
|
||||
pub fn read_xk_message_1(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
if self.role != HandshakeRole::Responder {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "responder".to_string(),
|
||||
got: "initiator".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Initial {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Initial.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
if message.len() != XK_HANDSHAKE_MSG1_SIZE {
|
||||
return Err(NoiseError::MessageTooShort {
|
||||
expected: XK_HANDSHAKE_MSG1_SIZE,
|
||||
got: message.len(),
|
||||
});
|
||||
}
|
||||
|
||||
// -> e: parse remote ephemeral, mix into hash
|
||||
let re = PublicKey::from_slice(&message[..PUBKEY_SIZE])
|
||||
.map_err(|_| NoiseError::InvalidPublicKey)?;
|
||||
self.remote_ephemeral = Some(re);
|
||||
self.symmetric.mix_hash(&message[..PUBKEY_SIZE]);
|
||||
|
||||
// -> es: DH(s, re), mix into key
|
||||
// (responder uses their static with initiator's ephemeral)
|
||||
let es = self.ecdh(&self.static_keypair.secret_key(), &re);
|
||||
self.symmetric.mix_key(&es);
|
||||
|
||||
self.progress = HandshakeProgress::Message1Done;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write XK message 2 (responder only).
|
||||
///
|
||||
/// XK msg2: `<- e, ee` + encrypted epoch
|
||||
/// - e: ephemeral public key (33 bytes)
|
||||
/// - ee: DH(e_priv, re_pub), mix_key
|
||||
/// - encrypted epoch (24 bytes)
|
||||
///
|
||||
/// Total: 57 bytes
|
||||
pub fn write_xk_message_2(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.role != HandshakeRole::Responder {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "responder".to_string(),
|
||||
got: "initiator".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Message1Done {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Message1Done.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let re = self.remote_ephemeral.expect("should have remote ephemeral");
|
||||
let epoch = self.local_epoch.expect("local epoch must be set before write_xk_message_2");
|
||||
|
||||
// Generate ephemeral keypair
|
||||
self.generate_ephemeral();
|
||||
let ephemeral = self.ephemeral_keypair.as_ref().unwrap();
|
||||
let e_pub = ephemeral.public_key().serialize();
|
||||
|
||||
let mut message = Vec::with_capacity(XK_HANDSHAKE_MSG2_SIZE);
|
||||
|
||||
// <- e: send ephemeral, mix into hash
|
||||
message.extend_from_slice(&e_pub);
|
||||
self.symmetric.mix_hash(&e_pub);
|
||||
|
||||
// <- ee: DH(e, re), mix into key
|
||||
let ee = self.ecdh(&ephemeral.secret_key(), &re);
|
||||
self.symmetric.mix_key(&ee);
|
||||
|
||||
// <- epoch: encrypt startup epoch for restart detection
|
||||
let encrypted_epoch = self.symmetric.encrypt_and_hash(&epoch)?;
|
||||
debug_assert_eq!(encrypted_epoch.len(), EPOCH_ENCRYPTED_SIZE);
|
||||
message.extend_from_slice(&encrypted_epoch);
|
||||
|
||||
self.progress = HandshakeProgress::Message2Done;
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Read XK message 2 (initiator only).
|
||||
///
|
||||
/// Processes the responder's message and extracts the responder's epoch.
|
||||
/// Does NOT complete the handshake — msg3 still needed.
|
||||
pub fn read_xk_message_2(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
if self.role != HandshakeRole::Initiator {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "initiator".to_string(),
|
||||
got: "responder".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Message1Done {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Message1Done.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
if message.len() != XK_HANDSHAKE_MSG2_SIZE {
|
||||
return Err(NoiseError::MessageTooShort {
|
||||
expected: XK_HANDSHAKE_MSG2_SIZE,
|
||||
got: message.len(),
|
||||
});
|
||||
}
|
||||
|
||||
// <- e: parse remote ephemeral, mix into hash
|
||||
let e_pub = &message[..PUBKEY_SIZE];
|
||||
let re = PublicKey::from_slice(e_pub).map_err(|_| NoiseError::InvalidPublicKey)?;
|
||||
self.remote_ephemeral = Some(re);
|
||||
self.symmetric.mix_hash(e_pub);
|
||||
|
||||
// <- ee: DH(e, re), mix into key
|
||||
let ephemeral = self.ephemeral_keypair.as_ref().unwrap();
|
||||
let ee = self.ecdh(&ephemeral.secret_key(), &re);
|
||||
self.symmetric.mix_key(&ee);
|
||||
|
||||
// <- epoch: decrypt responder's startup epoch
|
||||
let encrypted_epoch = &message[PUBKEY_SIZE..];
|
||||
debug_assert_eq!(encrypted_epoch.len(), EPOCH_ENCRYPTED_SIZE);
|
||||
let decrypted_epoch = self.symmetric.decrypt_and_hash(encrypted_epoch)?;
|
||||
debug_assert_eq!(decrypted_epoch.len(), EPOCH_SIZE);
|
||||
let mut epoch = [0u8; EPOCH_SIZE];
|
||||
epoch.copy_from_slice(&decrypted_epoch);
|
||||
self.remote_epoch = Some(epoch);
|
||||
|
||||
self.progress = HandshakeProgress::Message2Done;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write XK message 3 (initiator only).
|
||||
///
|
||||
/// XK msg3: `-> s, se` + encrypted epoch
|
||||
/// - s: encrypt_and_hash(s_pub) — encrypted static (49 bytes)
|
||||
/// - se: DH(s_priv, re_pub), mix_key
|
||||
/// - encrypted epoch (24 bytes)
|
||||
///
|
||||
/// Total: 73 bytes
|
||||
pub fn write_xk_message_3(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.role != HandshakeRole::Initiator {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "initiator".to_string(),
|
||||
got: "responder".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Message2Done {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Message2Done.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let re = self.remote_ephemeral.expect("should have remote ephemeral after msg2");
|
||||
let epoch = self.local_epoch.expect("local epoch must be set before write_xk_message_3");
|
||||
|
||||
let mut message = Vec::with_capacity(XK_HANDSHAKE_MSG3_SIZE);
|
||||
|
||||
// -> s: encrypt our static and send
|
||||
let our_static = self.static_keypair.public_key().serialize();
|
||||
let encrypted_static = self.symmetric.encrypt_and_hash(&our_static)?;
|
||||
message.extend_from_slice(&encrypted_static);
|
||||
|
||||
// -> se: DH(s, re), mix into key
|
||||
let se = self.ecdh(&self.static_keypair.secret_key(), &re);
|
||||
self.symmetric.mix_key(&se);
|
||||
|
||||
// -> epoch: encrypt startup epoch for restart detection
|
||||
let encrypted_epoch = self.symmetric.encrypt_and_hash(&epoch)?;
|
||||
debug_assert_eq!(encrypted_epoch.len(), EPOCH_ENCRYPTED_SIZE);
|
||||
message.extend_from_slice(&encrypted_epoch);
|
||||
|
||||
self.progress = HandshakeProgress::Complete;
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Read XK message 3 (responder only).
|
||||
///
|
||||
/// Processes the initiator's encrypted static key and epoch.
|
||||
/// After this, the responder learns the initiator's identity.
|
||||
pub fn read_xk_message_3(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
if self.role != HandshakeRole::Responder {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "responder".to_string(),
|
||||
got: "initiator".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Message2Done {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Message2Done.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
if message.len() != XK_HANDSHAKE_MSG3_SIZE {
|
||||
return Err(NoiseError::MessageTooShort {
|
||||
expected: XK_HANDSHAKE_MSG3_SIZE,
|
||||
got: message.len(),
|
||||
});
|
||||
}
|
||||
|
||||
// -> s: decrypt initiator's static
|
||||
let encrypted_static_end = PUBKEY_SIZE + super::TAG_SIZE;
|
||||
let encrypted_static = &message[..encrypted_static_end];
|
||||
let decrypted_static = self.symmetric.decrypt_and_hash(encrypted_static)?;
|
||||
let rs =
|
||||
PublicKey::from_slice(&decrypted_static).map_err(|_| NoiseError::InvalidPublicKey)?;
|
||||
self.remote_static = Some(rs);
|
||||
|
||||
// -> se: DH(e, rs), mix into key
|
||||
// (responder uses their ephemeral with initiator's now-known static)
|
||||
let ephemeral = self.ephemeral_keypair.as_ref().expect("should have ephemeral after msg2");
|
||||
let se = self.ecdh(&ephemeral.secret_key(), &rs);
|
||||
self.symmetric.mix_key(&se);
|
||||
|
||||
// -> epoch: decrypt initiator's startup epoch
|
||||
let encrypted_epoch = &message[encrypted_static_end..];
|
||||
debug_assert_eq!(encrypted_epoch.len(), EPOCH_ENCRYPTED_SIZE);
|
||||
let decrypted_epoch = self.symmetric.decrypt_and_hash(encrypted_epoch)?;
|
||||
debug_assert_eq!(decrypted_epoch.len(), EPOCH_SIZE);
|
||||
let mut epoch = [0u8; EPOCH_SIZE];
|
||||
epoch.copy_from_slice(&decrypted_epoch);
|
||||
self.remote_epoch = Some(epoch);
|
||||
|
||||
self.progress = HandshakeProgress::Complete;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// XX Pattern Methods
|
||||
// ========================================================================
|
||||
|
||||
/// Create a new XX handshake as initiator.
|
||||
///
|
||||
/// XX: neither side knows the other's static key. No pre-message.
|
||||
pub fn new_xx_initiator(static_keypair: Keypair) -> Self {
|
||||
let secp = Secp256k1::new();
|
||||
Self {
|
||||
pattern: NoisePattern::Xx,
|
||||
role: HandshakeRole::Initiator,
|
||||
progress: HandshakeProgress::Initial,
|
||||
symmetric: SymmetricState::initialize(PROTOCOL_NAME_XX),
|
||||
static_keypair,
|
||||
ephemeral_keypair: None,
|
||||
remote_static: None,
|
||||
remote_ephemeral: None,
|
||||
secp,
|
||||
local_epoch: None,
|
||||
remote_epoch: None,
|
||||
}
|
||||
// No pre-message: neither side's static is mixed into hash.
|
||||
}
|
||||
|
||||
/// Create a new XX handshake as responder.
|
||||
///
|
||||
/// XX: neither side knows the other's static key. No pre-message.
|
||||
pub fn new_xx_responder(static_keypair: Keypair) -> Self {
|
||||
let secp = Secp256k1::new();
|
||||
Self {
|
||||
pattern: NoisePattern::Xx,
|
||||
role: HandshakeRole::Responder,
|
||||
progress: HandshakeProgress::Initial,
|
||||
symmetric: SymmetricState::initialize(PROTOCOL_NAME_XX),
|
||||
static_keypair,
|
||||
ephemeral_keypair: None,
|
||||
remote_static: None,
|
||||
remote_ephemeral: None,
|
||||
secp,
|
||||
local_epoch: None,
|
||||
remote_epoch: None,
|
||||
}
|
||||
// No pre-message: neither side's static is mixed into hash.
|
||||
}
|
||||
|
||||
/// Write XX message 1 (initiator only).
|
||||
///
|
||||
/// XX msg1: `-> e`
|
||||
/// - e: ephemeral public key (33 bytes)
|
||||
/// - No DH operations (responder's static is unknown)
|
||||
///
|
||||
/// Total: 33 bytes
|
||||
pub fn write_xx_message_1(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.role != HandshakeRole::Initiator {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "initiator".to_string(),
|
||||
got: "responder".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Initial {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Initial.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Generate ephemeral keypair
|
||||
self.generate_ephemeral();
|
||||
let ephemeral = self.ephemeral_keypair.as_ref().unwrap();
|
||||
let e_pub = ephemeral.public_key().serialize();
|
||||
|
||||
let mut message = Vec::with_capacity(XX_HANDSHAKE_MSG1_SIZE);
|
||||
|
||||
// -> e: send ephemeral, mix into hash
|
||||
message.extend_from_slice(&e_pub);
|
||||
self.symmetric.mix_hash(&e_pub);
|
||||
|
||||
// No DH here — responder's static is unknown in XX
|
||||
|
||||
self.progress = HandshakeProgress::Message1Done;
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Read XX message 1 (responder only).
|
||||
///
|
||||
/// Parses the initiator's ephemeral key. No identity learned.
|
||||
pub fn read_xx_message_1(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
if self.role != HandshakeRole::Responder {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "responder".to_string(),
|
||||
got: "initiator".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Initial {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Initial.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
if message.len() != XX_HANDSHAKE_MSG1_SIZE {
|
||||
return Err(NoiseError::MessageTooShort {
|
||||
expected: XX_HANDSHAKE_MSG1_SIZE,
|
||||
got: message.len(),
|
||||
});
|
||||
}
|
||||
|
||||
// -> e: parse remote ephemeral, mix into hash
|
||||
let re = PublicKey::from_slice(&message[..PUBKEY_SIZE])
|
||||
.map_err(|_| NoiseError::InvalidPublicKey)?;
|
||||
self.remote_ephemeral = Some(re);
|
||||
self.symmetric.mix_hash(&message[..PUBKEY_SIZE]);
|
||||
|
||||
// No DH here — responder's static is not in pre-message for XX
|
||||
|
||||
self.progress = HandshakeProgress::Message1Done;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write XX message 2 (responder only).
|
||||
///
|
||||
/// XX msg2: `<- e, ee, s, es` + encrypted epoch
|
||||
/// - e: ephemeral public key (33 bytes)
|
||||
/// - ee: DH(e_priv, re_pub), mix_key
|
||||
/// - s: encrypt_and_hash(s_pub) — encrypted static (49 bytes)
|
||||
/// - es: DH(s_priv, re_pub), mix_key
|
||||
/// - encrypted epoch (24 bytes)
|
||||
///
|
||||
/// Total: 106 bytes
|
||||
pub fn write_xx_message_2(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.role != HandshakeRole::Responder {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "responder".to_string(),
|
||||
got: "initiator".to_string(),
|
||||
});
|
||||
}
|
||||
if self.progress != HandshakeProgress::Message1Done {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: HandshakeProgress::Message1Done.to_string(),
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let re = self.remote_ephemeral.expect("should have remote ephemeral");
|
||||
let epoch = self.local_epoch.expect("local epoch must be set before write_xx_message_2");
|
||||
|
||||
// Generate ephemeral keypair
|
||||
self.generate_ephemeral();
|
||||
let ephemeral = self.ephemeral_keypair.as_ref().unwrap();
|
||||
let e_pub = ephemeral.public_key().serialize();
|
||||
|
||||
let mut message = Vec::with_capacity(XX_HANDSHAKE_MSG2_SIZE);
|
||||
|
||||
// <- e: send ephemeral, mix into hash
|
||||
message.extend_from_slice(&e_pub);
|
||||
self.symmetric.mix_hash(&e_pub);
|
||||
|
||||
// <- ee: DH(e, re), mix into key
|
||||
let ee = self.ecdh(&ephemeral.secret_key(), &re);
|
||||
self.symmetric.mix_key(&ee);
|
||||
|
||||
// <- s: encrypt our static and send
|
||||
let our_static = self.static_keypair.public_key().serialize();
|
||||
let encrypted_static = self.symmetric.encrypt_and_hash(&our_static)?;
|
||||
@@ -1016,11 +369,11 @@ impl HandshakeState {
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Read XX message 2 (initiator only).
|
||||
/// Read message 2 (initiator only).
|
||||
///
|
||||
/// Processes the responder's ephemeral and encrypted static key.
|
||||
/// After this, the initiator learns the responder's identity.
|
||||
pub fn read_xx_message_2(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
pub fn read_message_2(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
if self.role != HandshakeRole::Initiator {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "initiator".to_string(),
|
||||
@@ -1033,9 +386,9 @@ impl HandshakeState {
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
if message.len() != XX_HANDSHAKE_MSG2_SIZE {
|
||||
if message.len() != HANDSHAKE_MSG2_SIZE {
|
||||
return Err(NoiseError::MessageTooShort {
|
||||
expected: XX_HANDSHAKE_MSG2_SIZE,
|
||||
expected: HANDSHAKE_MSG2_SIZE,
|
||||
got: message.len(),
|
||||
});
|
||||
}
|
||||
@@ -1077,7 +430,7 @@ impl HandshakeState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write XX message 3 (initiator only).
|
||||
/// Write message 3 (initiator only).
|
||||
///
|
||||
/// XX msg3: `-> s, se` + encrypted epoch
|
||||
/// - s: encrypt_and_hash(s_pub) — encrypted static (49 bytes)
|
||||
@@ -1085,7 +438,7 @@ impl HandshakeState {
|
||||
/// - encrypted epoch (24 bytes)
|
||||
///
|
||||
/// Total: 73 bytes
|
||||
pub fn write_xx_message_3(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
pub fn write_message_3(&mut self) -> Result<Vec<u8>, NoiseError> {
|
||||
if self.role != HandshakeRole::Initiator {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "initiator".to_string(),
|
||||
@@ -1100,9 +453,9 @@ impl HandshakeState {
|
||||
}
|
||||
|
||||
let re = self.remote_ephemeral.expect("should have remote ephemeral after msg2");
|
||||
let epoch = self.local_epoch.expect("local epoch must be set before write_xx_message_3");
|
||||
let epoch = self.local_epoch.expect("local epoch must be set before write_message_3");
|
||||
|
||||
let mut message = Vec::with_capacity(XX_HANDSHAKE_MSG3_SIZE);
|
||||
let mut message = Vec::with_capacity(HANDSHAKE_MSG3_SIZE);
|
||||
|
||||
// -> s: encrypt our static and send
|
||||
let our_static = self.static_keypair.public_key().serialize();
|
||||
@@ -1123,11 +476,11 @@ impl HandshakeState {
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Read XX message 3 (responder only).
|
||||
/// Read message 3 (responder only).
|
||||
///
|
||||
/// Processes the initiator's encrypted static key and epoch.
|
||||
/// After this, the responder learns the initiator's identity.
|
||||
pub fn read_xx_message_3(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
pub fn read_message_3(&mut self, message: &[u8]) -> Result<(), NoiseError> {
|
||||
if self.role != HandshakeRole::Responder {
|
||||
return Err(NoiseError::WrongState {
|
||||
expected: "responder".to_string(),
|
||||
@@ -1140,9 +493,9 @@ impl HandshakeState {
|
||||
got: self.progress.to_string(),
|
||||
});
|
||||
}
|
||||
if message.len() != XX_HANDSHAKE_MSG3_SIZE {
|
||||
if message.len() != HANDSHAKE_MSG3_SIZE {
|
||||
return Err(NoiseError::MessageTooShort {
|
||||
expected: XX_HANDSHAKE_MSG3_SIZE,
|
||||
expected: HANDSHAKE_MSG3_SIZE,
|
||||
got: message.len(),
|
||||
});
|
||||
}
|
||||
@@ -1176,12 +529,12 @@ impl HandshakeState {
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Payload Encryption (for negotiation payload in XX msg2/msg3)
|
||||
// Payload Encryption (for negotiation payload in msg2/msg3)
|
||||
// ========================================================================
|
||||
|
||||
/// Encrypt additional payload and mix into the handshake hash.
|
||||
///
|
||||
/// Called after write_xx_message_2/3 to append negotiation payload.
|
||||
/// Called after write_message_2/3 to append negotiation payload.
|
||||
/// Must be called before `into_session()`.
|
||||
pub fn encrypt_payload(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, NoiseError> {
|
||||
self.symmetric.encrypt_and_hash(plaintext)
|
||||
@@ -1189,7 +542,7 @@ impl HandshakeState {
|
||||
|
||||
/// Decrypt additional payload and mix ciphertext into the handshake hash.
|
||||
///
|
||||
/// Called after read_xx_message_2/3 to extract negotiation payload.
|
||||
/// Called after read_message_2/3 to extract negotiation payload.
|
||||
/// Must be called before `into_session()`.
|
||||
pub fn decrypt_payload(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>, NoiseError> {
|
||||
self.symmetric.decrypt_and_hash(ciphertext)
|
||||
|
||||
+15
-72
@@ -1,36 +1,9 @@
|
||||
//! Noise Protocol Implementations for FIPS
|
||||
//! Noise Protocol Implementation for FIPS
|
||||
//!
|
||||
//! Implements Noise Protocol Framework patterns using secp256k1:
|
||||
//!
|
||||
//! - **IK pattern**: Used by FMP (link layer) for hop-by-hop peer authentication.
|
||||
//! The initiator knows the responder's static key and sends its encrypted
|
||||
//! static in msg1. Two-message handshake.
|
||||
//!
|
||||
//! - **XK pattern**: Used by FSP (session layer) for end-to-end sessions.
|
||||
//! The initiator knows the responder's static key but defers revealing its
|
||||
//! own identity until msg3, providing stronger identity hiding. Three-message
|
||||
//! handshake.
|
||||
//!
|
||||
//! - **XX pattern**: Neither side knows the other's static key. Both identities
|
||||
//! are revealed during the handshake: responder in msg2, initiator in msg3.
|
||||
//! Three-message handshake. Will replace IK/XK for both layers.
|
||||
//!
|
||||
//! ## IK Handshake Pattern (Link Layer)
|
||||
//!
|
||||
//! ```text
|
||||
//! <- s (pre-message: responder's static known)
|
||||
//! -> e, es, s, ss (msg1: ephemeral + encrypted static)
|
||||
//! <- e, ee, se (msg2: ephemeral)
|
||||
//! ```
|
||||
//!
|
||||
//! ## XK Handshake Pattern (Session Layer)
|
||||
//!
|
||||
//! ```text
|
||||
//! <- s (pre-message: responder's static known)
|
||||
//! -> e, es (msg1: ephemeral + DH with responder's static)
|
||||
//! <- e, ee (msg2: ephemeral + DH)
|
||||
//! -> s, se (msg3: encrypted static + DH)
|
||||
//! ```
|
||||
//! Implements the Noise XX pattern using secp256k1 for both link-layer and
|
||||
//! session-layer encryption. Neither side knows the other's static key before
|
||||
//! the handshake. Both identities are revealed during the handshake: responder
|
||||
//! in msg2, initiator in msg3. Three-message handshake.
|
||||
//!
|
||||
//! ## XX Handshake Pattern
|
||||
//!
|
||||
@@ -40,12 +13,9 @@
|
||||
//! -> s, se (msg3: encrypted static)
|
||||
//! ```
|
||||
//!
|
||||
//! ## Separation of Concerns
|
||||
//!
|
||||
//! The IK pattern handles **link-layer peer authentication** — securing the
|
||||
//! direct link between neighboring nodes. The XK pattern handles **session-layer
|
||||
//! end-to-end encryption** between arbitrary network addresses, with stronger
|
||||
//! initiator identity protection.
|
||||
//! The XX pattern handles both **link-layer peer authentication** (securing the
|
||||
//! direct link between neighboring nodes) and **session-layer end-to-end
|
||||
//! encryption** between arbitrary network addresses.
|
||||
|
||||
mod handshake;
|
||||
mod replay;
|
||||
@@ -62,14 +32,6 @@ pub use handshake::HandshakeState;
|
||||
pub use replay::ReplayWindow;
|
||||
pub use session::NoiseSession;
|
||||
|
||||
/// Protocol name for Noise IK with secp256k1 (link layer).
|
||||
/// Format: Noise_IK_secp256k1_ChaChaPoly_SHA256
|
||||
pub(crate) const PROTOCOL_NAME_IK: &[u8] = b"Noise_IK_secp256k1_ChaChaPoly_SHA256";
|
||||
|
||||
/// Protocol name for Noise XK with secp256k1 (session layer).
|
||||
/// Format: Noise_XK_secp256k1_ChaChaPoly_SHA256
|
||||
pub(crate) const PROTOCOL_NAME_XK: &[u8] = b"Noise_XK_secp256k1_ChaChaPoly_SHA256";
|
||||
|
||||
/// Protocol name for Noise XX with secp256k1.
|
||||
/// Format: Noise_XX_secp256k1_ChaChaPoly_SHA256
|
||||
pub(crate) const PROTOCOL_NAME_XX: &[u8] = b"Noise_XX_secp256k1_ChaChaPoly_SHA256";
|
||||
@@ -89,29 +51,14 @@ pub const EPOCH_SIZE: usize = 8;
|
||||
/// Size of encrypted epoch (epoch + AEAD tag).
|
||||
pub const EPOCH_ENCRYPTED_SIZE: usize = EPOCH_SIZE + TAG_SIZE;
|
||||
|
||||
/// Size of IK handshake message 1: ephemeral (33) + encrypted static (33 + 16 tag) + encrypted epoch (8 + 16 tag).
|
||||
pub const HANDSHAKE_MSG1_SIZE: usize = PUBKEY_SIZE + PUBKEY_SIZE + TAG_SIZE + EPOCH_ENCRYPTED_SIZE;
|
||||
/// Handshake msg1: ephemeral only (33 bytes). No DH, no encryption.
|
||||
pub const HANDSHAKE_MSG1_SIZE: usize = PUBKEY_SIZE;
|
||||
|
||||
/// Size of IK handshake message 2: ephemeral (33) + encrypted epoch (8 + 16 tag).
|
||||
pub const HANDSHAKE_MSG2_SIZE: usize = PUBKEY_SIZE + EPOCH_ENCRYPTED_SIZE;
|
||||
/// Handshake msg2: ephemeral (33) + encrypted static (33 + 16 tag) + encrypted epoch (8 + 16 tag) = 106 bytes.
|
||||
pub const HANDSHAKE_MSG2_SIZE: usize = PUBKEY_SIZE + PUBKEY_SIZE + TAG_SIZE + EPOCH_ENCRYPTED_SIZE;
|
||||
|
||||
/// XK msg1: ephemeral only (33 bytes).
|
||||
pub const XK_HANDSHAKE_MSG1_SIZE: usize = PUBKEY_SIZE;
|
||||
|
||||
/// XK msg2: ephemeral (33) + encrypted epoch (8 + 16 tag) = 57 bytes.
|
||||
pub const XK_HANDSHAKE_MSG2_SIZE: usize = PUBKEY_SIZE + EPOCH_ENCRYPTED_SIZE;
|
||||
|
||||
/// XK msg3: encrypted static (33 + 16 tag) + encrypted epoch (8 + 16 tag) = 73 bytes.
|
||||
pub const XK_HANDSHAKE_MSG3_SIZE: usize = PUBKEY_SIZE + TAG_SIZE + EPOCH_ENCRYPTED_SIZE;
|
||||
|
||||
/// XX msg1: ephemeral only (33 bytes). No DH, no encryption.
|
||||
pub const XX_HANDSHAKE_MSG1_SIZE: usize = PUBKEY_SIZE;
|
||||
|
||||
/// XX msg2: ephemeral (33) + encrypted static (33 + 16 tag) + encrypted epoch (8 + 16 tag) = 106 bytes.
|
||||
pub const XX_HANDSHAKE_MSG2_SIZE: usize = PUBKEY_SIZE + PUBKEY_SIZE + TAG_SIZE + EPOCH_ENCRYPTED_SIZE;
|
||||
|
||||
/// XX msg3: encrypted static (33 + 16 tag) + encrypted epoch (8 + 16 tag) = 73 bytes.
|
||||
pub const XX_HANDSHAKE_MSG3_SIZE: usize = PUBKEY_SIZE + TAG_SIZE + EPOCH_ENCRYPTED_SIZE;
|
||||
/// Handshake msg3: encrypted static (33 + 16 tag) + encrypted epoch (8 + 16 tag) = 73 bytes.
|
||||
pub const HANDSHAKE_MSG3_SIZE: usize = PUBKEY_SIZE + TAG_SIZE + EPOCH_ENCRYPTED_SIZE;
|
||||
|
||||
/// Replay window size in packets (matching WireGuard).
|
||||
pub const REPLAY_WINDOW_SIZE: usize = 2048;
|
||||
@@ -174,10 +121,6 @@ impl fmt::Display for HandshakeRole {
|
||||
/// Which Noise pattern is being used for this handshake.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum NoisePattern {
|
||||
/// Noise IK: two-message handshake (link layer).
|
||||
Ik,
|
||||
/// Noise XK: three-message handshake (session layer).
|
||||
Xk,
|
||||
/// Noise XX: three-message handshake, no prior key knowledge.
|
||||
Xx,
|
||||
}
|
||||
@@ -189,7 +132,7 @@ pub enum HandshakeProgress {
|
||||
Initial,
|
||||
/// Message 1 sent/received, ready for message 2.
|
||||
Message1Done,
|
||||
/// Message 2 sent/received, ready for message 3 (XK/XX only).
|
||||
/// Message 2 sent/received, ready for message 3.
|
||||
Message2Done,
|
||||
/// Handshake complete, ready for transport.
|
||||
Complete,
|
||||
|
||||
+202
-814
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -1021,14 +1021,14 @@ impl ActivePeer {
|
||||
})?;
|
||||
|
||||
// Split msg2 into base XX part and any extra (negotiation payload)
|
||||
let base_size = crate::noise::XX_HANDSHAKE_MSG2_SIZE;
|
||||
let base_size = crate::noise::HANDSHAKE_MSG2_SIZE;
|
||||
let (base_msg2, extra) = if msg2_bytes.len() > base_size {
|
||||
(&msg2_bytes[..base_size], Some(&msg2_bytes[base_size..]))
|
||||
} else {
|
||||
(msg2_bytes, None)
|
||||
};
|
||||
|
||||
hs.read_xx_message_2(base_msg2)?;
|
||||
hs.read_message_2(base_msg2)?;
|
||||
|
||||
// Must decrypt negotiation payload (if present) to keep hash chain
|
||||
// in sync, even though rekey doesn't use the negotiation result.
|
||||
@@ -1036,7 +1036,7 @@ impl ActivePeer {
|
||||
let _ = hs.decrypt_payload(encrypted_neg)?;
|
||||
}
|
||||
|
||||
let msg3 = hs.write_xx_message_3()?;
|
||||
let msg3 = hs.write_message_3()?;
|
||||
let session = hs.into_session()?;
|
||||
|
||||
// Clear msg1 resend state
|
||||
@@ -1062,14 +1062,14 @@ impl ActivePeer {
|
||||
})?;
|
||||
|
||||
// Split msg3 into base XX part and any extra (negotiation payload)
|
||||
let base_size = crate::noise::XX_HANDSHAKE_MSG3_SIZE;
|
||||
let base_size = crate::noise::HANDSHAKE_MSG3_SIZE;
|
||||
let (base_msg3, extra) = if msg3_bytes.len() > base_size {
|
||||
(&msg3_bytes[..base_size], Some(&msg3_bytes[base_size..]))
|
||||
} else {
|
||||
(msg3_bytes, None)
|
||||
};
|
||||
|
||||
hs.read_xx_message_3(base_msg3)?;
|
||||
hs.read_message_3(base_msg3)?;
|
||||
|
||||
// Must decrypt negotiation payload (if present) to keep hash chain
|
||||
// in sync, even though rekey doesn't use the negotiation result.
|
||||
|
||||
+10
-10
@@ -421,9 +421,9 @@ impl PeerConnection {
|
||||
}
|
||||
|
||||
// XX initiator: no remote static needed upfront
|
||||
let mut hs = noise::HandshakeState::new_xx_initiator(our_keypair);
|
||||
let mut hs = noise::HandshakeState::new_initiator(our_keypair);
|
||||
hs.set_local_epoch(epoch);
|
||||
let msg1 = hs.write_xx_message_1()?;
|
||||
let msg1 = hs.write_message_1()?;
|
||||
|
||||
self.noise_handshake = Some(hs);
|
||||
self.handshake_state = HandshakeState::SentMsg1;
|
||||
@@ -463,14 +463,14 @@ impl PeerConnection {
|
||||
});
|
||||
}
|
||||
|
||||
let mut hs = noise::HandshakeState::new_xx_responder(our_keypair);
|
||||
let mut hs = noise::HandshakeState::new_responder(our_keypair);
|
||||
hs.set_local_epoch(epoch);
|
||||
|
||||
// Process XX message 1 (ephemeral only — no identity learned)
|
||||
hs.read_xx_message_1(message)?;
|
||||
hs.read_message_1(message)?;
|
||||
|
||||
// Generate XX message 2 (sends our static + epoch)
|
||||
let mut msg2 = hs.write_xx_message_2()?;
|
||||
let mut msg2 = hs.write_message_2()?;
|
||||
|
||||
// Append encrypted negotiation payload if provided
|
||||
if let Some(payload) = negotiation_payload {
|
||||
@@ -515,7 +515,7 @@ impl PeerConnection {
|
||||
.expect("noise handshake must exist in SentMsg1 state");
|
||||
|
||||
// Split msg2 into base XX part and optional negotiation
|
||||
let base_size = noise::XX_HANDSHAKE_MSG2_SIZE;
|
||||
let base_size = noise::HANDSHAKE_MSG2_SIZE;
|
||||
let (base_msg2, extra) = if message.len() > base_size {
|
||||
(&message[..base_size], Some(&message[base_size..]))
|
||||
} else {
|
||||
@@ -523,7 +523,7 @@ impl PeerConnection {
|
||||
};
|
||||
|
||||
// Process XX msg2 (learns responder identity + epoch)
|
||||
hs.read_xx_message_2(base_msg2)?;
|
||||
hs.read_message_2(base_msg2)?;
|
||||
|
||||
// Decrypt negotiation payload from msg2 if present
|
||||
let received_negotiation = if let Some(encrypted) = extra {
|
||||
@@ -542,7 +542,7 @@ impl PeerConnection {
|
||||
self.remote_epoch = hs.remote_epoch();
|
||||
|
||||
// Generate XX msg3
|
||||
let mut msg3 = hs.write_xx_message_3()?;
|
||||
let mut msg3 = hs.write_message_3()?;
|
||||
|
||||
// Append encrypted negotiation payload if provided
|
||||
if let Some(payload) = negotiation_payload {
|
||||
@@ -584,7 +584,7 @@ impl PeerConnection {
|
||||
.expect("noise handshake must exist in ReceivedMsg1 state");
|
||||
|
||||
// Split msg3 into base XX part and optional negotiation
|
||||
let base_size = noise::XX_HANDSHAKE_MSG3_SIZE;
|
||||
let base_size = noise::HANDSHAKE_MSG3_SIZE;
|
||||
let (base_msg3, extra) = if message.len() > base_size {
|
||||
(&message[..base_size], Some(&message[base_size..]))
|
||||
} else {
|
||||
@@ -592,7 +592,7 @@ impl PeerConnection {
|
||||
};
|
||||
|
||||
// Process XX msg3 (learns initiator identity + epoch)
|
||||
hs.read_xx_message_3(base_msg3)?;
|
||||
hs.read_message_3(base_msg3)?;
|
||||
|
||||
// Decrypt negotiation payload from msg3 if present
|
||||
let received_negotiation = if let Some(encrypted) = extra {
|
||||
|
||||
+25
-16
@@ -10,27 +10,32 @@ use std::fmt;
|
||||
|
||||
/// Handshake message type identifiers.
|
||||
///
|
||||
/// These messages are exchanged during Noise IK handshake before link
|
||||
/// These messages are exchanged during Noise XX handshake before link
|
||||
/// encryption is established. They use the same TLV framing as link
|
||||
/// messages but payloads are not encrypted (except Noise-internal encryption).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum HandshakeMessageType {
|
||||
/// Noise IK message 1: initiator sends ephemeral + encrypted static.
|
||||
/// Payload: 82 bytes (33 ephemeral + 33 static + 16 tag).
|
||||
NoiseIKMsg1 = 0x01,
|
||||
/// Noise XX message 1: initiator sends ephemeral key.
|
||||
/// Payload: 33 bytes (ephemeral pubkey).
|
||||
Msg1 = 0x01,
|
||||
|
||||
/// Noise IK message 2: responder sends ephemeral.
|
||||
/// Payload: 33 bytes (ephemeral pubkey only).
|
||||
NoiseIKMsg2 = 0x02,
|
||||
/// Noise XX message 2: responder sends ephemeral + encrypted static + epoch.
|
||||
/// Payload: 106+ bytes (33 ephemeral + 49 encrypted static + 24 encrypted epoch + negotiation).
|
||||
Msg2 = 0x02,
|
||||
|
||||
/// Noise XX message 3: initiator sends encrypted static + epoch.
|
||||
/// Payload: 73+ bytes (49 encrypted static + 24 encrypted epoch + negotiation).
|
||||
Msg3 = 0x03,
|
||||
}
|
||||
|
||||
impl HandshakeMessageType {
|
||||
/// Try to convert from a byte.
|
||||
pub fn from_byte(b: u8) -> Option<Self> {
|
||||
match b {
|
||||
0x01 => Some(HandshakeMessageType::NoiseIKMsg1),
|
||||
0x02 => Some(HandshakeMessageType::NoiseIKMsg2),
|
||||
0x01 => Some(HandshakeMessageType::Msg1),
|
||||
0x02 => Some(HandshakeMessageType::Msg2),
|
||||
0x03 => Some(HandshakeMessageType::Msg3),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -42,15 +47,16 @@ impl HandshakeMessageType {
|
||||
|
||||
/// Check if a byte represents a handshake message type.
|
||||
pub fn is_handshake(b: u8) -> bool {
|
||||
matches!(b, 0x01 | 0x02)
|
||||
matches!(b, 0x01..=0x03)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HandshakeMessageType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
HandshakeMessageType::NoiseIKMsg1 => "NoiseIKMsg1",
|
||||
HandshakeMessageType::NoiseIKMsg2 => "NoiseIKMsg2",
|
||||
HandshakeMessageType::Msg1 => "Msg1",
|
||||
HandshakeMessageType::Msg2 => "Msg2",
|
||||
HandshakeMessageType::Msg3 => "Msg3",
|
||||
};
|
||||
write!(f, "{}", name)
|
||||
}
|
||||
@@ -64,7 +70,7 @@ impl fmt::Display for HandshakeMessageType {
|
||||
///
|
||||
/// These messages are exchanged between directly connected peers over
|
||||
/// Noise-encrypted links. All payloads are encrypted with session keys
|
||||
/// established during the Noise IK handshake.
|
||||
/// established during the Noise XX handshake.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum LinkMessageType {
|
||||
@@ -391,8 +397,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_handshake_message_type_roundtrip() {
|
||||
let types = [
|
||||
HandshakeMessageType::NoiseIKMsg1,
|
||||
HandshakeMessageType::NoiseIKMsg2,
|
||||
HandshakeMessageType::Msg1,
|
||||
HandshakeMessageType::Msg2,
|
||||
HandshakeMessageType::Msg3,
|
||||
];
|
||||
|
||||
for ty in types {
|
||||
@@ -405,7 +412,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_handshake_message_type_invalid() {
|
||||
assert!(HandshakeMessageType::from_byte(0x00).is_none());
|
||||
assert!(HandshakeMessageType::from_byte(0x03).is_none());
|
||||
assert!(HandshakeMessageType::from_byte(0x04).is_none());
|
||||
assert!(HandshakeMessageType::from_byte(0x10).is_none());
|
||||
}
|
||||
|
||||
@@ -413,7 +420,9 @@ mod tests {
|
||||
fn test_handshake_message_type_is_handshake() {
|
||||
assert!(HandshakeMessageType::is_handshake(0x01));
|
||||
assert!(HandshakeMessageType::is_handshake(0x02));
|
||||
assert!(HandshakeMessageType::is_handshake(0x03));
|
||||
assert!(!HandshakeMessageType::is_handshake(0x00));
|
||||
assert!(!HandshakeMessageType::is_handshake(0x04));
|
||||
assert!(!HandshakeMessageType::is_handshake(0x10));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
//! links. Includes spanning tree gossip, bloom filter propagation, discovery
|
||||
//! protocol, and forwarding of session-layer datagrams.
|
||||
//!
|
||||
//! Link-layer peer authentication uses Noise IK (see `noise.rs`), which
|
||||
//! Link-layer peer authentication uses Noise XX (see `noise.rs`), which
|
||||
//! establishes the encrypted channel before any of these messages are sent.
|
||||
//!
|
||||
//! ## Session Layer (end-to-end, between FIPS addresses)
|
||||
|
||||
+13
-13
@@ -330,7 +330,7 @@ impl FspInnerFlags {
|
||||
///
|
||||
/// Carried inside a SessionDatagram envelope which provides src_addr and
|
||||
/// dest_addr. The SessionSetup payload contains coordinates, session flags,
|
||||
/// and the Noise IK handshake message for session establishment.
|
||||
/// and the Noise XX handshake message for session establishment.
|
||||
///
|
||||
/// ## Wire Format
|
||||
///
|
||||
@@ -343,7 +343,7 @@ impl FspInnerFlags {
|
||||
/// | ... | dest_coords_count| 2 bytes | u16 LE, number of dest coord entries|
|
||||
/// | ... | dest_coords | 16 × m | NodeAddr array (dest → root) |
|
||||
/// | ... | handshake_len | 2 bytes | u16 LE, Noise payload length |
|
||||
/// | ... | handshake_payload| variable| Noise IK msg1 (82 bytes typical) |
|
||||
/// | ... | handshake_payload| variable| Noise XX msg1 (33 bytes — ephemeral key) |
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SessionSetup {
|
||||
/// Source coordinates (for return path caching).
|
||||
@@ -352,7 +352,7 @@ pub struct SessionSetup {
|
||||
pub dest_coords: TreeCoordinate,
|
||||
/// Session options.
|
||||
pub flags: SessionFlags,
|
||||
/// Noise IK handshake message 1.
|
||||
/// Noise XX handshake message 1.
|
||||
pub handshake_payload: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ impl SessionSetup {
|
||||
/// dest_addr. The SessionAck payload contains both the acknowledger's and
|
||||
/// initiator's coordinates for route cache warming (ensuring return-path
|
||||
/// transit nodes can route independently of the forward path) and the Noise
|
||||
/// IK handshake response.
|
||||
/// XX handshake response.
|
||||
///
|
||||
/// ## Wire Format
|
||||
///
|
||||
@@ -469,7 +469,7 @@ impl SessionSetup {
|
||||
/// | ... | dest_coords_count| 2 bytes | u16 LE |
|
||||
/// | ... | dest_coords | 16 × m | Initiator's coords (for return path)|
|
||||
/// | ... | handshake_len | 2 bytes | u16 LE, Noise payload length |
|
||||
/// | ... | handshake_payload| variable| Noise IK msg2 (33 bytes typical) |
|
||||
/// | ... | handshake_payload| variable| Noise XX msg2 (106+ bytes — ephemeral + static + epoch + negotiation) |
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SessionAck {
|
||||
/// Acknowledger's coordinates.
|
||||
@@ -478,7 +478,7 @@ pub struct SessionAck {
|
||||
pub dest_coords: TreeCoordinate,
|
||||
/// Reserved flags byte (for forward compatibility).
|
||||
pub flags: u8,
|
||||
/// Noise IK handshake message 2.
|
||||
/// Noise XX handshake message 2.
|
||||
pub handshake_payload: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -567,10 +567,10 @@ impl SessionAck {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Msg3 (XK Handshake Message 3)
|
||||
// Session Msg3 (XX Handshake Message 3)
|
||||
// ============================================================================
|
||||
|
||||
/// XK handshake message 3 (initiator -> responder).
|
||||
/// XX 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
|
||||
@@ -582,12 +582,12 @@ impl SessionAck {
|
||||
/// |--------|------------------|---------|-------------------------------------|
|
||||
/// | 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) |
|
||||
/// | 3 | handshake_payload| variable| Noise XX msg3 (73 bytes typical) |
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SessionMsg3 {
|
||||
/// Reserved flags byte.
|
||||
pub flags: u8,
|
||||
/// Noise XK handshake message 3.
|
||||
/// Noise XX handshake message 3.
|
||||
pub handshake_payload: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -1240,7 +1240,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_session_setup_encode_decode() {
|
||||
let handshake = vec![0xAA; 82]; // typical Noise IK msg1
|
||||
let handshake = vec![0xAA; 82]; // typical Noise XX msg1
|
||||
let setup = SessionSetup::new(make_coords(&[1, 2, 0]), make_coords(&[3, 4, 0]))
|
||||
.with_flags(SessionFlags::new().with_ack().bidirectional())
|
||||
.with_handshake(handshake.clone());
|
||||
@@ -1276,7 +1276,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_session_ack_encode_decode() {
|
||||
let handshake = vec![0xBB; 33]; // typical Noise IK msg2
|
||||
let handshake = vec![0xBB; 33]; // typical Noise XX msg2
|
||||
let ack = SessionAck::new(make_coords(&[7, 8, 0]), make_coords(&[3, 4, 0]))
|
||||
.with_handshake(handshake.clone());
|
||||
|
||||
@@ -1666,7 +1666,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_session_msg3_encode_decode() {
|
||||
let handshake = vec![0xCC; 73]; // typical XK msg3
|
||||
let handshake = vec![0xCC; 73]; // typical XX msg3
|
||||
let msg3 = SessionMsg3::new(handshake.clone());
|
||||
|
||||
let encoded = msg3.encode();
|
||||
|
||||
@@ -45,7 +45,7 @@ impl DiscoveryBuffer {
|
||||
///
|
||||
/// Used after the pre-handshake pubkey exchange confirms the peer's
|
||||
/// identity. The pubkey_hint enables the node's auto-connect path
|
||||
/// to initiate the IK handshake.
|
||||
/// to initiate the XX handshake.
|
||||
pub fn add_peer_with_pubkey(&self, addr: &BleAddr, pubkey: XOnlyPublicKey) {
|
||||
let ta = addr.to_transport_addr();
|
||||
let peer = DiscoveredPeer::with_hint(self.transport_id, ta.clone(), pubkey);
|
||||
|
||||
@@ -96,7 +96,7 @@ pub struct BleTransport<I: BleIo> {
|
||||
///
|
||||
/// BLE advertisements carry only the FIPS UUID, not the pubkey.
|
||||
/// After L2CAP connection, both sides exchange `[0x00][pubkey:32]`
|
||||
/// so the node layer can initiate the IK handshake.
|
||||
/// so the node layer can initiate the XX handshake.
|
||||
/// Temporary — removed when FMP switches to XX.
|
||||
local_pubkey: Option<[u8; 32]>,
|
||||
}
|
||||
@@ -662,7 +662,7 @@ impl<I: BleIo> Transport for BleTransport<I> {
|
||||
/// Pre-handshake pubkey exchange prefix byte.
|
||||
///
|
||||
/// Distinguishes the identity exchange from FMP packets (version ≥ 0x01).
|
||||
/// Temporary — removed when FMP switches from IK to XX handshake.
|
||||
/// Temporary — removed when FMP uses XX handshake for BLE.
|
||||
const PUBKEY_EXCHANGE_PREFIX: u8 = 0x00;
|
||||
|
||||
/// Pre-handshake pubkey exchange message size: `[0x00][pubkey:32]`.
|
||||
|
||||
Reference in New Issue
Block a user