Using TLS to Secure QUIC
Extracted elements (34)
QUIC does not use the TLS record layer; instead, TLS handshake and alert messages are carried directly over QUIC transport in CRYPTO frames, and QUIC packet protection replaces TLS record protection. This allows QUIC to manage encryption level transitions and retransmissions independently.
The max_early_data_size field in NewSessionTicket is repurposed to a sentinel value 0xffffffff to indicate QUIC 0-RTT acceptance, rather than conveying a byte count, because QUIC uses its own initial_max_data transport parameter to control early data volume.
The TLS EndOfEarlyData message is not used in QUIC because QUIC itself provides reliable signaling of the end of early data via packet number spaces, making the TLS message redundant and potentially harmful to middlebox compatibility.
TLS_AES_128_CCM_8_SHA256 MUST NOT be negotiated for use with QUIC because no header protection scheme is defined for it in this document. All other TLS 1.3 cipher suites are supported, subject to header protection algorithm availability.
A client MUST authenticate the identity of the server, typically by verifying that the server's identity is included in a certificate issued by a trusted entity.
A client MUST discard Initial keys when it first sends a Handshake packet; a server MUST discard Initial keys when it first successfully processes a Handshake packet. Endpoints MUST NOT send Initial packets after this point.
A client MUST NOT use 0-RTT for application data unless specifically requested by the application in use. An application protocol using QUIC MUST include a profile that defines acceptable use of 0-RTT.
A server MUST NOT process incoming 1-RTT protected packets before the TLS handshake is complete; it cannot send acknowledgments for such packets until the handshake is complete. Received 1-RTT packets MAY be stored for later decryption.
A server MUST NOT use post-handshake client authentication (TLS CertificateRequest after handshake) because QUIC multiplexing prevents clients from correlating the certificate request with the application-level event that triggered it. Clients MUST treat receipt of such messages as a PROTOCOL_VIOLATION connection error.
An endpoint MUST discard its Handshake keys when the TLS handshake is confirmed (i.e., when the server's HANDSHAKE_DONE frame is received by the client).
An endpoint MUST NOT initiate a key update prior to confirming the handshake, and MUST NOT initiate a subsequent key update unless it has received an acknowledgment for a packet sent with keys from the current key phase.
Clients MUST NOT offer TLS versions older than 1.3. An endpoint MUST terminate the connection if a version of TLS older than 1.3 is negotiated.
Endpoints MUST count encrypted packets per key and initiate a key update before exceeding the AEAD confidentiality limit. Endpoints MUST also count packets that fail authentication; if the integrity limit is exceeded the endpoint MUST immediately close the connection with AEAD_LIMIT_REACHED.
Endpoints MUST NOT send a TLS KeyUpdate message; receipt of one MUST be treated as a connection error of type 0x010a. QUIC uses its own Key Phase bit mechanism for key updates instead.
Endpoints MUST send the quic_transport_parameters TLS extension in the ClientHello and EncryptedExtensions messages. Endpoints that receive these messages without the extension MUST close the connection with a TRANSPORT_PARAMETER_ERROR.
Endpoints MUST use ALPN for application protocol negotiation and MUST immediately close the connection with a no_application_protocol alert (error code 0x0178) if negotiation fails.
Servers MUST NOT send the early_data extension in a NewSessionTicket with a max_early_data_size field set to any value other than 0xffffffff. A client MUST treat any other value as a connection error of type PROTOCOL_VIOLATION.
The server MUST send a HANDSHAKE_DONE frame as soon as the TLS handshake is complete. At the client, the handshake is considered confirmed when a HANDSHAKE_DONE frame is received.
Four encryption levels are defined — Initial, 0-RTT (Early Data), Handshake, and 1-RTT (Application Data) — each corresponding to a packet type and packet number space. Each encryption level provides separate send/receive secrets. Table 1 maps packet types to their encryption keys and packet number spaces.
QUIC carries TLS handshake data in CRYPTO frames, each consisting of a contiguous block of handshake data identified by an offset and length. CRYPTO frames are carried at three of the four encryption levels (Initial, Handshake, and 1-RTT), omitting 0-RTT.
The Key Phase bit in 1-RTT packet short headers indicates which set of packet protection keys is in use. It is initially 0 and toggled with each key update. The same header protection key is used for the full connection lifetime and is not updated during key updates.
The quic_transport_parameters TLS extension (type 0x39) carries QUIC transport parameters in the ClientHello and EncryptedExtensions messages, providing integrity protection for these values via the TLS handshake.
TLS alerts are converted to QUIC connection errors by adding the AlertDescription value to 0x0100, producing a QUIC error code in the CRYPTO_ERROR range. All TLS alerts are treated as fatal by QUIC.
KEY_UPDATE_ERROR (error code 0x0e) is defined for signaling errors related to key updates, such as consecutive key updates without confirmation or receiving an ACK for newer-key packets in old-key packets.
0-RTT data is susceptible to replay attacks because it is sent before the server can verify client liveness. QUIC cannot prevent all replays; application protocols MUST define a 0-RTT usage profile and clients MUST NOT send application data in 0-RTT without one.
AEAD usage limits are set per algorithm: AEAD_AES_128_GCM and AEAD_AES_256_GCM have a confidentiality limit of 2^23 encrypted packets and an integrity limit of 2^52 invalid packets. Exceeding these limits requires closing the connection with AEAD_LIMIT_REACHED.
Endpoints MUST NOT generate timing side-channel signals when processing packets with an invalid Key Phase bit. Randomized packet protection keys SHOULD be used in place of discarded keys when key updates are not yet permitted, to prevent timing-based leakage of the Key Phase bit value.
Session resumption via TLS NewSessionTicket allows servers to link activity across connections, which may be a privacy issue. Clients SHOULD NOT reuse tickets, as doing so allows entities other than the server to correlate connections.
Key update state: initiating peer toggles the Key Phase bit and sends packets with new keys (@N); responding peer detects the changed Key Phase bit, derives next keys, and MUST update send keys and acknowledge with the updated keys before sending any further packets. Key update is complete when the initiator receives acknowledgment of a packet protected with the new keys.
Header protection masks 4 least-significant bits of the first byte (long header) or 5 bits (short header) plus the Packet Number field using a 5-byte mask. The mask is produced by AES-ECB (for AES AEADs) or raw ChaCha20 (for AEAD_CHACHA20_POLY1305) over a 16-byte ciphertext sample taken 4 bytes after the start of the Packet Number field.
Initial packet secrets are derived using HKDF-Extract with a fixed 20-byte salt (0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a) and the client's Destination Connection ID as IKM, then HKDF-Expand-Label with labels 'client in' and 'server in' to produce 32-byte secrets. SHA-256 is used as the hash function.
Packet protection keys are derived from TLS secrets using HKDF-Expand-Label with labels 'quic key' (AEAD key), 'quic iv' (IV, minimum of AEAD nonce length or 8 bytes), and 'quic hp' (header protection key). Key updates use label 'quic ku'.
The AEAD nonce N is formed by left-padding the 62-bit reconstructed QUIC packet number (in network byte order) with zeros to the size of the IV, then XOR-ing with the packet protection IV derived using the 'quic iv' label.
The Retry Integrity Tag is 128 bits computed using AEAD_AES_128_GCM with a fixed key (0xbe0c690b9f66575a1d766b54e368c84e) and nonce (0x461599d35d632bf2239825bb) over the Retry Pseudo-Packet, which prepends the Original Destination Connection ID to the transmitted Retry packet body.