Add periodic Noise rekey with fresh DH for forward secrecy (FMP + FSP)

Implement periodic full rekey at both protocol layers using fresh DH
key exchanges. Uses the existing K-bit flag (FLAG_KEY_EPOCH /
FSP_FLAG_K) to coordinate cutover between peers.

FMP layer (IK pattern):
- ActivePeer gains rekey state: pending/previous sessions, K-bit epoch
  tracking, drain window, dampening timer
- Handshake state stored on ActivePeer with msg1 sent on existing link
- Encrypted frame handler detects K-bit flips, promotes pending
  sessions, falls back to previous session during drain
- Handshake handlers distinguish rekey from new connections using
  addr_to_link lookup with identity-based fallback
- Free all session indices (current, rekey, pending, previous) on
  peer removal

FSP layer (XK pattern):
- SessionEntry gains parallel rekey fields with XK-specific state
  for the 3-message handshake
- Route availability check before FSP rekey initiation
- Encrypted session handler adds K-bit flip detection and dual-session
  decrypt fallback
- SessionSetup/Ack/Msg3 handlers extended for rekey paths

Defense-in-depth:
- Consecutive decryption failure detector (threshold=20) triggers
  forced peer removal instead of waiting for link-dead timeout
- Identity-based rekey detection as fallback when addr_to_link
  doesn't match (e.g., TCP ephemeral ports)

Configuration: RekeyConfig with enabled flag, after_secs (default 120),
and after_messages (default 65536) thresholds.

Logging: info for successful K-bit cutover completions, warn for
failures, debug for intermediate handshake steps, trace for routine
operations (resends, drain cleanup).

Rekey lifecycle:
1. Timer/counter fires -> initiator starts new handshake
2. Old session continues handling traffic during handshake
3. Handshake completes -> initiator cuts over, flips K-bit
4. Responder sees flipped K-bit -> promotes new session
5. Both keep old session for 10s drain window
6. After drain, old session discarded

Integration test: Docker-based multi-phase test exercising both FMP
and FSP rekey with aggressive timers (35s). Verifies connectivity
across all 20 directed pairs survives two consecutive rekey cycles.
Includes rekey topology, docker-compose profile, and CI matrix entry.

Increase ping test convergence wait from 3s to 5s for CI reliability.
This commit is contained in:
Johnathan Corgan
2026-03-07 18:33:27 +00:00
parent 392572f821
commit bf117df0ca
17 changed files with 1927 additions and 125 deletions
+41
View File
@@ -418,6 +418,42 @@ impl BuffersConfig {
// ECN Congestion Signaling
// ============================================================================
/// Rekey / session rekeying configuration (`node.rekey.*`).
///
/// Controls periodic full rekey for both FMP (link layer) and FSP
/// (session layer) Noise sessions. Rekeying provides true forward secrecy
/// with fresh DH randomness, nonce reset, and session index rotation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RekeyConfig {
/// Enable periodic rekey (`node.rekey.enabled`).
#[serde(default = "RekeyConfig::default_enabled")]
pub enabled: bool,
/// Initiate rekey after this many seconds (`node.rekey.after_secs`).
#[serde(default = "RekeyConfig::default_after_secs")]
pub after_secs: u64,
/// Initiate rekey after this many messages sent (`node.rekey.after_messages`).
#[serde(default = "RekeyConfig::default_after_messages")]
pub after_messages: u64,
}
impl Default for RekeyConfig {
fn default() -> Self {
Self {
enabled: true,
after_secs: 120,
after_messages: 1 << 16, // 65536
}
}
}
impl RekeyConfig {
fn default_enabled() -> bool { true }
fn default_after_secs() -> u64 { 120 }
fn default_after_messages() -> u64 { 1 << 16 }
}
/// ECN congestion signaling configuration (`node.ecn.*`).
///
/// Controls the FMP CE relay chain: transit nodes detect congestion on outgoing
@@ -541,6 +577,10 @@ pub struct NodeConfig {
/// ECN congestion signaling (`node.ecn.*`).
#[serde(default)]
pub ecn: EcnConfig,
/// Rekey / session rekeying (`node.rekey.*`).
#[serde(default)]
pub rekey: RekeyConfig,
}
impl Default for NodeConfig {
@@ -565,6 +605,7 @@ impl Default for NodeConfig {
mmp: MmpConfig::default(),
session_mmp: SessionMmpConfig::default(),
ecn: EcnConfig::default(),
rekey: RekeyConfig::default(),
}
}
}