mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 19:18:25 +00:00
A session rekey responder that has sent its SessionAck holds the only handshake the initiator's msg3 can complete: by then the initiator has read the SessionAck and holds the new keys. Two unauthenticated messages could discard that handshake inside the window. The responder arm of the SessionMsg3 handler abandoned it when the msg3 read failed, though nothing authenticates a msg3 before that read beyond a source address the sender chooses. And a setup message naming the peer while the handshake was armed ran the dual-initiation tie-break, which at the larger address abandoned the handshake to answer the setup. Either way the genuine msg3 and its resends found no handshake, the initiator cut over to keys this node never derived, and frames from it stopped decoding until a later rekey completed. The initial-handshake arm had the same flaw: it removed the half-open entry to read msg3 and did not put it back when the read failed, so a garbage msg3 naming the initiator, arriving ahead of the genuine one, left the genuine msg3 to an unknown session. The Noise handshake gains a rolling-back msg3 read, shaped like the msg2 one, and both msg3 arms use it and put the handshake back on a failed read. The rollback matters because the read advances the cipher nonce before it opens the first AEAD, so a handshake put back after a plain read could never read the genuine msg3. The restore leaves the rekey deadline, which runs from the peer's setup, and the half-open entry's activity stamp unchanged, so a spray cannot hold a handshake open. The abandons and drops after a successful read stay: each follows a read that authenticated the sender. A setup message is now dropped, at either address, while a handshake the peer armed awaits its msg3, and counted as rekey_held; the tie-break runs only when this node initiated the armed handshake. The cost is that a genuine retry from a peer that abandoned the rekey this node answered completes one handshake timeout later, once the held handshake expires, as it already did at the smaller address. New tests drive a genuine rekey to the point where the initiator has read the SessionAck, deliver a garbage msg3 or, at the larger-address end, a forged setup, and follow the genuine msg3, the cutover, the msg3 resend budget and the next rekey cycle, asserting the forged setup was held off by the armed handshake. Unit tests check that a forged msg3 leaves a stranger-armed handshake able to read the msg3 that finishes it and a peer-armed one with its deadline unchanged, that a second setup leaves a peer-armed handshake and the completed epoch intact, and that a forged initial msg3 leaves the half-open entry and its activity stamp untouched. Noise tests cover the rolling-back read against a msg3 failing at either AEAD, with a control showing the plain read cannot recover. The retry tests for a responder holding a stale handshake assert rekey_held rather than the tie-break counters. No wire format change.