Files
amethyst/quic/interop
Vitor Pamplona e8991fab69 fix(quic): gate client-initiated key update + path migration on HANDSHAKE_DONE
Pre-fix, [QuicConnection.initiateKeyUpdate] only checked that 1-RTT
keys were installed — which fires when the TLS handshake derives
Finished, well before HANDSHAKE_DONE arrives. RFC 9001 §6.1 + §4.1.2
require the CLIENT to consider the handshake confirmed (HANDSHAKE_DONE
received) before rolling KEY_PHASE; quinn / quic-go / picoquic
correctly close the connection with PROTOCOL_VIOLATION ("illegal
packet: key update error") when an early update arrives.

The interop runner's keyupdate testcase against quinn flushed this:
the client polled `status == CONNECTED` (which flips on TLS Finished
via `onHandshakeComplete`) and called `initiateKeyUpdate()` before
HANDSHAKE_DONE landed. ~33% repro rate; the rest of the runs HANDSHAKE_DONE
happened to arrive in the gap between the status check and the
update.

Fix:
  - Add `QuicConnection.handshakeConfirmed: Boolean` flipped only by
    the parser when a HANDSHAKE_DONE frame is processed.
  - Add `awaitHandshakeConfirmed()` for callers that need the
    spec-confirmed state.
  - Gate `initiateKeyUpdate()` on `handshakeConfirmed` (returns
    false otherwise — same shape as the existing app-keys-not-yet
    branch).
  - Tighten `triggerPathMigrationLocked` to also gate on
    `handshakeConfirmed` (RFC 9000 §9.1: same confirmation
    requirement). Pre-fix it gated on `handshakeComplete`, which
    flips at TLS-Finished too.
  - InteropClient's keyupdate flow now waits on
    `awaitHandshakeConfirmed` (with a 2s upper bound) rather than
    polling `status == CONNECTED`.

The test fixture in ConnectedClientFixture.newConnectedClient now
delivers HANDSHAKE_DONE by default — most tests want the
production "fully ready" state. New `deliverHandshakeDone = false`
parameter for tests that need to exercise the pre-confirmation
window (KeyUpdateClientInitiatedTest).

Tests:
  - KeyUpdateClientInitiatedTest:
      initiateKeyUpdateBeforeHandshakeDoneIsRejectedAndDoesNotRotateKeys
      initiateKeyUpdateAfterHandshakeDoneRotatesBothDirections
      awaitHandshakeConfirmedSuspendsUntilHandshakeDone
      triggerPathMigrationBeforeHandshakeDoneReturnsNotConnected

Verified against the live interop runner — 5/5 against quinn,
3/3 against quic-go, 3/3 against picoquic (pre-fix: ~33% pass rate
against quinn).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:40:36 -04:00
..