Files
fips/src/peer
Johnathan Corgan 17ce9d5169 Refuse a replayed link rekey msg1 and answer every msg1 on the established link
A link rekey msg1 carries nothing that ties it to one cycle. A copy taken
off the wire still decrypts as the peer in the peer's current epoch after
the cycle it started has ended, and once the new link session is past the
30 s floor it classified as a fresh rekey. Replayed from any address the
transport accepts handshakes on, or from the peer's own, it armed a
responder pending that refused the peer's genuine rekeys and suppressed
this node's own trigger until the 120 s hold retired it, and one replay per
hold kept link key rotation stopped. The msg2 also went to the address the
msg1 came from, so anyone holding a copy could have the node send a msg2 to
an address of their choosing: a reflection, 69 bytes out for 114 in.

The responder's per-peer record of answered msg1s, which already kept the
answer that armed the pending it holds, now keeps the digest of each
answered msg1 once its cycle ends, whether the pending was adopted, retired
or abandoned. A msg1 matching an ended cycle is refused before it can arm a
pending, and before the dual-initiation tie-break, so a copy cannot make
this node abandon its own rekey either. Retention is bounded at the 256 most
recent ended cycles per peer, at most 8 KiB: at least two hours of the
peer's cycles, and eight and a half at the default 120 s interval when the
message-count trigger does not fire first. A msg1 from an older cycle of the
same peer epoch is not recognized. The record lives with the peering, so it
starts empty when the peering forms again while the peer's epoch is
unchanged, after this node restarts or the link is torn down and re-formed;
a msg1 captured before that still arms a pending. Closing either gap needs a
field in msg1 that orders cycles, which is a wire change. A test that
replays across a re-formed peering records the second residual and is
ignored.

The rekey msg2 now goes to the peer's established link, as the resend of a
lost msg2 already did, through one helper both arms use. When the peer has
no established link the msg1 is refused, as a failed send already was. A
peer whose address changed and whose first frame from the new address is a
rekey msg1 is answered at the old address; the next authenticated frame from
the new address moves the link, and the initiator's resends reach it there.
The msg2 stored at link setup, resent for a duplicate setup msg1 inside the
30 s after a cutover, likewise goes to the established link rather than to
the msg1's source; a genuine duplicate comes from the address the peering
was formed with, so a peer whose setup msg2 was lost sees no change.

The responder's pending index is registered under the transport the msg2 was
sent on rather than the one the msg1 arrived on. Otherwise a msg1 that came
in on another transport left an index entry retirement never removed, and
the peer's first frame on the new session found no session under its index,
so the responder could not promote.

Tests run a genuine link rekey to completion while keeping its msg1, then
replay it from an address the node has no link with, from the peer's own
address, and from an earlier cycle than the latest, and probe both the
peer's next rekey and the node's own trigger. The aged two-node link setup
moves out of the held-msg2 builder so both use it, and the peer gains a
test-only seam to backdate its rekey dampening.
2026-10-01 14:20:06 +00:00
..