Files
fips/src/peer
Johnathan Corgan b37f7cdc21 fix(rekey): keep the rekey cycle when a msg2 fails to authenticate
The rekey initiator took its handshake off the peer before reading msg2
and abandoned the whole cycle when the read failed. Nothing authenticates
a msg2 ahead of that read, and the index it is dispatched on travels in
cleartext in rekey msg1, so anyone on the path who saw the msg1 could
answer it first with a msg2 of the right size under that index.

That costs more than one cycle. The IK responder commits its new keys
when it answers msg1 and cuts over on its own next rekey tick, while the
initiator, having abandoned, drops the responder's genuine msg2 at the
dispatch lookup. Frames from the responder then miss the initiator's
index table at once, frames to the responder fail once its drain window
closes, and each end removes the other on the link-dead timeout. With the
tick functions driven in-process at one-second steps, the responder
removed the link at about 30 s and the initiator at about 31 s, and the
initiator's last msg1 resend left the responder holding a peer the
initiator no longer had, removed 30 s after that.

Read msg2 through a rolling-back variant of the IK read, modelled on the
existing XK one, and put the handshake back on the peer when it fails.
The msg2 handler keeps the cycle and its dispatch entry in that case and
still counts the reject, so the genuine msg2 completes the rekey. The
other failures, a missing handshake or one after the read succeeded,
still abandon, and if no readable msg2 ever arrives the msg1 resend
budget abandons the cycle as before. Each forged msg2 carrying the live
index now costs the initiator the msg2 key agreement until the cycle
ends, where before only the first one did; the resend budget bounds
that. No wire format changes.

The new test ages a two-node link past both rekey gates, delivers the
rekey msg1, holds the responder's msg2, injects a forged one under the
live index, releases the real one, runs a rekey tick on both nodes and
checks delivery in both directions. It fails on the unfixed tree at the
responder-to-initiator delivery, which is the check that separates the
two outcomes, and fails there again with each part of the fix reverted
on its own: the handshake put-back, the symmetric-state rollback, and
keeping the dispatch entry.
2026-09-14 15:09:12 +00:00
..