RFC 9001
Using TLS to Secure QUIC
RFC 9001: Using TLS to Secure QUIC ๐๐
okay bestie, this is RFC 9001, and it's all about how we lock down QUIC with TLS ๐โจ It's an Internet Standards Track doc, written by M. Thomson, Ed. from Mozilla and S. Turner, Ed. from sn3rd (the "Ed." just means they're the editors ๐ ). It came out in May 2021 and is ISSN 2070-1721 ๐ธ The title block doesn't list anything it obsoletes or updates, so there's no earlier RFC it replaces or amends here ๐ง
Abstract โจ
so the whole point is pretty simple: this doc describes how Transport Layer Security (TLS) gets used to secure QUIC ๐๐
Status of This Memo ๐
this one is an Internet Standards Track document ๐
it's a product of the Internet Engineering Task Force (IETF), so it represents the consensus of the IETF community. it got public review and was approved for publication by the Internet Engineering Steering Group (IESG) ๐ซถ if you want more on Internet Standards, check out Section 2 of RFC 7841 ๐
for the current status, any errata, and how to send feedback on it, head to https://www.rfc-editor.org/info/rfc9001 ๐ฆ
Copyright Notice ยฉ๏ธ
Copyright (c) 2021 IETF Trust and the persons identified as the document authors. All rights reserved. ๐
this doc is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they stand on the date of publication. please actually read those, bestie, because they spell out your rights and restrictions for this document ๐
code components pulled out of this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come without warranty, as the Simplified BSD License describes ๐
Table of Contents ๐บ๏ธ
1. Introduction
2. Notational Conventions
2.1. TLS Overview
3. Protocol Overview
4. Carrying TLS Messages
4.1. Interface to TLS
4.1.1. Handshake Complete
4.1.2. Handshake Confirmed
4.1.3. Sending and Receiving Handshake Messages
4.1.4. Encryption Level Changes
4.1.5. TLS Interface Summary
4.2. TLS Version
4.3. ClientHello Size
4.4. Peer Authentication
4.5. Session Resumption
4.6. 0-RTT
4.6.1. Enabling 0-RTT
4.6.2. Accepting and Rejecting 0-RTT
4.6.3. Validating 0-RTT Configuration
4.7. HelloRetryRequest
4.8. TLS Errors
4.9. Discarding Unused Keys
4.9.1. Discarding Initial Keys
4.9.2. Discarding Handshake Keys
4.9.3. Discarding 0-RTT Keys
5. Packet Protection
5.1. Packet Protection Keys
5.2. Initial Secrets
5.3. AEAD Usage
5.4. Header Protection
5.4.1. Header Protection Application
5.4.2. Header Protection Sample
5.4.3. AES-Based Header Protection
5.4.4. ChaCha20-Based Header Protection
5.5. Receiving Protected Packets
5.6. Use of 0-RTT Keys
5.7. Receiving Out-of-Order Protected Packets
5.8. Retry Packet Integrity
6. Key Update
6.1. Initiating a Key Update
6.2. Responding to a Key Update
6.3. Timing of Receive Key Generation
6.4. Sending with Updated Keys
6.5. Receiving with Different Keys
6.6. Limits on AEAD Usage
6.7. Key Update Error Code
7. Security of Initial Messages
8. QUIC-Specific Adjustments to the TLS Handshake
8.1. Protocol Negotiation
8.2. QUIC Transport Parameters Extension
8.3. Removing the EndOfEarlyData Message
8.4. Prohibit TLS Middlebox Compatibility Mode
9. Security Considerations
9.1. Session Linkability
9.2. Replay Attacks with 0-RTT
9.3. Packet Reflection Attack Mitigation
9.4. Header Protection Analysis
9.5. Header Protection Timing Side Channels
9.6. Key Diversity
9.7. Randomness
10. IANA Considerations
11. References
11.1. Normative References
11.2. Informative References
Appendix A. Sample Packet Protection
A.1. Keys
A.2. Client Initial
A.3. Server Initial
A.4. Retry
A.5. ChaCha20-Poly1305 Short Header Packet
Appendix B. AEAD Algorithm Analysis
B.1. Analysis of AEAD_AES_128_GCM and AEAD_AES_256_GCM Usage
Limits
B.1.1. Confidentiality Limit
B.1.2. Integrity Limit
B.2. Analysis of AEAD_AES_128_CCM Usage Limits
Contributors
Authors' Addresses
1. Introduction ๐
this doc explains how QUIC [QUIC-TRANSPORT] gets secured using TLS [TLS13] ๐
TLS 1.3 is a huge glow-up for connection setup compared to older versions โก if nothing gets lost, most new connections can be set up and secured in a single round trip. and on later connections between the same client and server, the client can often send application data straight away, using a zero round-trip setup ๐โโ๏ธ๐จ
this document describes how TLS acts as a security component of QUIC ๐
2. Notational Conventions ๐โจ
the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be read as described in BCP 14 [RFC2119] [RFC8174], but only when they show up in all capitals, just like here ๐ if they're lowercase, they're just regular words ๐
this doc uses the terminology already set up in [QUIC-TRANSPORT] ๐ง
quick note: for brevity we just say TLS, and that means TLS 1.3. a newer version could be used, though. see Section 4.2 ๐ฆ
2.1. TLS Overview ๐ธ
TLS gives two endpoints a way to set up a means of communication over an untrusted medium (think the Internet ๐). it lets peers authenticate each other, and it gives confidentiality and integrity protection for the messages the endpoints swap ๐๐ซถ
under the hood, TLS is a layered protocol, with the structure shown in Figure 1 ๐ฐ
+-------------+------------+--------------+---------+
Content | | | Application | |
Layer | Handshake | Alerts | Data | ... |
| | | | |
+-------------+------------+--------------+---------+
Record | |
Layer | Records |
| |
+---------------------------------------------------+
Figure 1: TLS Layers
every content-layer message (like handshake, alerts, and application data) gets carried as a series of typed TLS records by the record layer ๐ฆ each record is cryptographically protected on its own, and then it's sent over a reliable transport (usually TCP), which handles sequencing and guaranteed delivery ๐
the TLS authenticated key exchange happens between two endpoints, the client and the server. the client starts it and the server responds ๐ if the exchange completes successfully, both sides will agree on a secret โจ TLS supports pre-shared key (PSK) and Diffie-Hellman over either finite fields or elliptic curves ((EC)DHE) key exchanges. PSK is the foundation for Early Data (0-RTT). the (EC)DHE option gives forward secrecy (FS) once the (EC)DHE keys get destroyed ๐ฅ you can also combine the two, so you get forward secrecy while still using the PSK for authentication ๐
after the TLS handshake finishes, the client has learned and authenticated an identity for the server, and the server is optionally able to learn and authenticate an identity for the client ๐ TLS supports X.509 [RFC5280] certificate-based authentication for both server and client. when PSK key exchange is used (like in resumption), knowing the PSK is what authenticates the peer ๐๏ธ
the TLS key exchange resists tampering by attackers, and it produces shared secrets that neither peer can control on its own ๐ก๏ธ
TLS has two basic handshake modes that matter for QUIC:
- A full 1-RTT handshake, where the client can send application data after one round trip, and the server responds right away once it gets the first handshake message from the client โก
- A 0-RTT handshake, where the client uses what it already knows about the server to send application data immediately. this application data can be replayed by an attacker, so 0-RTT isn't suited for carrying instructions that could trigger an action causing unwanted effects if replayed ๐โ ๏ธ
here's a simplified TLS handshake with 0-RTT application data, shown in Figure 2 ๐
Client Server
ClientHello
(0-RTT Application Data) -------->
ServerHello
{EncryptedExtensions}
{Finished}
<-------- [Application Data]
{Finished} -------->
[Application Data] <-------> [Application Data]
() Indicates messages protected by Early Data (0-RTT) Keys
{} Indicates messages protected using Handshake Keys
[] Indicates messages protected using Application Data
(1-RTT) Keys
Figure 2: TLS Handshake with 0-RTT
Figure 2 leaves out the EndOfEarlyData message, since QUIC doesn't use it. see Section 8.3 for the tea ๐ต same goes for ChangeCipherSpec and KeyUpdate messages, which QUIC doesn't use either. ChangeCipherSpec is redundant in TLS 1.3 (see Section 8.4), and QUIC has its own key update mechanism instead (see Section 6) ๐
data gets protected using a number of encryption levels:
- Initial keys
- Early data (0-RTT) keys
- Handshake keys
- Application data (1-RTT) keys
application data can only show up at the early data and application data levels. handshake and alert messages can show up at any level ๐
the 0-RTT handshake can be used if the client and server have talked before. in the 1-RTT handshake, the client can't send protected application data until it has received all of the handshake messages the server sends ๐ฅนโจ
3. Protocol Overview ๐ง
okay so QUIC [QUIC-TRANSPORT] is the one in charge of keeping packets confidential and making sure nobody has messed with them ๐. To do that it uses keys that come out of a TLS handshake [TLS13]. The twist is that QUIC doesn't carry TLS records the way TCP does. Instead, TLS handshake and alert messages ride directly over the QUIC transport, and QUIC takes over the jobs of the TLS record layer, as you can see in Figure 3 ๐ .
+--------------+--------------+ +-------------+
| TLS | TLS | | QUIC |
| Handshake | Alerts | | Applications|
| | | | (h3, etc.) |
+--------------+--------------+-+-------------+
| |
| QUIC Transport |
| (streams, reliability, congestion, etc.) |
| |
+---------------------------------------------+
| |
| QUIC Packet Protection |
| |
+---------------------------------------------+
Figure 3: QUIC Layers
QUIC also leans on TLS for authentication and for negotiating the parameters that matter for security and performance ๐ซถ.
It's not a strict layering, though. These two protocols are more like best friends who cooperate: QUIC runs the TLS handshake, and TLS uses the reliability, ordered delivery, and record layer that QUIC gives it ๐ธ.
There are two main ways the TLS and QUIC parts talk to each other ๐ฌ:
- The TLS component sends and receives messages through the QUIC component, and QUIC gives TLS a reliable stream abstraction to work with.
- The TLS component gives QUIC a steady stream of updates, including (a) new packet protection keys to install and (b) state changes like handshake completion, the server certificate, and so on.
Figure 4 breaks these interactions down in more detail, and it calls out QUIC packet protection on its own ๐ฆ.
+------------+ +------------+
| |<---- Handshake Messages ----->| |
| |<- Validate 0-RTT Parameters ->| |
| |<--------- 0-RTT Keys ---------| |
| QUIC |<------- Handshake Keys -------| TLS |
| |<--------- 1-RTT Keys ---------| |
| |<------- Handshake Done -------| |
+------------+ +------------+
| ^
| Protect | Protected
v | Packet
+------------+
| QUIC |
| Packet |
| Protection |
+------------+
Figure 4: QUIC and TLS Interactions
Here's the big difference from TLS over TCP ๐. QUIC apps that want to send data don't use TLS Application Data records. They send it as STREAM frames or other frame types, and those frames then get carried inside QUIC packets. Iconic, honestly ๐.
4. Carrying TLS Messages ๐ฆ
QUIC carries TLS handshake data inside CRYPTO frames. Each one holds a contiguous block of handshake data, identified by an offset and a length. Those frames get packed into QUIC packets and encrypted under the current encryption level. Just like with TLS over TCP, once the handshake data has been handed to QUIC, QUIC is the one responsible for delivering it reliably ๐ซก. Every chunk TLS produces is linked to the set of keys TLS is using at that moment. So if QUIC has to retransmit that data, it MUST use those same keys, even if TLS has already moved on to newer ones ๐.
Each encryption level maps to a packet number space, and the packet number space in use decides what the frames mean. Some frames aren't allowed in certain packet number spaces, so check Section 12.5 of [QUIC-TRANSPORT] for the details ๐.
Packets can get reordered on the wire, so QUIC uses the packet type to show which keys protected a given packet, as laid out in Table 1 ๐งฉ. When packets of different types need to go out, endpoints SHOULD use coalesced packets to send them in the same UDP datagram ๐.
+=====================+=================+==================+
| Packet Type | Encryption Keys | PN Space |
+=====================+=================+==================+
| Initial | Initial secrets | Initial |
+=====================+-----------------+------------------+
| 0-RTT Protected | 0-RTT | Application data |
+=====================+-----------------+------------------+
| Handshake | Handshake | Handshake |
+=====================+-----------------+------------------+
| Retry | Retry | N/A |
+=====================+-----------------+------------------+
| Version Negotiation | N/A | N/A |
+=====================+-----------------+------------------+
| Short Header | 1-RTT | Application data |
+=====================+-----------------+------------------+
Table 1: Encryption Keys by Packet Type
Section 17 of [QUIC-TRANSPORT] shows how packets at each encryption level fit into the handshake process ๐ง .
4.1. Interface to TLS ๐
As Figure 4 shows, the interface from QUIC to TLS has four main jobs โจ:
- Sending and receiving handshake messages
- Processing stored transport and application state from a resumed session, and deciding whether it's valid to generate or accept 0-RTT data
- Rekeying (both transmit and receive)
- Updating handshake state
TLS might need some extra functions to get configured. The big one is that QUIC and TLS have to agree on which of them is in charge of validating peer credentials, like certificate validation [RFC5280] ๐ชช.
4.1.1. Handshake Complete ๐
In this document, the TLS handshake counts as complete once the TLS stack reports that it's done. That happens when the TLS stack has both sent its Finished message and verified the peer's Finished message. Verifying the peer's Finished message gives both endpoints assurance that earlier handshake messages haven't been modified ๐. Heads up: the handshake doesn't finish at both endpoints at the same time ๐. So any requirement based on handshake completion depends on which endpoint you're looking at.
4.1.2. Handshake Confirmed โ
In this document, the TLS handshake is considered confirmed at the server once the handshake completes. The server MUST send a HANDSHAKE_DONE frame as soon as the handshake is complete ๐. At the client, the handshake is considered confirmed when a HANDSHAKE_DONE frame is received.
A client MAY also consider the handshake confirmed when it receives an acknowledgment for a 1-RTT packet ๐ซ. One way to do this is to record the lowest packet number it sent with 1-RTT keys, then compare that to the Largest Acknowledged field in any received 1-RTT ACK frame. Once the Largest Acknowledged value is greater than or equal to that lowest packet number, the handshake is confirmed ๐.
4.1.3. Sending and Receiving Handshake Messages โจ
okay so TLS needs to be able to send and receive handshake messages to drive the handshake ๐ There are two basic functions on this interface: one where QUIC asks for handshake messages, and one where QUIC hands TLS the bytes that make up handshake messages ๐ฆ
Before the handshake starts, QUIC gives TLS the transport parameters (see Section 8.2) it wants to carry ๐
A QUIC client gets things going by requesting TLS handshake bytes from TLS. It grabs those bytes before sending its first packet ๐ง A QUIC server starts differently: it gives TLS the client's handshake bytes ๐ธ
At any time, the TLS stack at an endpoint has a current sending encryption level and a current receiving encryption level ๐ These encryption levels decide which QUIC packet type and which keys get used to protect data ๐ฅ
Each encryption level has its own sequence of bytes, and those get reliably sent to the peer in CRYPTO frames ๐ซถ When TLS hands over handshake bytes to send, they get tacked onto the handshake bytes for the current encryption level. That encryption level then determines the type of packet the resulting CRYPTO frame rides in. See Table 1 for the details ๐
There are four encryption levels, which produce keys for Initial, 0-RTT, Handshake, and 1-RTT packets. CRYPTO frames only show up in three of those levels, because the 0-RTT level doesn't carry them ๐ญ The four levels map onto three packet number spaces. Initial and Handshake encrypted packets each get their own separate space, while 0-RTT and 1-RTT packets share the application data packet number space ๐
QUIC takes the unprotected content of TLS handshake records and uses it as the content of CRYPTO frames ๐ TLS record protection isn't used by QUIC at all, so no record protection here, bestie ๐ QUIC puts CRYPTO frames into QUIC packets, and those packets are protected using QUIC packet protection โจ
QUIC CRYPTO frames only carry TLS handshake messages. TLS alerts get turned into QUIC CONNECTION_CLOSE error codes, so see Section 4.8 for that ๐ฅ TLS application data and other content types can't be carried by QUIC at any encryption level. If the TLS stack hands any of those over, that's an error ๐
When an endpoint gets a QUIC packet with a CRYPTO frame from the network, here's what happens ๐
- If the packet uses the current TLS receiving encryption level, the data gets sequenced into the input flow as usual. Just like with STREAM frames, the offset is used to find the right spot in the data sequence. If that produces new data, it gets delivered to TLS in order ๐ฆ
- If the packet comes from a previously installed encryption level, it MUST NOT contain data that extends past the end of the previously received data in that flow. Implementations MUST treat any violations of this requirement as a connection error of type PROTOCOL_VIOLATION ๐
- If the packet comes from a new encryption level, it gets saved for later processing by TLS. Once TLS moves to receiving from that encryption level, the saved data can be handed to TLS. When TLS provides keys for a higher encryption level while there's data from a previous encryption level that TLS hasn't consumed yet, this MUST be treated as a connection error of type PROTOCOL_VIOLATION ๐
Every time TLS gets new data, new handshake bytes get requested from TLS. TLS might not give back any bytes if the handshake messages it has received are incomplete, or if it has nothing to send ๐ธ
The content of CRYPTO frames might be processed incrementally by TLS, or it might get buffered until complete messages or flights are available ๐ง TLS is responsible for buffering handshake bytes that have arrived in order. QUIC is responsible for buffering handshake bytes that arrive out of order, or that belong to encryption levels that aren't ready yet ๐ซถ QUIC doesn't offer any flow control for CRYPTO frames. See Section 7.5 of [QUIC-TRANSPORT] for more ๐
Once the TLS handshake is complete, that gets indicated to QUIC along with any final handshake bytes TLS needs to send. At this point, the transport parameters the peer advertised during the handshake get authenticated. See Section 8.2 โจ
Once the handshake is complete, TLS becomes passive ๐ It can still receive data from its peer and respond to it, but it won't need to send more data unless something specifically asks for it, either an application or QUIC ๐ One reason to send data is that the server might want to hand a client additional or updated session tickets ๐
When the handshake is complete, QUIC only needs to give TLS any data that arrives in CRYPTO streams. Just like during the handshake, new data gets requested from TLS after the received data has been provided ๐ฆ
4.1.4. Encryption Level Changes ๐
As keys at a given encryption level become available to TLS, TLS tells QUIC that reading or writing keys at that encryption level is possible now ๐
New keys always show up as a result of giving inputs to TLS. TLS only provides new keys after it's been initialized (by a client), or when it gets new handshake data ๐
But a TLS implementation could do some of its processing asynchronously. Validating a certificate, for example, can take a while ๐ญ While TLS processing is still going, an endpoint SHOULD buffer received packets if they might be processed using keys that aren't available yet. Those packets can be processed once TLS provides the keys. An endpoint SHOULD also keep responding to packets that can be processed during this time ๐ซถ
After processing inputs, TLS might produce handshake bytes, keys for new encryption levels, or both ๐ง
When a new encryption level becomes available, TLS gives QUIC three things ๐
- A secret
- An Authenticated Encryption with Associated Data (AEAD) function
- A Key Derivation Function (KDF)
These values are based on what TLS negotiates, and QUIC uses them to generate packet and header protection keys. See Section 5 and Section 5.4 ๐ฅ
If 0-RTT is possible, it's ready after the client sends a TLS ClientHello message, or after the server receives that message ๐ธ After giving a QUIC client its first handshake bytes, the TLS stack might signal the switch to 0-RTT keys. On the server, after receiving handshake bytes that contain a ClientHello message, a TLS server might signal that 0-RTT keys are available ๐
TLS only uses one encryption level at a time, but QUIC can use more than one ๐ฆ For instance, after sending its Finished message (using a CRYPTO frame at the Handshake encryption level), an endpoint can send STREAM data using 1-RTT encryption. If the Finished message gets lost, the endpoint uses the Handshake encryption level to retransmit it ๐ญ Reordering or loss of packets can mean QUIC has to handle packets at multiple encryption levels. During the handshake, that means it could be handling packets at higher and lower encryption levels than the one TLS is currently using ๐
Server implementations in particular need to be able to read packets at the Handshake encryption level at the same time as the 0-RTT encryption level ๐ A client could interleave ACK frames protected with Handshake keys alongside its 0-RTT data, and the server needs to process those acknowledgments so it can detect lost Handshake packets โจ
QUIC also needs access to keys that a TLS implementation wouldn't normally have available. For example, a client might need to acknowledge Handshake packets before it's ready to send CRYPTO frames at that encryption level. So TLS needs to provide keys to QUIC before it would produce them for its own use ๐
4.1.5. TLS Interface Summary ๐
Figure 5 sums up the exchange between QUIC and TLS for both client and server ๐ฆ Solid arrows show packets that carry handshake data, and dashed arrows show where application data can be sent. Each arrow is tagged with the encryption level used for that transmission ๐
Client Server
====== ======
Get Handshake
Initial ------------->
Install tx 0-RTT keys
0-RTT - - - - - - - ->
Handshake Received
Get Handshake
<------------- Initial
Install rx 0-RTT keys
Install Handshake keys
Get Handshake
<----------- Handshake
Install tx 1-RTT keys
<- - - - - - - - 1-RTT
Handshake Received (Initial)
Install Handshake keys
Handshake Received (Handshake)
Get Handshake
Handshake ----------->
Handshake Complete
Install 1-RTT keys
1-RTT - - - - - - - ->
Handshake Received
Handshake Complete
Handshake Confirmed
Install rx 1-RTT keys
<--------------- 1-RTT
(HANDSHAKE_DONE)
Handshake Confirmed
Figure 5: Interaction Summary between QUIC and TLS
Figure 5 shows the multiple packets that make up a single "flight" of messages, with each one processed individually so you can see which incoming messages trigger which actions ๐ง It also shows multiple "Get Handshake" invocations, which retrieve handshake messages at different encryption levels. New handshake messages get requested after incoming packets have been processed ๐
Figure 5 is just one possible structure for a simple handshake exchange ๐ซถ The exact process depends on how endpoint implementations are structured and the order packets arrive in. Implementations could use a different number of operations, or run them in other orders ๐
4.2. TLS Version ๐
This document describes how TLS 1.3 [TLS13] is used with QUIC ๐ธ
In practice, the TLS handshake negotiates which version of TLS gets used. If both endpoints support a version newer than 1.3, that version could end up being negotiated. That's fine, as long as the TLS 1.3 features that QUIC uses are supported by the newer version โจ
Clients MUST NOT offer TLS versions older than 1.3 ๐ A badly configured TLS implementation could negotiate TLS 1.2 or some other older version of TLS, so an endpoint MUST terminate the connection if a version of TLS older than 1.3 is negotiated ๐ฅ
4.3. ClientHello Size โจ
okay so bestie, the very first Initial packet a client sends carries the start (or all!) of its first crypto handshake message, which for TLS is the ClientHello ๐ง servers sometimes need to read the WHOLE ClientHello, just to peek at stuff like Server Name Identification (SNI) or Application-Layer Protocol Negotiation (ALPN), before they decide whether to accept the new QUIC connection ๐ and if that ClientHello gets split across several Initial packets, the server would have to hold onto those early fragments, which can eat up a ton of resources if the client's address hasn't been validated yet ๐ญ so to avoid that drama, servers MAY use the Retry feature (see Section 8.1 of [QUIC-TRANSPORT]) so they only buffer partial ClientHello messages from clients with a validated address ๐ซถ
the QUIC packet and framing add at least 36 bytes of overhead to the ClientHello message ๐ and that overhead gets bigger if the client picks a Source Connection ID field longer than zero bytes ๐ฆ also, the overhead doesn't count the token or a Destination Connection ID longer than 8 bytes, and those two might be needed if a server sends a Retry packet, so keep that in mind ๐ธ
a typical TLS ClientHello can easily fit into a 1200-byte packet, periodt โจ but there are a bunch of variables that could push it over that limit ๐ big session tickets, multiple or large key shares, and long lists of supported ciphers, signature algorithms, versions, QUIC transport parameters, and other negotiable parameters and extensions can all make this message grow, and it's giving chaotic ๐
for servers, the connection IDs, tokens, and the size of TLS session tickets can all affect how efficiently a client gets connected ๐ so minimizing the size of these values makes it more likely that clients can actually reuse them AND still fit their entire ClientHello in their first Initial packet ๐
the TLS implementation doesn't have to make sure the ClientHello is big enough to meet QUIC's requirements for datagrams that carry Initial packets, see Section 14.1 of [QUIC-TRANSPORT] for that ๐ QUIC implementations handle it instead, using PADDING frames or packet coalescing to make sure the datagrams are large enough ๐
4.4. Peer Authentication ๐
the requirements for authentication depend on which application protocol is in use ๐ TLS gives you server authentication and lets the server ask for client authentication too ๐ซถ
a client MUST authenticate the identity of the server ๐ค this usually means checking that the server's identity is in a certificate and that the certificate was issued by a trusted entity (see for example [RFC2818]) โจ
Note: where servers give certificates for authentication, the certificate chain can take up a LOT of bytes ๐ญ controlling its size is super critical for performance in QUIC, because servers can only send 3 bytes for every byte received before the client address is validated, see Section 8.1 of [QUIC-TRANSPORT] ๐ you can keep chains small by limiting the number of names or extensions, using keys with small public key representations like ECDSA, or using certificate compression [COMPRESS] ๐
a server MAY request that the client authenticate during the handshake ๐ง and a server MAY refuse a connection if the client can't authenticate when asked ๐ the requirements for client authentication vary based on the application protocol and how it's deployed ๐ธ
a server MUST NOT use post-handshake client authentication (as defined in Section 4.6.2 of [TLS13]) because QUIC's multiplexing means clients can't link the certificate request to the application-level event that triggered it (see [HTTP2-TLS13]) ๐ more specifically, servers MUST NOT send post-handshake TLS CertificateRequest messages, and clients MUST treat receiving one of those as a connection error of type PROTOCOL_VIOLATION ๐จ
4.5. Session Resumption ๐
QUIC can use TLS 1.3's session resumption feature โจ it does this by carrying NewSessionTicket messages in CRYPTO frames after the handshake is complete ๐ซถ resumption can be used to provide 0-RTT, and it can also be used when 0-RTT is turned off ๐ฆ
endpoints that use session resumption might need to remember some info about the current connection when they create a resumed one ๐ง TLS requires that some info gets retained, see Section 4.6.1 of [TLS13] ๐ QUIC itself doesn't depend on any state being kept when resuming a connection unless 0-RTT is also used, see Section 7.4.1 of [QUIC-TRANSPORT] and Section 4.6.1 ๐ application protocols could depend on state that's kept between resumed connections though ๐
clients can store any state needed for resumption along with the session ticket ๐ servers can use the session ticket to help carry state ๐ง
session resumption lets servers link activity on the original connection with the resumed one, which might be a privacy issue for clients ๐ฌ clients can choose not to enable resumption to avoid creating this correlation ๐
clients SHOULD NOT reuse tickets, because that lets entities other than the server correlate connections, see Appendix C.4 of [TLS13] ๐ซ๐
4.6. 0-RTT โก
the 0-RTT feature in QUIC lets a client send application data before the handshake is complete โจ this works because it reuses negotiated parameters from a previous connection ๐ to make it happen, 0-RTT depends on the client remembering critical parameters and giving the server a TLS session ticket that lets the server recover the same info ๐๏ธ
this info includes parameters that determine TLS state (as governed by [TLS13]), QUIC transport parameters, the chosen application protocol, and any info the application protocol might need, see Section 4.6.3 ๐ this info decides how 0-RTT packets and their contents get formed ๐
to make sure both endpoints have the same info, all info used to establish 0-RTT comes from the same connection ๐ซถ endpoints can't selectively ignore info that might change how 0-RTT is sent or processed ๐ค
[TLS13] sets a limit of seven days on the time between the original connection and any attempt to use 0-RTT ๐ there are other constraints on 0-RTT usage too, especially the ones caused by the possible exposure to replay attacks, see Section 9.2 ๐ก๏ธ
4.6.1. Enabling 0-RTT ๐
the TLS early_data extension in the NewSessionTicket message is defined to convey (in the max_early_data_size parameter) how much TLS 0-RTT data the server is willing to accept ๐ฆ QUIC doesn't use TLS early data though ๐
QUIC uses 0-RTT packets to carry early data instead ๐ฆ so the max_early_data_size parameter gets repurposed to hold a sentinel value of 0xffffffff, which means the server is willing to accept QUIC 0-RTT data โจ to say the server does NOT accept 0-RTT data, the early_data extension is left out of the NewSessionTicket ๐ the amount of data a client can send in QUIC 0-RTT is controlled by the initial_max_data transport parameter the server supplies ๐
servers MUST NOT send the early_data extension with a max_early_data_size field set to any value other than 0xffffffff ๐ซ a client MUST treat receiving a NewSessionTicket that has an early_data extension with any other value as a connection error of type PROTOCOL_VIOLATION ๐
a client that wants to send 0-RTT packets uses the early_data extension in the ClientHello of a later handshake, see Section 4.2.10 of [TLS13] ๐ง then it sends application data in 0-RTT packets ๐ธ
a client that tries 0-RTT might also include an address validation token if the server has sent a NEW_TOKEN frame, see Section 8.1 of [QUIC-TRANSPORT] ๐
4.6.2. Accepting and Rejecting 0-RTT ๐
a server accepts 0-RTT by sending an early_data extension in the EncryptedExtensions, see Section 4.2.10 of [TLS13] โ then the server processes and acknowledges the 0-RTT packets it gets ๐
a server rejects 0-RTT by sending the EncryptedExtensions without an early_data extension ๐ซ a server will ALWAYS reject 0-RTT if it sends a TLS HelloRetryRequest ๐ญ when rejecting 0-RTT, a server MUST NOT process any 0-RTT packets, even if it could ๐
and when 0-RTT was rejected, a client SHOULD treat receiving an acknowledgment for a 0-RTT packet as a connection error of type PROTOCOL_VIOLATION, if it's able to detect that condition ๐ต๏ธ
when 0-RTT gets rejected, every connection characteristic the client assumed might be wrong ๐ฌ that includes the choice of application protocol, transport parameters, and any application configuration ๐ so the client MUST reset the state of all streams, including application state bound to those streams ๐งน
a client MAY try 0-RTT again if it receives a Retry or Version Negotiation packet ๐ those packets do NOT signify rejection of 0-RTT ๐ซถ
4.6.3. Validating 0-RTT Configuration ๐
when a server gets a ClientHello with the early_data extension, it has to decide whether to accept or reject the 0-RTT data from the client ๐ค some of that decision is made by the TLS stack, for example by checking that the cipher suite being resumed was included in the ClientHello, see Section 4.2.10 of [TLS13] ๐ even when the TLS stack has no reason to reject 0-RTT data, the QUIC stack or the application protocol using QUIC might still reject it, because the transport or application config tied to the resumed session isn't compatible with the server's current config ๐
QUIC needs extra transport state associated with a 0-RTT session ticket ๐๏ธ one common way to do this is with stateless session tickets, storing that state inside the session ticket itself ๐ง application protocols that use QUIC might have similar requirements about associating or storing state ๐ธ this associated state is used to decide whether 0-RTT data must be rejected ๐ for example, HTTP/3 settings [QUIC-HTTP] determine how 0-RTT data from the client gets interpreted ๐ฆ other applications using QUIC could have different rules for accepting or rejecting 0-RTT data ๐
4.7. HelloRetryRequest ๐
the HelloRetryRequest message (see Section 4.1.4 of [TLS13]) can be used to ask a client to provide new info, like a key share, or to validate some characteristic of the client ๐ from QUIC's point of view, HelloRetryRequest isn't treated any differently from other crypto handshake messages carried in Initial packets ๐ง even though it's possible in principle to use this feature for address verification, QUIC implementations SHOULD use the Retry feature instead, see Section 8.1 of [QUIC-TRANSPORT] ๐โจ
4.8. TLS Errors ๐ฅ
okay so if TLS hits an error, it generates an appropriate alert, as defined in Section 6 of [TLS13] ๐
A TLS alert gets turned into a QUIC connection error ๐ซถ You take the AlertDescription value and add 0x0100 to it, and boom, you've got a QUIC error code from the range reserved for CRYPTO_ERROR (see Section 20.1 of [QUIC-TRANSPORT]) โจ That value rides along in a CONNECTION_CLOSE frame of type 0x1c.
QUIC can only carry the alert level "fatal" ๐ค In TLS 1.3, the only real uses of the "warning" level are signalling connection close (see Section 6.1 of [TLS13]). QUIC has its own ways to end connections, and the TLS connection only closes when an error shows up, so a QUIC endpoint MUST treat any alert from TLS as if it were at the "fatal" level. no notes, no drama ๐ โโ๏ธ
QUIC lets you use a generic code in place of a specific one (see Section 11 of [QUIC-TRANSPORT]). For TLS alerts, that includes swapping any alert for a generic one, like handshake_failure (0x0128 in QUIC). Endpoints MAY use a generic error code to avoid possibly exposing confidential information. ๐
4.9. Discarding Unused Keys ๐๏ธ
Once QUIC has finished moving to a new encryption level, the packet protection keys for previous encryption levels can be discarded โจ This happens several times during the handshake, and also whenever keys are updated (see Section 6).
Keys don't get tossed the second new ones show up ๐ If packets from a lower encryption level contain CRYPTO frames, the frames that retransmit that data MUST be sent at the same encryption level. Similarly, an endpoint generates acknowledgments for packets at the same encryption level as the packet being acknowledged. So keys for a lower encryption level can still be needed for a short time after keys for a newer one are available ๐ง
An endpoint can't discard keys for a given encryption level unless it has received all the cryptographic handshake messages from its peer at that level, and its peer has done the same ๐ค Different methods for figuring this out are given for Initial keys (Section 4.9.1) and Handshake keys (Section 4.9.2). These methods don't stop packets from being received or sent at that encryption level, because a peer might not have received all the acknowledgments it needs.
Even if an endpoint keeps older keys, new data MUST be sent at the highest currently available encryption level. Only ACK frames and retransmissions of data in CRYPTO frames are sent at a previous encryption level. These packets MAY also include PADDING frames. ๐
4.9.1. Discarding Initial Keys ๐ฃ
Packets protected with Initial secrets (Section 5.2) aren't authenticated, which means an attacker could spoof packets to mess with a connection ๐ฌ To limit that, Initial packet protection keys get tossed way more aggressively than other keys.
Successfully using Handshake packets tells you no more Initial packets need to be exchanged, since those keys can only be produced after receiving all CRYPTO frames from Initial packets ๐ฏ So a client MUST discard Initial keys when it first sends a Handshake packet, and a server MUST discard Initial keys when it first successfully processes a Handshake packet. Endpoints MUST NOT send Initial packets after this point. ๐ซ
This results in abandoning loss recovery state for the Initial encryption level and ignoring any outstanding Initial packets. bye bye, Initial era ๐
4.9.2. Discarding Handshake Keys ๐
An endpoint MUST discard its Handshake keys when the TLS handshake is confirmed (Section 4.1.2). โ
4.9.3. Discarding 0-RTT Keys โณ
0-RTT and 1-RTT packets share the same packet number space, and clients don't send 0-RTT packets after sending a 1-RTT packet (Section 5.6) ๐งฉ
Therefore, a client SHOULD discard 0-RTT keys as soon as it installs 1-RTT keys, since they have no use after that moment.
Additionally, a server MAY discard 0-RTT keys as soon as it receives a 1-RTT packet. However, due to packet reordering, a 0-RTT packet could arrive after a 1-RTT packet. Servers MAY temporarily retain 0-RTT keys to allow decrypting reordered packets without requiring their contents to be retransmitted with 1-RTT keys. After receiving a 1-RTT packet, servers MUST discard 0-RTT keys within a short time; the RECOMMENDED time period is three times the Probe Timeout (PTO, see [QUIC-RECOVERY]). A server MAY discard 0-RTT keys earlier if it determines that it has received all 0-RTT packets, which can be done by keeping track of missing packet numbers. ๐ต๏ธโโ๏ธ
5. Packet Protection ๐ก๏ธ
As with TLS over TCP, QUIC protects packets with keys derived from the TLS handshake, using the AEAD algorithm [AEAD] negotiated by TLS ๐
QUIC packets have varying protections depending on their type:
- Version Negotiation packets have no cryptographic protection.
- Retry packets use
AEAD_AES_128_GCMto provide protection against accidental modification and to limit the entities that can produce a valid Retry; see Section 5.8. - Initial packets use
AEAD_AES_128_GCMwith keys derived from theDestination Connection IDfield of the first Initial packet sent by the client; see Section 5.2. - All other packets have strong cryptographic protections for confidentiality and integrity, using keys and algorithms negotiated by TLS. this is the good stuff ๐
This section describes how packet protection is applied to Handshake packets, 0-RTT packets, and 1-RTT packets. The same packet protection process is applied to Initial packets. However, as it is trivial to determine the keys used for Initial packets, these packets are not considered to have confidentiality or integrity protection. Retry packets use a fixed key and so similarly lack confidentiality and integrity protection. ๐
5.1. Packet Protection Keys ๐
QUIC derives packet protection keys in the same way that TLS derives record protection keys โจ
Each encryption level has separate secret values for protection of packets sent in each direction. These traffic secrets are derived by TLS (see Section 7.1 of [TLS13]) and are used by QUIC for all encryption levels except the Initial encryption level. The secrets for the Initial encryption level are computed based on the client's initial Destination Connection ID, as described in Section 5.2.
The keys used for packet protection are computed from the TLS secrets using the KDF provided by TLS. In TLS 1.3, the HKDF-Expand-Label function described in Section 7.1 of [TLS13] is used with the hash function from the negotiated cipher suite. All uses of HKDF-Expand-Label in QUIC use a zero-length Context. ๐งฎ
Note that labels, which are described using strings, are encoded as bytes using ASCII [ASCII] without quotes or any trailing NUL byte.
Other versions of TLS MUST provide a similar function in order to be used with QUIC. ๐
The current encryption level secret and the label "quic key" are input to the KDF to produce the AEAD key; the label "quic iv" is used to derive the Initialization Vector (IV); see Section 5.3. The header protection key uses the "quic hp" label; see Section 5.4. Using these labels provides key separation between QUIC and TLS; see Section 9.6. key separation is the vibe ๐
Both "quic key" and "quic hp" are used to produce keys, so the Length provided to HKDF-Expand-Label along with these labels is determined by the size of keys in the AEAD or header protection algorithm. The Length provided with "quic iv" is the minimum length of the AEAD nonce or 8 bytes if that is larger; see [AEAD].
The KDF used for initial secrets is always the HKDF-Expand-Label function from TLS 1.3; see Section 5.2. ๐ฅ
5.2. Initial Secrets ๐ฃ
Initial packets apply the packet protection process, but use a secret derived from the Destination Connection ID field from the client's first Initial packet ๐ธ
This secret is determined by using HKDF-Extract (see Section 2.2 of [HKDF]) with a salt of 0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a and the input keying material (IKM) of the Destination Connection ID field. This produces an intermediate pseudorandom key (PRK) that is used to derive two separate secrets for sending and receiving.
The secret used by clients to construct Initial packets uses the PRK and the label "client in" as input to the HKDF-Expand-Label function from TLS [TLS13] to produce a 32-byte secret. Packets constructed by the server use the same process with the label "server in". The hash function for HKDF when deriving initial secrets and keys is SHA-256 [SHA]. ๐ง
This process in pseudocode is:
initial_salt = 0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a
initial_secret = HKDF-Extract(initial_salt,
client_dst_connection_id)
client_initial_secret = HKDF-Expand-Label(initial_secret,
"client in", "",
Hash.length)
server_initial_secret = HKDF-Expand-Label(initial_secret,
"server in", "",
Hash.length)
The connection ID used with HKDF-Expand-Label is the Destination Connection ID in the Initial packet sent by the client. This will be a randomly selected value unless the client creates the Initial packet after receiving a Retry packet, where the Destination Connection ID is selected by the server. ๐ฒ
Future versions of QUIC SHOULD generate a new salt value, thus ensuring that the keys are different for each version of QUIC. This prevents a middlebox that recognizes only one version of QUIC from seeing or modifying the contents of packets from future versions. ๐ก๏ธ
The HKDF-Expand-Label function defined in TLS 1.3 MUST be used for Initial packets even where the TLS versions offered do not include TLS 1.3. ๐
The secrets used for constructing subsequent Initial packets change when a server sends a Retry packet to use the connection ID value selected by the server. The secrets do not change when a client changes the Destination Connection ID it uses in response to an Initial packet from the server. ๐
Note: TheDestination Connection IDfield could be any length up to 20 bytes, including zero length if the server sends a Retry packet with a zero-lengthSource Connection IDfield. After a Retry, the Initial keys provide the client no assurance that the server received its packet, so the client has to rely on the exchange that included the Retry packet to validate the server address; see Section 8.1 of [QUIC-TRANSPORT]. ๐
Appendix A contains sample Initial packets. ๐ง
5.3. AEAD Usage โจ
okay so bestie, the AEAD function (see [AEAD]) that QUIC uses for packet protection is just whichever AEAD got negotiated for the TLS connection ๐ like, if TLS is using TLS_AES_128_GCM_SHA256, then the AEAD_AES_128_GCM function is what gets used. iykyk ๐ฆ
QUIC can use any of the cipher suites defined in [TLS13] EXCEPT TLS_AES_128_CCM_8_SHA256 ๐. A cipher suite MUST NOT be negotiated unless a header protection scheme is defined for it ๐. This document defines a header protection scheme for every [TLS13] cipher suite aside from TLS_AES_128_CCM_8_SHA256 ๐. These cipher suites have a 16-byte authentication tag, so their output is 16 bytes bigger than their input ๐ธ.
An endpoint MUST NOT reject a ClientHello that offers a cipher suite it doesn't support ๐, or else no new cipher suite could ever get deployed ๐ญ. This also applies to TLS_AES_128_CCM_8_SHA256 ๐ซถ.
When constructing packets, the AEAD function gets applied BEFORE header protection (see Section 5.4) โจ. The unprotected packet header counts as part of the associated data (A) ๐ง. When processing packets, an endpoint removes header protection first ๐ฅ.
The key and IV for the packet are computed as described in Section 5.1 ๐. The nonce, N, is formed by combining the packet protection IV with the packet number ๐. The 62 bits of the reconstructed QUIC packet number, in network byte order, are left-padded with zeros up to the size of the IV. Then the exclusive OR of the padded packet number and the IV forms the AEAD nonce ๐.
The associated data, A, for the AEAD is the contents of the QUIC header, starting from the first byte of either the short or long header, up to and including the unprotected packet number ๐ฆ.
The input plaintext, P, for the AEAD is the payload of the QUIC packet, as described in [QUIC-TRANSPORT] ๐.
The output ciphertext, C, of the AEAD is sent in place of P โญ.
Some AEAD functions have limits on how many packets can be encrypted under the same key and IV (see Section 6.6) ๐ญ. That limit might be lower than the packet number limit, so watch out ๐. An endpoint MUST initiate a key update (Section 6) before it goes over any limit set for the AEAD in use ๐จ.
5.4. Header Protection ๐ก๏ธ
Parts of QUIC packet headers, especially the Packet Number field, are protected with a key that's derived separately from the packet protection key and IV ๐. The key derived with the "quic hp" label is what gives confidentiality to those fields that on-path elements aren't supposed to see ๐๐.
This protection covers the least significant bits of the first byte, plus the Packet Number field ๐. For long headers, the four least significant bits of the first byte are protected ๐ธ. For short headers, it's the five least significant bits ๐ . For both header forms this covers the reserved bits and the Packet Number Length field, and the Key Phase bit is protected too, but only for short headers ๐ฆ.
The same header protection key gets used for the whole connection, and its value doesn't change after a key update (see Section 6) โจ. That's what lets header protection cover the key phase too ๐ซถ.
This process does NOT apply to Retry or Version Negotiation packets ๐ โโ๏ธ, since those don't carry a protected payload or any of the fields this process protects ๐.
5.4.1. Header Protection Application
Header protection gets applied AFTER packet protection (see Section 5.3) ๐. The ciphertext of the packet is sampled and fed into an encryption algorithm ๐ง, and the algorithm you use depends on the negotiated AEAD ๐.
The output of that algorithm is a 5-byte mask ๐. It gets XORed onto the protected header fields: the least significant bits of the first byte of the packet are masked with the least significant bits of the first mask byte, and the packet number gets masked with the remaining bytes โญ. Any unused mask bytes that show up because of a shorter packet number encoding just go unused ๐.
Figure 6 shows a sample algorithm for applying header protection ๐ฅ. Removing header protection works the same way, except the order changes in how the packet number length (pn_length) gets figured out (here "^" stands for exclusive OR) ๐.
mask = header_protection(hp_key, sample)
pn_length = (packet[0] & 0x03) + 1
if (packet[0] & 0x80) == 0x80:
# Long header: 4 bits masked
packet[0] ^= mask[0] & 0x0f
else:
# Short header: 5 bits masked
packet[0] ^= mask[0] & 0x1f
# pn_offset is the start of the Packet Number field.
packet[pn_offset:pn_offset+pn_length] ^= mask[1:1+pn_length]
Figure 6: Header Protection Pseudocode ๐
Specific header protection functions are defined based on the selected cipher suite (see Section 5.4.3 and Section 5.4.4) ๐ฆ.
Figure 7 shows an example long header packet (Initial) and a short header packet (1-RTT) โจ. It shows which fields in each header are covered by header protection, and which part of the protected payload gets sampled ๐ซถ.
Initial Packet {
Header Form (1) = 1,
Fixed Bit (1) = 1,
Long Packet Type (2) = 0,
Reserved Bits (2), # Protected
Packet Number Length (2), # Protected
Version (32),
DCID Len (8),
Destination Connection ID (0..160),
SCID Len (8),
Source Connection ID (0..160),
Token Length (i),
Token (..),
Length (i),
Packet Number (8..32), # Protected
Protected Payload (0..24), # Skipped Part
Protected Payload (128), # Sampled Part
Protected Payload (..) # Remainder
}
1-RTT Packet {
Header Form (1) = 0,
Fixed Bit (1) = 1,
Spin Bit (1),
Reserved Bits (2), # Protected
Key Phase (1), # Protected
Packet Number Length (2), # Protected
Destination Connection ID (0..160),
Packet Number (8..32), # Protected
Protected Payload (0..24), # Skipped Part
Protected Payload (128), # Sampled Part
Protected Payload (..), # Remainder
}
Figure 7: Header Protection and Ciphertext Sample ๐ธ
Before a TLS cipher suite can be used with QUIC, a header protection algorithm MUST be specified for the AEAD used with that cipher suite ๐. This document defines algorithms for AEAD_AES_128_GCM, AEAD_AES_128_CCM, AEAD_AES_256_GCM (all of these AES AEADs are defined in [AEAD]), and AEAD_CHACHA20_POLY1305 (defined in [CHACHA]) ๐. Before TLS has selected a cipher suite, AES header protection is used (see Section 5.4.3), matching the AEAD_AES_128_GCM packet protection ๐.
5.4.2. Header Protection Sample
The header protection algorithm uses both the header protection key AND a sample of the ciphertext from the packet Payload field ๐ง.
The same number of bytes is always sampled, but a receiving endpoint has to allow for the fact that it won't know the length of the Packet Number field when it removes protection ๐. So the sample of ciphertext starts at an offset of 4 bytes after the start of the Packet Number field ๐. In other words, when sampling for header protection, the Packet Number field is assumed to be 4 bytes long (its maximum possible encoded length) ๐.
An endpoint MUST discard packets that aren't long enough to contain a complete sample ๐ซ.
To make sure there's enough data to sample, packets get padded so that the combined length of the encoded packet number and protected payload is at least 4 bytes longer than the sample needed for header protection ๐. The [TLS13] cipher suites, other than TLS_AES_128_CCM_8_SHA256 (which doesn't have a header protection scheme defined in this document), have 16-byte expansions and 16-byte header protection samples โจ. That means you need at least 3 bytes of frames in the unprotected payload if the packet number is encoded on a single byte, or 2 bytes of frames for a 2-byte packet number encoding ๐ซถ.
The sampled ciphertext can be figured out with this pseudocode:
# pn_offset is the start of the Packet Number field.
sample_offset = pn_offset + 4
sample = packet[sample_offset..sample_offset+sample_length]
For a short header packet, the packet number offset can be calculated as:
pn_offset = 1 + len(connection_id)
And for a long header packet, it can be calculated as:
pn_offset = 7 + len(destination_connection_id) +
len(source_connection_id) +
len(payload_length)
if packet_type == Initial:
pn_offset += len(token_length) +
len(token)
For example, for a packet with a short header, an 8-byte connection ID, and protected with AEAD_AES_128_GCM, the sample takes bytes 13 to 28 inclusive (using zero-based indexing) ๐ฆ.
Multiple QUIC packets might be included in the same UDP datagram ๐. Each packet gets handled separately โญ.
5.4.3. AES-Based Header Protection
This section defines the packet protection algorithm for AEAD_AES_128_GCM, AEAD_AES_128_CCM, and AEAD_AES_256_GCM ๐ . AEAD_AES_128_GCM and AEAD_AES_128_CCM use 128-bit AES in Electronic Codebook (ECB) mode, and AEAD_AES_256_GCM uses 256-bit AES in ECB mode ๐ฅ. AES is defined in [AES] ๐.
This algorithm samples 16 bytes from the packet ciphertext ๐ง. That value is used as the input to AES-ECB ๐. In pseudocode, the header protection function is defined as:
header_protection(hp_key, sample):
mask = AES-ECB(hp_key, sample)
5.4.4. ChaCha20-Based Header Protection
When AEAD_CHACHA20_POLY1305 is in use, header protection uses the raw ChaCha20 function as defined in Section 2.4 of [CHACHA] ๐ธ. This uses a 256-bit key and 16 bytes sampled from the packet protection output ๐.
The first 4 bytes of the sampled ciphertext are the block counter ๐ฆ. A ChaCha20 implementation could take a 32-bit integer in place of a byte sequence, in which case the byte sequence is interpreted as a little-endian value ๐.
The remaining 12 bytes are used as the nonce ๐ซถ. A ChaCha20 implementation might take an array of three 32-bit integers in place of a byte sequence, in which case the nonce bytes are interpreted as a sequence of 32-bit little-endian integers โจ.
The encryption mask is produced by invoking ChaCha20 to protect 5 zero bytes ๐. In pseudocode, the header protection function is defined as:
header_protection(hp_key, sample):
counter = sample[0..3]
nonce = sample[4..15]
mask = ChaCha20(hp_key, counter, nonce, {0,0,0,0,0})
5.5. Receiving Protected Packets ๐ฌ
Once an endpoint successfully receives a packet with a given packet number, it MUST discard all packets in the same packet number space with higher packet numbers if they can't be successfully unprotected with either the same key or, if there's a key update, a subsequent packet protection key (see Section 6) ๐ซ. Similarly, a packet that seems to trigger a key update but can't be unprotected successfully MUST be discarded ๐๏ธ.
Failing to unprotect a packet doesn't necessarily mean there's a protocol error in a peer or an attack ๐. The truncated packet number encoding used in QUIC can cause packet numbers to be decoded incorrectly if they're delayed significantly ๐.
5.6. Use of 0-RTT Keys โจ
okay so if 0-RTT keys are available (peep Section 4.6.1 ๐), there's no replay protection, which means we NEED some rules about how they get used. Otherwise attackers could replay stuff and cause chaos ๐
Of the frames in [QUIC-TRANSPORT], the STREAM, RESET_STREAM, STOP_SENDING, and CONNECTION_CLOSE frames are the potentially unsafe ones for 0-RTT, bc they carry application data. Application data that arrives in 0-RTT could make the server app process it more than once instead of just once ๐ฑ Any extra stuff the server does because of replayed app data could have unwanted consequences. So a client MUST NOT use 0-RTT for application data unless the application it's running specifically asks for it ๐ซถ
An application protocol that uses QUIC MUST include a profile that defines acceptable use of 0-RTT. If it doesn't, 0-RTT can only carry QUIC frames that don't carry application data. For example, the HTTP profile lives in [HTTP-REPLAY] and is used for HTTP/3; see Section 10.9 of [QUIC-HTTP] ๐ธ
Replayed packets might cause extra connection attempts, but processing replayed frames that don't carry application data only changes the state of the affected connection. A TLS handshake cannot be successfully completed using replayed packets ๐
A client MAY wish to apply additional restrictions on what data it sends before the TLS handshake is complete ๐
Otherwise, a client treats 0-RTT keys as equivalent to 1-RTT keys, except it cannot send certain frames with 0-RTT keys; see Section 12.5 of [QUIC-TRANSPORT] ๐
A client that gets an indication that its 0-RTT data has been accepted by a server can keep sending 0-RTT data until it receives all of the server's handshake messages. A client SHOULD stop sending 0-RTT data if it gets an indication that the 0-RTT data has been rejected ๐ฆ
A server MUST NOT use 0-RTT keys to protect packets. Instead it uses 1-RTT keys to protect acknowledgments of 0-RTT packets. A client MUST NOT attempt to decrypt 0-RTT packets it receives, and instead MUST discard them ๐๏ธ
Once a client has installed 1-RTT keys, it MUST NOT send any more 0-RTT packets ๐ซ
Note: 0-RTT data can be acknowledged by the server as it receives it, but any packets containing acknowledgments of 0-RTT data cannot have packet protection removed by the client until the TLS handshake is complete. The 1-RTT keys needed to remove packet protection can't be derived until the client receives all server handshake messages ๐ง
5.7. Receiving Out-of-Order Protected Packets ๐
Because of reordering and loss, protected packets might reach an endpoint before the final TLS handshake messages do. A client can't decrypt 1-RTT packets from the server yet, but a server CAN decrypt 1-RTT packets from the client ๐ Endpoints in either role MUST NOT decrypt 1-RTT packets from their peer before the handshake is complete ๐ โโ๏ธ
Even though a server has 1-RTT keys after getting the first handshake messages from a client, it's still missing some assurances about the client's state:
- The client isn't authenticated, unless the server picked a pre-shared key and validated the client's pre-shared key binder; see Section 4.2.11 of [TLS13] ๐
- The client hasn't shown liveness, unless the server has validated the client's address with a Retry packet or some other means; see Section 8.1 of [QUIC-TRANSPORT] ๐
- Any 0-RTT data the server responds to might be from a replay attack ๐ฌ
So the server's use of 1-RTT keys before the handshake is complete is limited to sending data. A server MUST NOT process incoming 1-RTT protected packets before the TLS handshake is complete. Sending acknowledgments means all the frames in a packet have been processed, so a server can't send acknowledgments for 1-RTT packets until the TLS handshake is complete ๐ง Received packets protected with 1-RTT keys MAY be stored and then decrypted and used once the handshake is done ๐ฆ
Note: TLS implementations might provide all 1-RTT secrets before handshake completion. Even where QUIC implementations have 1-RTT read keys, those keys are not to be used before the handshake is complete.
The server having to wait for the client's Finished message means it depends on that message actually getting delivered. A client can dodge the head-of-line blocking this causes by sending its 1-RTT packets coalesced with a Handshake packet that contains a copy of the CRYPTO frame carrying the Finished message, until one of those Handshake packets gets acknowledged. This lets the server process those packets right away ๐โโ๏ธ๐จ
A server could receive packets protected with 0-RTT keys before it gets a TLS ClientHello. The server MAY keep these packets around for later decryption in anticipation of receiving a ClientHello ๐ง
A client usually gets its 1-RTT keys at the same time the handshake completes. Even if it has 1-RTT secrets, a client MUST NOT process incoming 1-RTT protected packets before the TLS handshake is complete ๐
5.8. Retry Packet Integrity ๐
Retry packets (see Section 17.2.5 of [QUIC-TRANSPORT]) carry a Retry Integrity Tag with two superpowers ๐ช First, it lets you throw away packets that the network accidentally corrupted. Second, only an entity that can see an Initial packet can send a valid Retry packet. Iconic ๐
The Retry Integrity Tag is a 128-bit field, computed as the output of AEAD_AES_128_GCM [AEAD] using these inputs:
- The secret key, K, is 128 bits equal to
0xbe0c690b9f66575a1d766b54e368c84e. - The nonce, N, is 96 bits equal to
0x461599d35d632bf2239825bb. - The plaintext, P, is empty.
- The associated data, A, is the contents of the Retry Pseudo-Packet, as shown in Figure 8:
The secret key and the nonce are derived by calling HKDF-Expand-Label with 0xd9c9943e6101fd200021506bcc02814c73030f25c79d71ce876eca876e6fca8e as the secret, using the labels "quic key" and "quic iv" (Section 5.1) ๐งฉ
Retry Pseudo-Packet {
ODCID Length (8),
Original Destination Connection ID (0..160),
Header Form (1) = 1,
Fixed Bit (1) = 1,
Long Packet Type (2) = 3,
Unused (4),
Version (32),
DCID Len (8),
Destination Connection ID (0..160),
SCID Len (8),
Source Connection ID (0..160),
Retry Token (..),
}
Figure 8: Retry Pseudo-Packet ๐
The Retry Pseudo-Packet never gets sent over the wire. It's built by taking the transmitted Retry packet, removing the Retry Integrity Tag, and prepending these two fields:
- ODCID Length: The
ODCID Lengthfield holds the length in bytes of the Original Destination Connection ID field that comes right after it, encoded as an 8-bit unsigned integer ๐ - Original Destination Connection ID: This holds the value of the Destination Connection ID from the Initial packet that the Retry is responding to. Its length is given by
ODCID Length. Because of this field, only an entity that observes the Initial packet can send a valid Retry packet ๐
6. Key Update ๐
Once the handshake is confirmed (see Section 4.1.2), an endpoint MAY start a key update โจ
The Key Phase bit tells you which packet protection keys were used to protect the packet. It starts at 0 for the first set of 1-RTT packets and gets toggled to signal each later key update ๐
The Key Phase bit lets a recipient notice a change in keying material without having to receive the first packet that triggered the change. An endpoint that spots a changed Key Phase bit updates its keys and decrypts the packet containing the new value ๐ต๏ธโโ๏ธ
When one side starts a key update, both endpoints update their keys. This is different from TLS, where endpoints can update keys independently ๐ซถ
This mechanism replaces TLS's key update mechanism, which relies on KeyUpdate messages sent using 1-RTT encryption keys. Endpoints MUST NOT send a TLS KeyUpdate message. Endpoints MUST treat receiving a TLS KeyUpdate message as a connection error of type 0x010a, equivalent to a fatal TLS alert of unexpected_message; see Section 4.8 ๐ซ
Figure 9 shows a key update process, where the initial set of keys (marked @M) gets replaced by updated keys (marked @N). The value of the Key Phase bit is shown in brackets [] ๐
Initiating Peer Responding Peer
@M [0] QUIC Packets
... Update to @N
@N [1] QUIC Packets
-------->
Update to @N ...
QUIC Packets [1] @N
<--------
QUIC Packets [1] @N
containing ACK
<--------
... Key Update Permitted
@N [1] QUIC Packets
containing ACK for @N packets
-------->
Key Update Permitted ...
Figure 9: Key Update ๐
6.1. Initiating a Key Update
okay so endpoints keep separate read and write secrets for packet protection ๐ to start a key update, an endpoint updates its packet protection write secret and then uses that new one to protect new packets โจ the new write secret gets made from the old one the same way as Section 7.2 of [TLS13] does, using the KDF TLS provides with the label "quic ku" ๐
the key and IV come from that secret like Section 5.1 says, and the header protection key is not updated ๐ซถ
for example, updating write keys with TLS 1.3 uses HKDF-Expand-Label like this:
secret_<n+1> = HKDF-Expand-Label(secret_<n>, "quic ku",
"", Hash.length)
then the endpoint flips the Key Phase bit and uses the updated key and IV for every packet after that ๐ฆ
now the rules, bestie ๐ญ an endpoint MUST NOT start a key update before the handshake is confirmed (Section 4.1.2) ๐ and it MUST NOT start a later key update unless it has received an acknowledgment for a packet that was sent protected with keys from the current key phase. the whole point is that both peers have the keys before another update can start ๐ one way to pull this off is to track the lowest packet number sent in each key phase and the highest acknowledged packet number in the 1-RTT space. once the highest acknowledged number is greater than or equal to the lowest sent number, another key update is good to go ๐
Note: Keys of packets other than the 1-RTT packets are never updated; their keys are derived solely from the TLS handshake state.
the endpoint that starts a key update also updates the keys it uses for receiving ๐ฌ it needs these to read the packets its peer sends after the update ๐ง
an endpoint MUST keep the old keys until it has successfully unprotected a packet sent with the new keys ๐ก๏ธ it SHOULD also keep the old keys for a while after that. toss them too early and delayed packets can get thrown away ๐ฌ the peer reads discarded packets as packet loss, and that can hurt performance ๐
6.2. Responding to a Key Update
a peer is allowed to start a key update after getting an acknowledgment for a packet in the current key phase ๐ธ an endpoint notices a key update when a packet shows up with a key phase that's different from the one on the last packet it sent ๐ to process that packet, it uses the next packet protection key and IV. Section 6.3 has more on how those keys get made ๐ง
if the packet works with the next key and IV, then the peer has started a key update ๐ the endpoint MUST update its send keys to match that key phase, as described in Section 6.1 ๐ sending keys MUST be updated before the endpoint sends an acknowledgment for the packet that came in with the updated keys. by acknowledging that packet using the new keys, the endpoint basically says "update complete" ๐
an endpoint can hold off on sending the packet or acknowledgment based on its normal sending behavior. it doesn't have to rush out a packet just because of a key update โญ the next packet it sends will use the updated keys, and the next packet carrying an acknowledgment will finish the key update ๐ if an endpoint spots a second update before it has sent any packets with updated keys that acknowledge the packet which started the first one, that means its peer updated keys twice without waiting for confirmation ๐ค an endpoint MAY treat those back-to-back key updates as a connection error of type KEY_UPDATE_ERROR ๐จ
an endpoint that gets an acknowledgment inside a packet protected with old keys, where any acknowledged packet was protected with newer keys, MAY treat that as a connection error of type KEY_UPDATE_ERROR ๐ this means the peer got and acknowledged a packet that starts a key update, but never updated its keys in response ๐
6.3. Timing of Receive Key Generation
endpoints responding to a key update that seems to be happening MUST NOT produce a timing side-channel signal that could reveal the Key Phase bit was invalid (see Section 9.5) ๐ต๏ธโโ๏ธ this is so serious, no cap. endpoints can use randomized packet protection keys in place of discarded keys when key updates aren't allowed yet ๐ฒ using randomized keys means that trying to remove packet protection doesn't cause timing differences, and packets with an invalid Key Phase bit get rejected ๐
making new receive keys can also reveal that a key update happened ๐ณ an endpoint MAY generate new keys as part of packet processing, but that creates a timing signal an attacker could use to figure out when key updates happen, which leaks the value of the Key Phase bit ๐ซฃ
endpoints are generally expected to have current and next receive keys ready to go ๐ for a short window after a key update finishes, up to the PTO, endpoints MAY hold off on generating the next set of receive packet protection keys. this lets them keep only two sets of receive keys (see Section 6.5) โจ
once generated, the next set of packet protection keys SHOULD be kept, even if the packet that triggered it gets thrown away later ๐๏ธ packets that look like key updates are easy to forge, and while a key update doesn't take much work, an attacker could trigger this process to cause a DoS ๐ฅ
so endpoints MUST be able to keep two sets of packet protection keys for receiving: the current set and the next set ๐ keeping the previous keys too might help performance, but it isn't essential ๐ท
6.4. Sending with Updated Keys
an endpoint never sends packets protected with old keys ๐ซ only the current keys get used. keys used for protecting packets can be thrown out right after switching to the newer ones ๐๏ธ
packets with higher packet numbers MUST be protected with either the same or newer packet protection keys than packets with lower packet numbers. if an endpoint successfully removes protection using old keys while newer keys were used for packets with lower packet numbers, it MUST treat that as a connection error of type KEY_UPDATE_ERROR ๐ฑ that's a whole red flag ๐ฉ
6.5. Receiving with Different Keys
when a key update is happening, packets protected with older keys might show up late if the network delayed them ๐ข keeping old packet protection keys lets those packets still get processed โ
packets protected with keys from the next key phase use the same Key Phase value as packets protected with keys from the previous key phase. so if you want to process old-key packets, you have to tell the two apart ๐ง one way is packet numbers: a recovered packet number lower than any packet number in the current key phase uses the previous keys, and a recovered packet number higher than any in the current key phase needs the next keys ๐
some care is needed so that picking between previous, current, and next keys doesn't leak a timing side channel that reveals which keys removed protection. see Section 9.5 for more ๐ซถ
another option is keeping only two sets of packet protection keys, swapping previous for next once enough time has passed for reordering in the network to settle. in that case, the Key Phase bit alone can pick the keys ๐ฆ
an endpoint MAY allow about one Probe Timeout (PTO; see [QUIC-RECOVERY]) after moving the next set of receive keys to current before it creates the set after that. those updated keys MAY replace the previous keys at that point ๐ the PTO is a bit subjective, since a peer could see the RTT differently. even so, this time should be long enough that a peer would declare any reordered packets lost even if they were acknowledged, and short enough that a peer can still start more key updates ๐ซ
endpoints have to allow for the chance that a peer can't decrypt packets starting a key update while the peer still has old keys ๐ฅบ endpoints SHOULD wait three times the PTO before starting a key update after receiving an acknowledgment confirming the previous key update was received. not waiting long enough can get packets thrown away ๐
an endpoint SHOULD keep old read keys for no more than three times the PTO after receiving a packet protected with the new keys. after that, the old read keys and their secrets SHOULD be discarded ๐งนโจ
6.6. Limits on AEAD Usage โจ
Okay bestie, this part is about how much work one AEAD key is allowed to do before it needs to retire ๐ญ. Overusing a key gives an attacker a disproportionate advantage when they go after the confidentiality and integrity of your traffic, so limits = safety ๐ซถ.
The usage limits in TLS 1.3 exist to protect confidentiality, and they apply to successful AEAD protection. The integrity side of authenticated encryption also depends on capping how many forgery attempts an attacker gets. TLS handles that by closing the connection the moment any record fails authentication. QUIC does things differently: it quietly ignores any packet it can't authenticate, which means multiple forgery attempts are possible ๐.
QUIC tracks the AEAD confidentiality and integrity limits separately ๐งฎ. The confidentiality limit is about how many packets are encrypted with a given key. The integrity limit is about how many packets are decrypted within a given connection. The details for each AEAD algorithm are just below ๐.
Endpoints MUST count the number of encrypted packets for each set of keys ๐. If the total number of encrypted packets with the same key goes over the confidentiality limit for the selected AEAD, the endpoint MUST stop using those keys ๐. Endpoints MUST initiate a key update before they send more protected packets than the confidentiality limit for the selected AEAD allows. If a key update isn't possible or the integrity limits have been reached, the endpoint MUST stop using the connection and only send stateless resets in response to received packets ๐. It is RECOMMENDED that endpoints immediately close the connection with a connection error of type AEAD_LIMIT_REACHED before they reach a state where key updates aren't possible โจ.
For AEAD_AES_128_GCM and AEAD_AES_256_GCM, the confidentiality limit is 2^23 encrypted packets; see Appendix B.1. For AEAD_CHACHA20_POLY1305, the confidentiality limit is greater than the number of possible packets (2^62) and so can be disregarded. For AEAD_AES_128_CCM, the confidentiality limit is 2^21.5 encrypted packets; see Appendix B.2. Applying a limit lowers the probability that an attacker can tell the AEAD in use apart from a random permutation; see [AEBounds], [ROBUST], and [GCM-MU] ๐.
In addition to counting packets sent, endpoints MUST count the number of received packets that fail authentication during the lifetime of a connection. If the total number of received packets that fail authentication within the connection, across all keys, goes over the integrity limit for the selected AEAD, the endpoint MUST immediately close the connection with a connection error of type AEAD_LIMIT_REACHED and not process any more packets ๐ซ.
For AEAD_AES_128_GCM and AEAD_AES_256_GCM, the integrity limit is 2^52 invalid packets; see Appendix B.1. For AEAD_CHACHA20_POLY1305, the integrity limit is 2^36 invalid packets; see [AEBounds]. For AEAD_AES_128_CCM, the integrity limit is 2^21.5 invalid packets; see Appendix B.2. Applying this limit lowers the probability that an attacker can successfully forge a packet; see [AEBounds], [ROBUST], and [GCM-MU] ๐ก๏ธ.
Endpoints that limit the size of packets MAY use higher confidentiality and integrity limits; see Appendix B for details ๐.
Future analyses and specifications MAY relax confidentiality or integrity limits for an AEAD ๐ฎ.
Any TLS cipher suite that is specified for use with QUIC MUST define limits on the use of the associated AEAD function that preserves margins for confidentiality and integrity. That is, limits MUST be specified for the number of packets that can be authenticated and for the number of packets that can fail authentication. Providing a reference to any analysis the values are based on, and any assumptions used in that analysis, lets limits be adapted to varying usage conditions ๐ง .
6.7. Key Update Error Code ๐จ
The KEY_UPDATE_ERROR error code (0x0e) is used to signal errors related to key updates ๐
.
7. Security of Initial Messages ๐
Initial packets aren't protected with a secret key, so they're open to tampering by an attacker ๐ฌ. QUIC protects against attackers who can't read packets, but it doesn't try to add extra protection against attackers who can observe and inject packets. Some tampering, like modifying the TLS messages themselves, is detectable. But some, like modifying ACKs, is not ๐.
For example, an attacker could inject a packet containing an ACK frame to make it look like a packet hadn't been received, or to create a false impression of the state of the connection (for example, by modifying the ACK Delay). Note that such a packet could cause a legitimate packet to be dropped as a duplicate โ ๏ธ. Implementations SHOULD use caution in relying on any data that is contained in Initial packets that is not otherwise authenticated ๐ซฃ.
It's also possible for the attacker to tamper with data carried in Handshake packets. But because that kind of tampering requires modifying TLS handshake messages, any such tampering will cause the TLS handshake to fail ๐งจ.
8. QUIC-Specific Adjustments to the TLS Handshake ๐ค
Some parts of the TLS handshake work differently when it's used with QUIC ๐ซ.
QUIC also needs extra features from TLS. On top of negotiating cryptographic parameters, the TLS handshake carries and authenticates values for QUIC transport parameters ๐ฆ.
8.1. Protocol Negotiation ๐ฃ๏ธ
QUIC requires that the cryptographic handshake provide authenticated protocol negotiation. TLS uses Application-Layer Protocol Negotiation [ALPN] to pick an application protocol. Unless another mechanism is used for agreeing on an application protocol, endpoints MUST use ALPN for this purpose ๐ค.
When using ALPN, endpoints MUST immediately close a connection (see Section 10.2 of [QUIC-TRANSPORT]) with a no_application_protocol TLS alert (QUIC error code 0x0178; see Section 4.8) if an application protocol isn't negotiated ๐ช. While [ALPN] only specifies that servers use this alert, QUIC clients MUST use error 0x0178 to terminate a connection when ALPN negotiation fails ๐ฅ.
An application protocol MAY restrict the QUIC versions it can operate over ๐ข. Servers MUST select an application protocol compatible with the QUIC version that the client has selected. The server MUST treat the inability to select a compatible application protocol as a connection error of type 0x0178 (no_application_protocol). Similarly, a client MUST treat the selection of an incompatible application protocol by a server as a connection error of type 0x0178 ๐งฉ.
8.2. QUIC Transport Parameters Extension ๐
QUIC transport parameters ride along in a TLS extension. Different versions of QUIC might define a different method for negotiating transport configuration ๐ ๏ธ.
Including transport parameters in the TLS handshake gives these values integrity protection ๐.
enum {
quic_transport_parameters(0x39), (65535)
} ExtensionType;
The extension_data field of the quic_transport_parameters extension contains a value that is defined by the version of QUIC that is in use ๐.
The quic_transport_parameters extension is carried in the ClientHello and the EncryptedExtensions messages during the handshake. Endpoints MUST send the quic_transport_parameters extension. Endpoints that receive ClientHello or EncryptedExtensions messages without the quic_transport_parameters extension MUST close the connection with an error of type 0x016d (equivalent to a fatal TLS missing_extension alert, see Section 4.8) ๐จ.
Transport parameters become available before the handshake completes, and a server might even use them before it finishes ๐. However, the value of transport parameters isn't authenticated until the handshake completes, so any use of these parameters can't depend on their authenticity ๐ค. Any tampering with transport parameters will cause the handshake to fail.
Endpoints MUST NOT send this extension in a TLS connection that does not use QUIC (such as the use of TLS with TCP defined in [TLS13]). A fatal unsupported_extension alert MUST be sent by an implementation that supports this extension if the extension is received when the transport is not QUIC ๐
.
Negotiating the quic_transport_parameters extension causes the EndOfEarlyData to be removed; see Section 8.3 โ๏ธ.
8.3. Removing the EndOfEarlyData Message โ๏ธ
The TLS EndOfEarlyData message isn't used with QUIC. QUIC doesn't rely on this message to mark the end of 0-RTT data or to signal the change to Handshake keys ๐.
Clients MUST NOT send the EndOfEarlyData message. A server MUST treat receipt of a CRYPTO frame in a 0-RTT packet as a connection error of type PROTOCOL_VIOLATION ๐ซ.
As a result, EndOfEarlyData does not appear in the TLS handshake transcript ๐.
8.4. Prohibit TLS Middlebox Compatibility Mode ๐
Appendix D.4 of [TLS13] describes a tweak to the TLS 1.3 handshake as a workaround for bugs in some middleboxes. The TLS 1.3 middlebox compatibility mode involves setting the legacy_session_id field to a 32-byte value in the ClientHello and ServerHello, then sending a change_cipher_spec record. Both the field and the record carry no semantic content and are ignored ๐ง.
This mode has no use in QUIC, since it only applies to middleboxes that interfere with TLS over TCP. QUIC also provides no means to carry a change_cipher_spec record. A client MUST NOT request the use of the TLS 1.3 compatibility mode. A server SHOULD treat the receipt of a TLS ClientHello with a non-empty legacy_session_id field as a connection error of type PROTOCOL_VIOLATION ๐.
9. Security Considerations ๐ก๏ธ
All of the security considerations that apply to TLS also apply to the use of TLS in QUIC. Reading all of [TLS13] and its appendices is the best way to gain an understanding of QUIC's security properties ๐. This is serious business, bestie ๐จ.
This section summarizes some of the more important security aspects specific to the TLS integration, though there are many security-relevant details in the remainder of the document ๐.
9.1. Session Linkability ๐
Use of TLS session tickets allows servers and possibly other entities to correlate connections made by the same client; see Section 4.5 for details ๐๏ธ.
9.2. Replay Attacks with 0-RTT โจ
okay so, like Section 8 of [TLS13] explains, TLS early data has an exposure to replay attacks ๐ฌ and 0-RTT in QUIC is giving the same vibe, bestie ๐.
Endpoints MUST implement and use the replay protections from [TLS13] ๐ก๏ธ. ngl though, those protections are imperfect, so you have to think about replay risk on top of them ๐ซถ.
QUIC is not vulnerable to replay attacks, except through whatever application protocol info it might be carrying ๐ง. The way QUIC manages its protocol state with the frame types from [QUIC-TRANSPORT] is not vulnerable to replay. Processing QUIC frames is idempotent, so replayed, reordered, or lost frames can't push a connection into an invalid state ๐ . QUIC connections don't leave effects that stick around past the connection's lifetime, unless the application protocol QUIC serves causes them ๐ธ.
TLS session tickets and address validation tokens carry QUIC configuration info between connections ๐๏ธ. Basically, they help a server quickly recover the state it needs for connection establishment and address validation โจ. These MUST NOT be used to communicate application semantics between endpoints, and clients MUST treat them as opaque values ๐. Since these tokens can get reused, they need stronger protection against replay ๐.
A server that accepts 0-RTT on a connection pays a higher cost than one that accepts a connection without it ๐ธ. That includes higher processing and computation costs. Servers need to weigh the probability of replay and all the associated costs before accepting 0-RTT ๐งฎ.
Ultimately, the responsibility for managing replay risk with 0-RTT belongs to the application protocol ๐. An application protocol that uses QUIC MUST describe how it uses 0-RTT and what measures it uses to protect against replay attacks ๐. A replay risk analysis needs to consider every QUIC protocol feature that carries application semantics ๐.
Disabling 0-RTT entirely is the most effective defense against replay attacks ๐ซโจ.
QUIC extensions MUST either describe how replay attacks affect their operation or prohibit use of the extension in 0-RTT ๐งฉ. Application protocols MUST either prohibit extensions that carry application semantics in 0-RTT or provide replay mitigation strategies ๐.
9.3. Packet Reflection Attack Mitigation ๐ช
A small ClientHello that makes a server send back a big pile of handshake messages can be abused in packet reflection attacks to amplify the traffic an attacker generates ๐ข๐ญ.
QUIC has three defenses against this ๐ก๏ธ. First, the packet containing a ClientHello MUST be padded to a minimum size ๐. Second, when responding to an unverified source address, the server is forbidden from sending more than three times as many bytes as it has received (see Section 8.1 of [QUIC-TRANSPORT]) ๐ง. Finally, because acknowledgments of Handshake packets are authenticated, a blind attacker can't forge them ๐. Put together, these defenses limit how much amplification is possible ๐.
9.4. Header Protection Analysis ๐ฌ
[NAN] analyzes authenticated encryption algorithms that provide nonce privacy, which it calls "Hide Nonce" (HN) transforms ๐งช. The general header protection construction in this document is one of those algorithms (HN1) ๐. Header protection gets applied after the packet protection AEAD. It samples a set of bytes ("sample") from the AEAD output and encrypts the header field with a pseudorandom function (PRF), like this:
protected_field = field XOR PRF(hp_key, sample)
The header protection variants in this document use a pseudorandom permutation (PRP) instead of a generic PRF ๐. But since all PRPs are also PRFs [IMC], these variants don't deviate from the HN1 construction ๐ง .
Because "hp_key" is distinct from the packet protection key, header protection achieves AE2 security as defined in [NAN]. That means it guarantees privacy of "field", the protected packet header ๐ฅนโจ. Future header protection variants based on this construction MUST use a PRF so they get equivalent security guarantees ๐ฆ.
Using the same key and ciphertext sample more than once is a risk to header protection ๐จ. If two different headers are protected with the same key and ciphertext sample, the exclusive OR of the protected fields is revealed ๐ฑ. Assuming the AEAD acts as a PRF, if L bits are sampled, the odds of two ciphertext samples being identical approach 2^(-L/2), which is the birthday bound ๐. For the algorithms described in this document, that probability is one in 2^64 ๐.
To stop an attacker from modifying packet headers, the header is transitively authenticated by packet protection, since the entire packet header is part of the authenticated additional data ๐. Protected fields that get falsified or modified can only be detected once packet protection is removed ๐ต๏ธโโ๏ธ.
9.5. Header Protection Timing Side Channels โฑ๏ธ
An attacker could guess values for packet numbers or Key Phase and have an endpoint confirm those guesses through timing side channels ๐. Guesses for the packet number length can be tried and exposed the same way ๐. If a recipient discards packets with duplicate packet numbers without trying to remove packet protection, timing side channels could reveal that the packet number matches a packet it already received ๐ฌ. For authentication to be free from side channels, the whole process of header protection removal, packet number recovery, and packet protection removal MUST be applied together, without timing or other side channels ๐ซฃ.
For sending, building and protecting packet payloads and packet numbers MUST be free from side channels that would reveal the packet number or its encoded size ๐ฆ.
During a key update, the time it takes to generate new keys could reveal through timing side channels that a key update happened ๐ฐ๏ธ. Or, if an attacker injects packets, this side channel could reveal the Key Phase value on those injected packets ๐. So after receiving a key update, an endpoint SHOULD generate and save the next set of receive packet protection keys, as described in Section 6.3 ๐. By generating new keys before a key update arrives, receiving packets won't create timing signals that leak the Key Phase value ๐คซโจ.
This only works if the key generation isn't happening during packet processing, and it can mean endpoints have to keep three sets of packet protection keys for receiving: one for the previous key phase, one for the current key phase, and one for the next key phase ๐๏ธ. Endpoints can instead choose to delay generating the next receive packet protection keys until they discard the old keys, so only two sets of receive keys need to be kept at any moment ๐งบ.
9.6. Key Diversity ๐
When using TLS, the central key schedule of TLS is used ๐๏ธ. Because the TLS handshake messages get folded into the calculation of secrets, including the QUIC transport parameters extension makes sure the handshake and 1-RTT keys aren't the same as the ones a server running TLS over TCP might produce ๐. To avoid any chance of cross-protocol key synchronization, extra measures are included to improve key separation ๐.
QUIC packet protection keys and IVs are derived using a different label than the equivalent keys in TLS ๐ท๏ธ.
To keep that separation, a new version of QUIC SHOULD define new key derivation labels for the packet protection key and IV, plus the header protection keys ๐. This version of QUIC uses the string "quic" ๐. Other versions can use a version-specific label in place of that string ๐ฆ.
The initial secrets use a key that's specific to the negotiated QUIC version ๐ง. New QUIC versions SHOULD define a new salt value for calculating initial secrets ๐ธ.
9.7. Randomness ๐ฒ
QUIC depends on endpoints being able to generate secure random numbers, both directly for protocol values like the connection ID and indirectly through TLS ๐ซถ. For guidance on secure random number generation, see [RFC4086] ๐โจ.
10. IANA Considerations ๐๏ธ
IANA has registered codepoint 57 (or 0x39) for the quic_transport_parameters extension (defined in Section 8.2) in the "TLS ExtensionType Values" registry [TLS-REGISTRIES] ๐ .
The Recommended column for this extension is marked Yes ๐. The TLS 1.3 column includes CH (ClientHello) and EE (EncryptedExtensions) ๐งพ.
+=======+===========================+=====+=============+===========+
| Value | Extension Name | TLS | Recommended | Reference |
| | | 1.3 | | |
+=======+===========================+=====+=============+===========+
| 57 | quic_transport_parameters | CH, | Y | This |
| | | EE | | document |
+-------+---------------------------+-----+-------------+-----------+
Table 2: TLS ExtensionType Values Registry Entry
11. References ๐โจ
11.1. Normative References ๐
[AEAD]McGrew, D., "An Interface and Algorithms for Authenticated Encryption", RFC 5116, DOI 10.17487/RFC5116, January 2008,<https://www.rfc-editor.org/info/rfc5116>.[AES]"Advanced encryption standard (AES)", National Institute of Standards and Technology report, DOI 10.6028/nist.fips.197, November 2001,<https://doi.org/10.6028/nist.fips.197>.[ALPN]Friedl, S., Popov, A., Langley, A., and E. Stephan, "Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension", RFC 7301, DOI 10.17487/RFC7301, July 2014,<https://www.rfc-editor.org/info/rfc7301>.[CHACHA]Nir, Y. and A. Langley, "ChaCha20 and Poly1305 for IETF Protocols", RFC 8439, DOI 10.17487/RFC8439, June 2018,<https://www.rfc-editor.org/info/rfc8439>.[HKDF]Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)", RFC 5869, DOI 10.17487/RFC5869, May 2010,<https://www.rfc-editor.org/info/rfc5869>.[QUIC-RECOVERY]Iyengar, J., Ed. and I. Swett, Ed., "QUIC Loss Detection and Congestion Control", RFC 9002, DOI 10.17487/RFC9002, May 2021,<https://www.rfc-editor.org/info/rfc9002>.[QUIC-TRANSPORT]Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: A UDP-Based Multiplexed and Secure Transport", RFC 9000, DOI 10.17487/RFC9000, May 2021,<https://www.rfc-editor.org/info/rfc9000>.[RFC2119]Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997,<https://www.rfc-editor.org/info/rfc2119>.[RFC4086]Eastlake 3rd, D., Schiller, J., and S. Crocker, "Randomness Requirements for Security", BCP 106, RFC 4086, DOI 10.17487/RFC4086, June 2005,<https://www.rfc-editor.org/info/rfc4086>.[RFC8174]Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017,<https://www.rfc-editor.org/info/rfc8174>.[SHA]Dang, Q., "Secure Hash Standard", National Institute of Standards and Technology report, DOI 10.6028/nist.fips.180-4, July 2015,<https://doi.org/10.6028/nist.fips.180-4>.[TLS-REGISTRIES]Salowey, J. and S. Turner, "IANA Registry Updates for TLS and DTLS", RFC 8447, DOI 10.17487/RFC8447, August 2018,<https://www.rfc-editor.org/info/rfc8447>.[TLS13]Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018,<https://www.rfc-editor.org/info/rfc8446>.
11.2. Informative References ๐ฆ
These are the extra reads, the bonus material that gives the whole spec more context. Here's the full list, bestie ๐
[AEBounds]Luykx, A. and K. Paterson, "Limits on Authenticated Encryption Use in TLS", 28 August 2017,<https://www.isg.rhul.ac.uk/~kp/TLS-AEbounds.pdf>.[ASCII]Cerf, V., "ASCII format for network interchange", STD 80, RFC 20, DOI 10.17487/RFC0020, October 1969,<https://www.rfc-editor.org/info/rfc20>.[CCM-ANALYSIS]Jonsson, J., "On the Security of CTR + CBC-MAC", Selected Areas in Cryptography, SAC 2002, Lecture Notes in Computer Science, vol 2595, pp. 76-93, DOI 10.1007/3-540-36492-7_7, 2003,<https://doi.org/10.1007/3-540-36492-7_7>.[COMPRESS]Ghedini, A. and V. Vasiliev, "TLS Certificate Compression", RFC 8879, DOI 10.17487/RFC8879, December 2020,<https://www.rfc-editor.org/info/rfc8879>.[GCM-MU]Hoang, V., Tessaro, S., and A. Thiruvengadam, "The Multi-user Security of GCM, Revisited: Tight Bounds for Nonce Randomization", CCS '18: Proceedings of the 2018 ACM SIGSAC Conference on Computer and Communications Security, pp. 1429-1440, DOI 10.1145/3243734.3243816, 2018,<https://doi.org/10.1145/3243734.3243816>.[HTTP-REPLAY]Thomson, M., Nottingham, M., and W. Tarreau, "Using Early Data in HTTP", RFC 8470, DOI 10.17487/RFC8470, September 2018,<https://www.rfc-editor.org/info/rfc8470>.[HTTP2-TLS13]Benjamin, D., "Using TLS 1.3 with HTTP/2", RFC 8740, DOI 10.17487/RFC8740, February 2020,<https://www.rfc-editor.org/info/rfc8740>.[IMC]Katz, J. and Y. Lindell, "Introduction to Modern Cryptography, Second Edition", ISBN 978-1466570269, 6 November 2014.[NAN]Bellare, M., Ng, R., and B. Tackmann, "Nonces Are Noticed: AEAD Revisited", Advances in Cryptology - CRYPTO 2019, Lecture Notes in Computer Science, vol 11692, pp. 235-265, DOI 10.1007/978-3-030-26948-7_9, 2019,<https://doi.org/10.1007/978-3-030-26948-7_9>.[QUIC-HTTP]Bishop, M., Ed., "Hypertext Transfer Protocol Version 3 (HTTP/3)", Work in Progress, Internet-Draft, draft-ietf-quic-http-34, 2 February 2021,<https://tools.ietf.org/html/draft-ietf-quic-http-34>.[RFC2818]Rescorla, E., "HTTP Over TLS", RFC 2818, DOI 10.17487/RFC2818, May 2000,<https://www.rfc-editor.org/info/rfc2818>.[RFC5280]Cooper, D., Santesson, S., Farrell, S., Boeyen, S., Housley, R., and W. Polk, "Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, May 2008,<https://www.rfc-editor.org/info/rfc5280>.[ROBUST]Fischlin, M., Gรผnther, F., and C. Janson, "Robust Channels: Handling Unreliable Networks in the Record Layers of QUIC and DTLS 1.3", 16 May 2020,<https://eprint.iacr.org/2020/718>.
Appendix A. Sample Packet Protection ๐
This section is basically a worked example, so you can check your packet protection maths one step at a time. โจ Samples of Initial packets from both client and server, plus a Retry packet, are defined here. All of them use an 8-byte client-chosen Destination Connection ID of 0x8394c8f03e515708. Some intermediate values are included, and every value is shown in hexadecimal ๐ฅ
A.1. Keys ๐
The labels generated during the execution of the HKDF-Expand-Label function (that is, HkdfLabel.label), and the part of the value given to the HKDF-Expand function to produce its output, are these:
client in: 00200f746c73313320636c69656e7420696e00
server in: 00200f746c7331332073657276657220696e00
quic key: 00100e746c7331332071756963206b657900
quic iv: 000c0d746c733133207175696320697600
quic hp: 00100d746c733133207175696320687000
The initial secret is shared by both sides ๐ซถ
initial_secret = HKDF-Extract(initial_salt, cid)
= 7db5df06e7a69e432496adedb0085192
3595221596ae2ae9fb8115c1e9ed0a44
Here are the secrets for protecting client packets ๐
client_initial_secret
= HKDF-Expand-Label(initial_secret, "client in", "", 32)
= c00cf151ca5be075ed0ebfb5c80323c4
2d6b7db67881289af4008f1f6c357aea
key = HKDF-Expand-Label(client_initial_secret, "quic key", "", 16)
= 1f369613dd76d5467730efcbe3b1a22d
iv = HKDF-Expand-Label(client_initial_secret, "quic iv", "", 12)
= fa044b2f42a3fd3b46fb255c
hp = HKDF-Expand-Label(client_initial_secret, "quic hp", "", 16)
= 9f50449e04a0e810283a1e9933adedd2
And here are the secrets for protecting server packets ๐ธ
server_initial_secret
= HKDF-Expand-Label(initial_secret, "server in", "", 32)
= 3c199828fd139efd216c155ad844cc81
fb82fa8d7446fa7d78be803acdda951b
key = HKDF-Expand-Label(server_initial_secret, "quic key", "", 16)
= cf3a5331653c364c88f0f379b6067e37
iv = HKDF-Expand-Label(server_initial_secret, "quic iv", "", 12)
= 0ac1493ca1905853b0bba03e
hp = HKDF-Expand-Label(server_initial_secret, "quic hp", "", 16)
= c206b8d9b9f0f37644430b490eeaa314
A.2. Client Initial ๐ง
okay so the client sends an Initial packet ๐ธ its unprotected payload holds this CRYPTO frame, plus enough PADDING frames to hit a 1162-byte payload:
060040f1010000ed0303ebf8fa56f129 39b9584a3896472ec40bb863cfd3e868
04fe3a47f06a2b69484c000004130113 02010000c000000010000e00000b6578
616d706c652e636f6dff01000100000a 00080006001d00170018001000070005
04616c706e0005000501000000000033 00260024001d00209370b2c9caa47fba
baf4559fedba753de171fa71f50f1ce1 5d43e994ec74d748002b000302030400
0d0010000e0403050306030203080408 050806002d00020101001c0002400100
3900320408ffffffffffffffff050480 00ffff07048000ffff08011001048000
75300901100f088394c8f03e51570806 048000ffff
The unprotected header says the length is 1182 bytes ๐ : that's the 4-byte packet number, 1162 bytes of frames, and the 16-byte authentication tag. The header carries the connection ID and a packet number of 2 โจ
c300000001088394c8f03e5157080000449e00000002
Next up is protecting the payload, which gives you output that gets sampled for header protection. Because this header uses a 4-byte packet number encoding, the first 16 bytes of the protected payload are sampled and then applied to the header like so ๐
sample = d1b1c98dd7689fb8ec11d242b123dc9b
mask = AES-ECB(hp, sample)[0..4]
= 437b9aec36
header[0] ^= mask[0] & 0x0f
= c0
header[18..21] ^= mask[1..4]
= 7b9aec34
header = c000000001088394c8f03e5157080000449e7b9aec34
And the resulting protected packet looks like this ๐ฆ
c000000001088394c8f03e5157080000 449e7b9aec34d1b1c98dd7689fb8ec11
d242b123dc9bd8bab936b47d92ec356c 0bab7df5976d27cd449f63300099f399
1c260ec4c60d17b31f8429157bb35a12 82a643a8d2262cad67500cadb8e7378c
8eb7539ec4d4905fed1bee1fc8aafba1 7c750e2c7ace01e6005f80fcb7df6212
30c83711b39343fa028cea7f7fb5ff89 eac2308249a02252155e2347b63d58c5
457afd84d05dfffdb20392844ae81215 4682e9cf012f9021a6f0be17ddd0c208
4dce25ff9b06cde535d0f920a2db1bf3 62c23e596d11a4f5a6cf3948838a3aec
4e15daf8500a6ef69ec4e3feb6b1d98e 610ac8b7ec3faf6ad760b7bad1db4ba3
485e8a94dc250ae3fdb41ed15fb6a8e5 eba0fc3dd60bc8e30c5c4287e53805db
059ae0648db2f64264ed5e39be2e20d8 2df566da8dd5998ccabdae053060ae6c
7b4378e846d29f37ed7b4ea9ec5d82e7 961b7f25a9323851f681d582363aa5f8
9937f5a67258bf63ad6f1a0b1d96dbd4 faddfcefc5266ba6611722395c906556
be52afe3f565636ad1b17d508b73d874 3eeb524be22b3dcbc2c7468d54119c74
68449a13d8e3b95811a198f3491de3e7 fe942b330407abf82a4ed7c1b311663a
c69890f4157015853d91e923037c227a 33cdd5ec281ca3f79c44546b9d90ca00
f064c99e3dd97911d39fe9c5d0b23a22 9a234cb36186c4819e8b9c5927726632
291d6a418211cc2962e20fe47feb3edf 330f2c603a9d48c0fcb5699dbfe58964
25c5bac4aee82e57a85aaf4e2513e4f0 5796b07ba2ee47d80506f8d2c25e50fd
14de71e6c418559302f939b0e1abd576 f279c4b2e0feb85c1f28ff18f58891ff
ef132eef2fa09346aee33c28eb130ff2 8f5b766953334113211996d20011a198
e3fc433f9f2541010ae17c1bf202580f 6047472fb36857fe843b19f5984009dd
c324044e847a4f4a0ab34f719595de37 252d6235365e9b84392b061085349d73
203a4a13e96f5432ec0fd4a1ee65accd d5e3904df54c1da510b0ff20dcc0c77f
cb2c0e0eb605cb0504db87632cf3d8b4 dae6e705769d1de354270123cb11450e
fc60ac47683d7b8d0f811365565fd98c 4c8eb936bcab8d069fc33bd801b03ade
a2e1fbc5aa463d08ca19896d2bf59a07 1b851e6c239052172f296bfb5e724047
90a2181014f3b94a4e97d117b4381303 68cc39dbb2d198065ae3986547926cd2
162f40a29f0c3c8745c0f50fba3852e5 66d44575c29d39a03f0cda721984b6f4
40591f355e12d439ff150aab7613499d bd49adabc8676eef023b15b65bfc5ca0
6948109f23f350db82123535eb8a7433 bdabcb909271a6ecbcb58b936a88cd4e
8f2e6ff5800175f113253d8fa9ca8885 c2f552e657dc603f252e1a8e308f76f0
be79e2fb8f5d5fbbe2e30ecadd220723 c8c0aea8078cdfcb3868263ff8f09400
54da48781893a7e49ad5aff4af300cd8 04a6b6279ab3ff3afb64491c85194aab
760d58a606654f9f4400e8b38591356f bf6425aca26dc85244259ff2b19c41b9
f96f3ca9ec1dde434da7d2d392b905dd f3d1f9af93d1af5950bd493f5aa731b4
056df31bd267b6b90a079831aaf579be 0a39013137aac6d404f518cfd4684064
7e78bfe706ca4cf5e9c5453e9f7cfd2b 8b4c8d169a44e55c88d4a9a7f9474241
e221af44860018ab0856972e194cd934
A.3. Server Initial ๐
Now the server replies with a payload that has an ACK frame, a CRYPTO frame, and no PADDING frames at all ๐ซถ
02000000000600405a020000560303ee fce7f7b37ba1d1632e96677825ddf739
88cfc79825df566dc5430b9a045a1200 130100002e00330024001d00209d3c94
0d89690b84d08a60993c144eca684d10 81287c834d5311bcf32bb9da1a002b00
020304
The server's header has a new connection ID and a 2-byte packet number encoding, for a packet number of 1 ๐ง
c1000000010008f067a5502a4262b50040750001
So after protection, the header protection sample is taken starting from the third protected byte, which is lowkey a plot twist ๐
sample = 2cd0991cd25b0aac406a5816b6394100
mask = 2ec0d8356a
header = cf000000010008f067a5502a4262b5004075c0d9
The final protected packet is then ๐
cf000000010008f067a5502a4262b500 4075c0d95a482cd0991cd25b0aac406a
5816b6394100f37a1c69797554780bb3 8cc5a99f5ede4cf73c3ec2493a1839b3
dbcba3f6ea46c5b7684df3548e7ddeb9 c3bf9c73cc3f3bded74b562bfb19fb84
022f8ef4cdd93795d77d06edbb7aaf2f 58891850abbdca3d20398c276456cbc4
2158407dd074ee
A.4. Retry ๐
This is a Retry packet that could get sent back in response to the Initial packet from Appendix A.2. Heads up: the integrity check uses the client-chosen connection ID value of 0x8394c8f03e515708, but that value is NOT included in the final Retry packet ๐
ff000000010008f067a5502a4262b574 6f6b656e04a265ba2eff4d829058fb3f
0f2496ba
A.5. ChaCha20-Poly1305 Short Header Packet ๐
This example walks through some of the steps you need to protect a packet with a short header ๐ It uses AEAD_CHACHA20_POLY1305.
Here, TLS produces an application write secret, and a server uses HKDF-Expand-Label on it to get four values: a key, an IV, a header protection key, and the secret that gets used after keys are updated (that last one isn't used again in this example) โจ
secret
= 9ac312a7f877468ebe69422748ad00a1
5443f18203a07d6060f688f30f21632b
key = HKDF-Expand-Label(secret, "quic key", "", 32)
= c6d98ff3441c3fe1b2182094f69caa2e
d4b716b65488960a7a984979fb23e1c8
iv = HKDF-Expand-Label(secret, "quic iv", "", 12)
= e0459b3474bdd0e44a41c144
hp = HKDF-Expand-Label(secret, "quic hp", "", 32)
= 25a282b9e82f06f21f488917a4fc8f1b
73573685608597d0efcb076b0ab7a7a4
ku = HKDF-Expand-Label(secret, "quic ku", "", 32)
= 1223504755036d556342ee9361d25342
1a826c9ecdf3c7148684b36b714881f9
Next, the steps for protecting a minimal packet with an empty Destination Connection ID ๐ฆ This packet holds a single PING frame (so just a payload of 0x01) and has a packet number of 654360564. Using a packet number of length 3 (so 49140 is encoded) means you don't have to pad the payload of the packet. You'd need PADDING frames if the packet number got encoded in fewer bytes ๐ซถ
pn = 654360564 (decimal)
nonce = e0459b3474bdd0e46d417eb0
unprotected header = 4200bff4
payload plaintext = 01
payload ciphertext = 655e5cd55c41f69080575d7999c25a5bfb
The resulting ciphertext is the smallest size possible. One byte gets skipped to make the sample for header protection ๐
sample = 5e5cd55c41f69080575d7999c25a5bfb
mask = aefefe7d03
header = 4cfe4189
And the protected packet is the smallest possible packet size, which is 21 bytes ๐
packet = 4cfe4189655e5cd55c41f69080575d7999c25a5bfb
Appendix B. AEAD Algorithm Analysis ๐ฅ
This section has the analyses used to derive the AEAD algorithm limits for AEAD_AES_128_GCM, AEAD_AES_128_CCM, and AEAD_AES_256_GCM. The math here uses symbols for multiplication (*), division (/), and exponentiation (^), plus parentheses to set precedence. These are the symbols:
t: the size of the authentication tag in bits. For these ciphers,tis 128.n: the size of the block function in bits. For these ciphers,nis 128.k: the size of the key in bits. It's 128 for AEAD_AES_128_GCM and AEAD_AES_128_CCM, and 256 for AEAD_AES_256_GCM.l: the number of blocks in each packet (see below).q: the number of genuine packets created and protected by endpoints. This is the bound on how many packets can be protected before keys get updated.v: the number of forged packets that endpoints will accept. This is the bound on how many forged packets an endpoint can reject before keys get updated.o: the number of offline ideal cipher queries an adversary makes.
The analyses below count the block operations needed to produce each message. That count is done for packets up to 2^11 bytes (l = 2^7) and up to 2^16 bytes (l = 2^12) โจ The 2^11 size is expected to be a limit that matches common deployment patterns, while 2^16 is the largest possible size of a QUIC packet. Only endpoints that strictly limit packet size can use the bigger confidentiality and integrity limits that come from the smaller packet size ๐
For AEAD_AES_128_GCM and AEAD_AES_256_GCM, the message length (l) is the length of the associated data in blocks plus the length of the plaintext in blocks.
For AEAD_AES_128_CCM, the total number of block cipher operations is the sum of: the length of the associated data in blocks, the length of the ciphertext in blocks, the length of the plaintext in blocks, plus 1. The analysis simplifies this to twice the length of the packet in blocks (so "2l = 2^8" for packets limited to 2^11 bytes, or "2l = 2^13" otherwise) ๐ธ That simplification is based on the packet holding all of the associated data and ciphertext. It leads to a one to three block overestimation of the operations per packet.
B.1. Analysis of AEAD_AES_128_GCM and AEAD_AES_256_GCM Usage Limits ๐ฆ
[GCM-MU] gives concrete bounds for AEAD_AES_128_GCM and AEAD_AES_256_GCM as used in TLS 1.3 and QUIC. This section writes that analysis up, with a few simplifying assumptions:
- The number of ciphertext blocks an attacker uses in forgery attempts is bounded by
v * l, which is the number of forgery attempts multiplied by the size of each packet (in blocks). - The amount of offline work an attacker does doesn't dominate the other factors in the analysis.
The bounds in [GCM-MU] are tighter and more complete than the ones in [AEBounds], which allows for larger limits than those described in [TLS13] ๐ง
B.1.1. Confidentiality Limit ๐ฅ
For confidentiality, Theorem (4.3) in [GCM-MU] establishes that, for a single user that doesn't repeat nonces, the dominant term in the distinguishing advantage an attacker can gain between a real and a random AEAD algorithm is:
2 * (q * l)^2 / 2^n
For a target advantage of 2^-57, this gives the relation:
q <= 2^35 / l
So endpoints that don't send packets larger than 2^11 bytes can't protect more than 2^28 packets in a single connection without letting an attacker gain a more significant advantage than the target of 2^-57 ๐ญ For endpoints that allow packets as large as 2^16, the limit is 2^23 instead ๐
B.1.2. Integrity Limit ๐
okay so integrity is the other half of the story, bestie ๐ Theorem (4.3) in [GCM-MU] says an attacker's chance of successfully forging a packet is capped at roughly this:
(1 / 2^(8 * n)) + ((2 * v) / 2^(2 * n))
+ ((2 * o * v) / 2^(k + n)) + (n * (v + (v * l)) / 2^k)
The goal is to keep that advantage at 2^-57 or lower โจ For AEAD_AES_128_GCM, the fourth term is the main character ๐ It's so much bigger than the rest that you can drop the others without really changing the answer, which gives us this approximation:
v <= 2^64 / l
Now the practical bit ๐ Endpoints that don't try to remove protection from packets bigger than 2^11 bytes can attempt that on at most 2^57 packets. Endpoints that don't put any limit on the size of the packets they process can attempt it on at most 2^52 packets. Be careful with those numbers, bestie ๐
For AEAD_AES_256_GCM, that same term still dominates, but the bigger value of k gives us a much roomier approximation:
v <= 2^192 / l
That limit is seriously huge ๐ However, this document still recommends applying the same limit to both functions, because either limit is more than big enough to be acceptably large ๐ซถ
B.2. Analysis of AEAD_AES_128_CCM Usage Limits ๐ฆ
Here's the tea ๐ต [TLS13] and [AEBounds] don't specify usage limits for AEAD_AES_128_CCM. Still, any AEAD used with QUIC needs usage limits that keep both confidentiality and integrity intact, so this section writes down that analysis ๐
The analysis is based on [CCM-ANALYSIS], and its results are used to derive usage limits that line up with the ones chosen in [TLS13] โจ
For confidentiality, Theorem 2 in [CCM-ANALYSIS] says an attacker gains a distinguishing advantage over an ideal pseudorandom permutation (PRP) of no more than this:
(2l * q)^2 / 2^n
The integrity limit in Theorem 1 in [CCM-ANALYSIS] gives the attacker a strictly higher advantage for the same number of messages ๐ญ Since the confidentiality and integrity advantages are aiming at the same target, we only need to look at Theorem 1 ๐ง
Theorem 1 says an attacker gains an advantage over an ideal PRP of no more than this:
v / 2^t + (2l * (v + q))^2 / 2^n
Both "t" and "n" are 128 here, so the first term is negligible next to the second one. That means we can drop it without really changing the result ๐
This gives one relation that covers both encryption and decryption attempts, and it lands on the same limit as the confidentiality-only theorem. For a target advantage of 2^-57, it works out to:
v + q <= 2^34.5 / l
If we set "q = v", we get limits for both confidentiality and integrity ๐ฅน Endpoints that cap packets at 2^11 bytes end up with confidentiality and integrity limits of 2^26.5 packets each. Endpoints that don't restrict packet size get a limit of 2^21.5 โจ
Contributors ๐ธ
The IETF QUIC Working Group got an enormous amount of support from so many people ๐ซถ These folks made substantive contributions to this document:
- Adam Langley
- Alessandro Ghedini
- Christian Huitema
- Christopher Wood
- David Schinazi
- Dragana Damjanovic
- Eric Rescorla
- Felix Gรผnther
- Ian Swett
- Jana Iyengar
- ๅฅฅ ไธ็ฉ (Kazuho Oku)
- Marten Seemann
- Martin Duke
- Mike Bishop
- Mikkel Fahnรธe Jรธrgensen
- Nick Banks
- Nick Harper
- Roberto Peon
- Rui Paulo
- Ryan Hamilton
- Victor Vasiliev
Authors' Addresses ๐
Martin Thomson (editor) Mozilla
Email: mt@lowentropy.net
Sean Turner (editor) sn3rd
Email: sean@sn3rd.com
๐ฆ where this page comes from
- ietf: rfc9001 2021-05
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms