flare_mlake · API · quic

flare.quic.retry

QUIC server Retry + Version Negotiation packet codecs (RFC 9000 §8.1 / §17.2.1 / §17.2.5, RFC 9001 §5.8).

When a server wants to validate a client's source address before committing handshake state, it answers the client Initial with a Retry packet carrying an opaque address-validation token. The client replays the token in a fresh Initial; the server validates it and proceeds. This module ships:

  • :func:mint_retry_token / :func:validate_retry_token -- an HMAC-SHA256 keyed token that binds the client's address and the original Destination Connection ID with an issue timestamp, so a token is single-path and expires. The server holds the HMAC key; the token is opaque to the client.
  • :func:encode_retry_packet -- assemble the full Retry packet including the RFC 9001 §5.8 Retry Integrity Tag (AEAD_AES_128_GCM over the Retry Pseudo-Packet with the fixed v1 retry key/nonce).
  • :func:verify_retry_integrity -- recompute + compare the integrity tag of a received Retry (client-side check).
  • :func:encode_version_negotiation -- build the Version Negotiation packet a server sends when a client Initial carries an unsupported QUIC version (RFC 9000 §6.1).

Not sans-I/O: the Retry Integrity Tag routes through the OpenSSL AEAD FFI (:class:flare.quic.crypto.OpenSslQuicCrypto). The token HMAC and the version-negotiation builder are pure, but the module as a whole links the crypto backend, so it lives outside the sans-I/O set.

References:

  • RFC 9000 §8.1 "Address Validation", §17.2.1 / §17.2.5, §6.1.
  • RFC 9001 §5.8 "Retry Packet Integrity".

Functions

mint_retry_token

def mint_retry_token(server_key: List[UInt8], client_addr: List[UInt8], odcid: ConnectionId, issued_ms: UInt64) -> List[UInt8]

Issue an opaque Retry token bound to client_addr and odcid, stamped at issued_ms.

Wire layout (server-private; opaque to the client): version(1) || issued_ms(8) || odcid_len(1) || odcid || HMAC-SHA256(server_key, mac_message).

validate_retry_token

def validate_retry_token(server_key: List[UInt8], token: List[UInt8], client_addr: List[UInt8], now_ms: UInt64, max_age_ms: UInt64) -> Optional[ConnectionId]

Validate a replayed Retry token. Returns the recovered original DCID when the token is authentic, address-matched, and unexpired; None otherwise. Never raises on a malformed token -- a forged or truncated token is simply rejected (returns None).

mint_new_token

def mint_new_token(server_key: List[UInt8], client_ip: List[UInt8], issued_ms: UInt64) -> List[UInt8]

A token for a NEW_TOKEN frame, bound to the client's IP address (not its port, which a later connection may not share): version(1) || issued_ms(8) || HMAC-SHA256(key, mac_message).

validate_new_token

def validate_new_token(server_key: List[UInt8], token: List[UInt8], client_ip: List[UInt8], now_ms: UInt64, max_age_ms: UInt64) -> Bool

Whether token is a NEW_TOKEN token this server issued to client_ip within max_age_ms. A malformed one is just rejected.

retry_integrity_tag

def retry_integrity_tag(odcid: ConnectionId, retry_header_and_token: List[UInt8]) -> List[UInt8]

Compute the RFC 9001 §5.8 Retry Integrity Tag: AEAD_AES_128_GCM over the Retry Pseudo-Packet (ODCID-len || ODCID || the Retry packet body without the tag) with the fixed v1 retry key/nonce and an empty plaintext. The 16-byte output is the tag.

encode_retry_packet

def encode_retry_packet(version: UInt32, dcid: ConnectionId, scid: ConnectionId, token: List[UInt8], odcid: ConnectionId) -> List[UInt8]

Build a complete Retry packet (RFC 9000 §17.2.5).

dcid is the client's Source CID (echoed as the new DCID), scid is the server's freshly chosen Source CID, token is the address-validation token (see :func:mint_retry_token), and odcid is the Destination CID from the triggering client Initial -- it feeds the integrity tag but is not carried on the wire.

verify_retry_integrity

def verify_retry_integrity(packet: List[UInt8], odcid: ConnectionId) -> Bool

Recompute the Retry Integrity Tag of packet and compare it to the trailing 16 bytes. Used by a client to authenticate a received Retry against the original DCID it sent.

encode_version_negotiation

def encode_version_negotiation(dcid: ConnectionId, scid: ConnectionId, versions: List[UInt32]) -> List[UInt8]

Build a Version Negotiation packet (RFC 9000 §6.1 / §17.2.1).

A server sends this in response to a client Initial whose version it does not support. dcid MUST be the client Initial's Source CID and scid the client Initial's Destination CID (the fields are echoed swapped so the client can match the response). versions is the server's supported-version list; the all-zero Version field marks the packet as VN.

Aliases

comptime NEW_TOKEN_VERSION = UInt8(2)

First byte of a NEW_TOKEN token; a Retry token starts with _RETRY_TOKEN_VERSION, so the server tells them apart (sec 8.1.3).