mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-06 11:38:24 +00:00
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.