Context
Milestone 2a (multi-peer data plane, branch feat/multipeer-2a) introduced the in-loop lazy Noise-IK handshake in bin/yipd/src/peer_manager.rs. The whole-branch review surfaced a design-level gap that 2a deliberately defers (handshake hardening is #3 / NAT-roaming is 2b), tracked here.
Problem
The handshake carries no anti-replay token (unlike WireGuard's TAI64N timestamp in the initiation). Two consequences:
-
Endpoint hijack via replay. handle_handshake_init learns a peer's endpoint from the datagram source (endpoint = src) after admission. An on-path adversary (in-threat-model for a censorship-resistance VPN) who captures one genuine HandshakeInit from configured peer P can replay it from a spoofed source S: start_responder succeeds, recovers P's real static key (so admission passes), and our outbound traffic for P is redirected to S. Admission itself is sound — an unconfigured/forged key allocates nothing — the exposure is specifically trusting an unauthenticated-freshness Init for endpoint learning.
-
No safe session rebuild on genuine peer restart. Because a replayed old Init is indistinguishable from a genuine re-initiation, an Established responder cannot safely rebuild its session when a peer restarts with a fresh ephemeral (rebuilding on any differing Init would let a replayed old Init tear down a live session — a DoS). 2a works around the loss case by having the initiator retransmit the SAME Init within HANDSHAKE_TOTAL_MS (see the constant's doc comment in peer_manager.rs and commit 9954bfd), which is safe and closes the common wedge — but a genuine peer restart mid-session still cannot be recovered until the responder's own attempt path times out.
Fix (future milestone — handshake hardening / rekey)
- Add an anti-replay token to the initiation (monotonic timestamp / counter, TAI64N-style), rejecting any
Init not strictly newer than the last accepted one from that static key.
- With anti-replay: (a) only update
endpoint from an Init that passes the freshness check; (b) allow an Established responder to rebuild its session on a newer Init (genuine restart/rekey), safely.
- Revisit
by_tag eviction on rebuild (documented on the field) once rebuild becomes reachable.
Scope note
Out of scope for 2a (data plane). Belongs with the session-rekey / PQ-hybrid handshake work and the anti-DPI hardening track. Referenced from the HANDSHAKE_TOTAL_MS doc comment in peer_manager.rs.
Context
Milestone 2a (multi-peer data plane, branch
feat/multipeer-2a) introduced the in-loop lazy Noise-IK handshake inbin/yipd/src/peer_manager.rs. The whole-branch review surfaced a design-level gap that 2a deliberately defers (handshake hardening is #3 / NAT-roaming is 2b), tracked here.Problem
The handshake carries no anti-replay token (unlike WireGuard's TAI64N timestamp in the initiation). Two consequences:
Endpoint hijack via replay.
handle_handshake_initlearns a peer's endpoint from the datagram source (endpoint = src) after admission. An on-path adversary (in-threat-model for a censorship-resistance VPN) who captures one genuineHandshakeInitfrom configured peer P can replay it from a spoofed source S:start_respondersucceeds, recovers P's real static key (so admission passes), and our outbound traffic for P is redirected to S. Admission itself is sound — an unconfigured/forged key allocates nothing — the exposure is specifically trusting an unauthenticated-freshnessInitfor endpoint learning.No safe session rebuild on genuine peer restart. Because a replayed old
Initis indistinguishable from a genuine re-initiation, anEstablishedresponder cannot safely rebuild its session when a peer restarts with a fresh ephemeral (rebuilding on any differingInitwould let a replayed oldInittear down a live session — a DoS). 2a works around the loss case by having the initiator retransmit the SAMEInitwithinHANDSHAKE_TOTAL_MS(see the constant's doc comment inpeer_manager.rsand commit 9954bfd), which is safe and closes the common wedge — but a genuine peer restart mid-session still cannot be recovered until the responder's own attempt path times out.Fix (future milestone — handshake hardening / rekey)
Initnot strictly newer than the last accepted one from that static key.endpointfrom anInitthat passes the freshness check; (b) allow anEstablishedresponder to rebuild its session on a newerInit(genuine restart/rekey), safely.by_tageviction on rebuild (documented on the field) once rebuild becomes reachable.Scope note
Out of scope for 2a (data plane). Belongs with the session-rekey / PQ-hybrid handshake work and the anti-DPI hardening track. Referenced from the
HANDSHAKE_TOTAL_MSdoc comment inpeer_manager.rs.