New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework Key Update #2237
Rework Key Update #2237
Changes from 2 commits
867af94
b4ddb7f
4d3f65a
4836c87
8c72c8d
a2ef722
d62829d
e9bb544
acff3b6
a2a860f
e378a88
1b63f75
fcc1d9e
940bac4
3fca7a5
8fd93b8
efeaf0d
5fb15c8
b3d8ff1
b56d3d9
5b075a8
331d3a7
1c9fe31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1101,40 +1101,58 @@ anticipation of receiving a ClientHello. | |
# Key Update | ||
|
||
Once the 1-RTT keys are established and the short header is in use, it is | ||
possible to update the keys used to protect packets. The Key Phase bits in the | ||
short header are used to indicate whether key updates have occurred. The | ||
Key Phase is initially set to 0 and then incremented with each key update. | ||
possible to update the keys used to protect packets. The Key Update field in the | ||
short header are used to indicate when key updates are permitted and when they | ||
have occurred. | ||
|
||
The Key Phase allows a recipient to detect a change in keying material | ||
The the low bit of the Key Update field (0x04) is the Key Phase bit. The Key | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "The the" |
||
Phase is used to indicate which packet protection keys are in use. The Key | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe better: "which packet protection key was used to protect this packet." |
||
Phase bit is initially set to 0 for the first set of 1-RTT packets. The Key | ||
Phase is toggled to signal each key update. | ||
|
||
The Key Phase bit allows a recipient to detect a change in keying material | ||
without needing to receive the first packet that triggered the change. An | ||
endpoint that notices a changed Key Phase bit can update keys and decrypt the | ||
packet that contains the changed bit. | ||
endpoint that notices a changed Key Phase updates keys and decrypts the packet | ||
that contains the changed value. | ||
|
||
|
||
The high bit of the Key Update field (0x08) is the Key Update Permitted bit. | ||
Endpoints set this value to 0 until they successfully process a packet with keys | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As written, this seems to imply that Endpoints must set the value to 1 when they successfully process a packet. I agree with Ian that there will be cases where this is set to 0 and never changed (e.g. when the expected flow size is short). Perhaps this would work instead: "The high bit of the Key Update field (0x08) is the Key Update Permitted bit. Endpoints may set this value to 0 for any reason, forbidding Key Updates. Endpoint MUST NOT set this value to 1 until they successfully process a packet the largest received packet number with keys from the same key phase as they are using. They MAY set it to 1 to indicate key update is permitted at any time after that." |
||
from the same key phase as they are using to send. An endpoint MUST NOT | ||
initiate a key update until it receives a packet with the Key Update Permitted | ||
bit set. | ||
|
||
Only packets that increase the largest received packet number are used to | ||
trigger key updates or changes in the Key Update Permitted bit. | ||
|
||
This mechanism replaces the TLS KeyUpdate message. Endpoints MUST NOT send a | ||
TLS KeyUpdate message. Endpoints MUST treat the receipt of a TLS KeyUpdate | ||
message as a connection error of type 0x10a, equivalent to a fatal TLS alert of | ||
unexpected_message (see {{tls-errors}}). | ||
|
||
This process ensures that once the handshake is complete, packets with the same | ||
Key Phase will have the same packet protection keys, unless there are multiple | ||
key updates in short succession and significant packet reordering. | ||
{{ex-key-update}} shows a key update process, with keys used identified with @M | ||
and @N. Values in brackets [] indicate the value of Key Update bits. | ||
|
||
~~~ | ||
Initiating Peer Responding Peer | ||
|
||
@M QUIC Frames | ||
New Keys -> @N | ||
@N QUIC Frames | ||
@M [10] QUIC Packets | ||
. Update to @N | ||
@N [01] QUIC Packets | ||
--------> | ||
QUIC Frames @M | ||
New Keys -> @N | ||
QUIC Frames @N | ||
QUIC Packets [10] @M | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this line here? The responding peer should update the keys immediately when receiving the first @n packet. |
||
Update to @N . | ||
QUIC Packets [11] @N | ||
<-------- | ||
. Key Update Permitted | ||
@N [11] QUIC Packets | ||
--------> | ||
Key Update Permitted . | ||
~~~ | ||
{: #ex-key-update title="Key Update"} | ||
|
||
|
||
## Initiating a Key Update | ||
## Initiating a Key Update {#key-update-initiate} | ||
|
||
Endpoints maintain separate read and write secrets for packet protection. An | ||
endpoint initiates a key update by updating its packet protection write secret | ||
|
@@ -1144,54 +1162,78 @@ uses the KDF function provided by TLS with a label of "quic ku". The | |
corresponding key and IV are created from that secret as defined in | ||
{{protection-keys}}. The header protection key is not updated. | ||
|
||
The endpoint uses the key and IV to protect all subsequent packets, and | ||
increments the value of the Key Phase bits modulo 4 in the short packet header | ||
to signal the change of keys. | ||
The endpoint uses the key and IV to protect all subsequent packets, clears the | ||
Key Update Permitted bit, and toggles the value of the low Key Phase bit. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sentence reads better if you turn it around and insert an "updated": "The endpoint clears the Key Update Permitted bit, and toggles the value of the low Key Phase bit, and uses the updated key and IV to protect all subsequent packets." |
||
|
||
An endpoint MUST NOT initiate more than one key update at a time. A new key | ||
cannot be used until the endpoint has received an indication that its peer is | ||
using the same key phase. | ||
cannot be used until the endpoint has successfully processed a packet with a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better: A subsequent key update can only be performed once the endpoint... |
||
matching Key Phase and the Key Update Permitted bit set. Together, these | ||
indicate that the key update was received and acted on. | ||
|
||
Once an endpoint has received and successfully processed packets with the same | ||
Key Phase value, this indicates that the peer has also updated keys. The | ||
endpoint can then set the Key Update Permitted bit to 1 on packets it | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer stronger language here. Ideally, a MUST (although we'd have to rearrange the text a bit to allow deferring this until the old keys are discarded). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I struggled with this too. I want people to enable key updates, but the fact is that we can't really enforce it. If we didn't want to allow peers to minimize the number of active keys, you could allow endpoints to punish them if they acknowledged a packet with a matching key phase in a packet that didn't include the Key Update Permitted bit. I will try to work in a SHOULD somehow. |
||
subsequently sends. An endpoint MUST NOT set Key Update Permitted to 1 on | ||
packets it sends unless it has successfully processed packets with a matching | ||
Key Phase. An endpoint MAY defer setting Key Update Permitted to 1 until it has | ||
discarded old keys, see {{key-update-old-keys}}. | ||
|
||
Using Key Update Permitted in this manner guarantees at least one round trip | ||
between updates, preventing multiple updates that could result in endpoints | ||
being unable to process any packets. | ||
|
||
An endpoint that receives a packet protected with old keys that includes an | ||
acknowledgement for a packet protected with newer keys MAY treat that as a | ||
connection error of type PROTOCOL_VIOLATION. | ||
connection error of type KEY_UDPATE_ERROR. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume that "processed a packet" means "received, successfully decrypted, and processed the content of the frames." But I have to guess. Encrypting and sending is a form of processing too. The term first occurs in section 8.1, and it is a bit puzzling there too. I would suggest adding a formal definition in "1.2. Terms and Definitions ". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A lot of that text is going to be specified again in the following paragraphs. Do we need a paraphrase here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initial to Handshake, and Handshake to 1-RTT. Are these special cases of Key Update? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a note about the handshake to clear that up. Logically, they are the same, but there is nothing to be gained by delaying the signal, or by requiring that the signal be used before a key change can be made. |
||
|
||
## Responding to a Key Update | ||
|
||
A receiving endpoint detects an update when the Key Phase is one greater than | ||
what it is expecting. The endpoint creates a new read secret and the | ||
corresponding read key and IV using the same process as its peer. | ||
|
||
A packet with a Key Phase other than the expected or next value MUST be | ||
discarded. However, endpoints MUST NOT generate a timing side-channel signal | ||
that might indicate that this specific field was invalid (see | ||
{{header-protect-analysis}}). | ||
An endpoint that sets Key Update Permitted to 1 on packets it sends is willing | ||
to accept key updates. If a packet is received with a Key Phase that differs | ||
from the value the endpoint expects, the endpoint creates a new packet | ||
protection secret for reading and the corresponding key and IV. The endpoint | ||
uses the same key derivation process as its peer uses to generate keys for | ||
receiving. | ||
|
||
If the packet can be decrypted and authenticated using the updated key and IV, | ||
then the keys the endpoint uses for packet protection (the write secret) are | ||
also updated using the same KDF and label as for the read secret. The next | ||
packet sent by the endpoint MUST use the new packet protection keys. | ||
If the packet protection is successfully removed using the updated key and IV, | ||
then the keys the endpoint initiates a key update in response, as described in | ||
{{key-update-initiate}}. However, as packets with a matching Key Phase have | ||
been received, the Key Update Permitted bit MAY be set to 1 on the next packet | ||
it sends. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't that a restatement of the lines 1167-1169? Why do we have the same text twice, with slightly different phrasing, "processed" vs. "packet protection is successfully removed" ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No difference intended. I've tried to even this out. |
||
An endpoint does not always need to send packets when it detects that its peer | ||
has updated keys. The next packet that it sends will simply use the new keys. | ||
If an endpoint detects a second update before it has sent any packets with | ||
updated keys, it indicates that its peer has updated keys twice without awaiting | ||
a reciprocal update. An endpoint MUST treat consecutive key updates as a fatal | ||
error and abort the connection. | ||
a reciprocal update. An endpoint MUST treat consecutive key updates as a | ||
connection error of type KEY_UDPATE_ERROR. | ||
|
||
|
||
## Using Old Keys {#key-update-old-keys} | ||
|
||
If the most recent packet sent by the endpoint contained a Key Update Permitted | ||
bit set to 0, a Key Phase other than the value expected indicates that the | ||
packet was protected with old keys. If those old keys are available, then they | ||
can be used to remove packet protection. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "key updates that use the Key Phase bit". That's a weird way to put it. Maybe a reference to the ladder diagram in the transport spec? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe "after the handshake is complete," assuming that's now a defined term? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tried very hard to separate key update as a concept from the key changes that happen during the handshake. So I'll remove the 'that use the Key Phase bit' part. |
||
Old keys might be used to remove protection from packets that were are reordered | ||
in the network. However, it is never valid for old keys to be used to protect | ||
packets with packets that have higher packet numbers than packets that were | ||
protected with newer keys. An endpoint that successfully removes protection | ||
with old keys when newer keys were used for packets with lower packet numbers | ||
MUST treat this as a connection error of type KEY_UPDATE_ERROR. | ||
|
||
An endpoint SHOULD retain old read keys for a period of no more than three times | ||
the Probe Timeout (PTO, see {{QUIC-RECOVERY}}). After this period, old read | ||
keys and their corresponding secrets SHOULD be discarded. Retaining keys allows | ||
endpoints to process packets that were sent with old keys and delayed in the | ||
network. Packets with higher packet numbers always use the updated keys and | ||
MUST NOT be decrypted with old keys. | ||
keys and their corresponding secrets SHOULD be discarded. An endpoint MAY keep | ||
the Key Update Permitted bit set to 0 until it discards old read keys to limit | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about explicitly stating "until it discards the 0-RTT key or the previous 1-RTT read key" here, to be clear that we can have the Key Update Permitted bit set to zero until the 0-RTT key is discarded when 0-RTT is used. |
||
the number of keys it maintains. | ||
|
||
A packet that triggers a key update could arrive after successfully processing a | ||
packet with a higher packet number. This is only possible if there is a key | ||
compromise and an attack, or if the peer is incorrectly reverting to use of old | ||
keys. Because the latter cannot be differentiated from an attack, an endpoint | ||
MUST immediately terminate the connection if it detects this condition. | ||
Endpoints MUST NOT generate a timing side-channel signal that might indicate | ||
that the Key Update field was invalid (see {{header-protect-analysis}}). | ||
Endpoints can use dummy packet protection keys in place of discarded keys when | ||
key updates are not permitted. | ||
|
||
|
||
## Key Update Frequency | ||
|
@@ -1200,6 +1242,12 @@ In deciding when to update keys, endpoints MUST NOT exceed the limits for use of | |
specific keys, as described in Section 5.5 of {{!TLS13}}. | ||
|
||
|
||
## Key Update Error Code {#key-update-error} | ||
|
||
The KEY_UPDATE_ERROR error code (0xD) is used to signal errors related to key | ||
updates. | ||
|
||
|
||
# Security of Initial Messages | ||
|
||
Initial packets are not protected with a secret key, so they are subject to | ||
|
@@ -1425,6 +1473,9 @@ values in the following registries: | |
Recommended column is to be marked Yes. The TLS 1.3 Column is to include CH | ||
and EE. | ||
|
||
* QUIC Error Codes Registry {{QUIC-TRANSPORT}} - IANA is to register the | ||
KEY_UPDATE_ERROR (0xD), as described in {{key-update-error}}. | ||
|
||
|
||
--- back | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3471,65 +3471,18 @@ See {{QUIC-INVARIANTS}} for details on how packets from different versions of | |
QUIC are interpreted. | ||
|
||
The interpretation of the fields and the payload are specific to a version and | ||
packet type. Type-specific semantics for this version are described in the | ||
following sections. | ||
|
||
The end of the packet is determined by the Length field. The Length field | ||
covers both the Packet Number and Payload fields, both of which are | ||
confidentiality protected and initially of unknown length. The length of the | ||
Payload field is learned once header protection is removed. The Length field | ||
enables packet coalescing ({{packet-coalesce}}). | ||
|
||
|
||
## Short Header Packet {#short-header} | ||
|
||
~~~~~ | ||
0 1 2 3 | ||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
+-+-+-+-+-+-+-+-+ | ||
|0|1|S|R| K | P | | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| Destination Connection ID (0..144) ... | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| Packet Number (8/16/24/32) ... | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
| Protected Payload (*) ... | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
~~~~~ | ||
{: #fig-short-header title="Short Header Packet Format"} | ||
|
||
The short header can be used after the version and 1-RTT keys are negotiated. | ||
Packets that use the short header contain the following fields: | ||
|
||
Header Form: | ||
|
||
: The most significant bit (0x80) of byte 0 is set to 0 for the short header. | ||
|
||
Fixed Bit: | ||
|
||
: The next bit (0x40) of byte 0 is set to 1. Packets containing a zero value | ||
for this bit are not valid packets in this version and MUST be discarded. | ||
|
||
Spin Bit (S): | ||
|
||
: The sixth bit (0x20) of byte 0 is the Latency Spin Bit, set as described in | ||
{{!SPIN=I-D.ietf-quic-spin-exp}}. | ||
packet type. While type-specific semantics for this version are described in | ||
the following sections, several long-header packets in this version of QUIC | ||
contain these additional fields: | ||
|
||
Reserved Bit (R): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's still two bits. |
||
|
||
: The next bit (0x10) of byte 0 is reserved. This bit is protected using header | ||
protection (see Section 5.4 of {{QUIC-TLS}}). The value included prior to | ||
protection MUST be set to 0. An endpoint MUST treat receipt of a packet that | ||
has a non-zero value for this bit after removing protection as a connection | ||
error of type PROTOCOL_VIOLATION. | ||
|
||
Key Phase (K): | ||
|
||
: The next two bits (those with a mask of 0x0c) of byte 0 indicate the key | ||
phase, which allows a recipient of a packet to identify the packet protection | ||
keys that are used to protect the packet. See {{QUIC-TLS}} for details. | ||
These bits are protected using header protection (see Section 5.4 of | ||
{{QUIC-TLS}}). | ||
: Two bits (those with a mask of 0x0c) of byte 0 are reserved across multiple | ||
packet types. These bits are protected using header protection (see Section | ||
5.4 of {{QUIC-TLS}}). The value included prior to protection MUST be set to 0. | ||
An endpoint MUST treat receipt of a packet that has a non-zero value for these | ||
bits after removing protection as a connection error of type | ||
PROTOCOL_VIOLATION. | ||
|
||
Packet Number Length (P): | ||
|
||
|
@@ -3945,7 +3898,7 @@ short packet header. | |
0 1 2 3 | ||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
+-+-+-+-+-+-+-+-+ | ||
|0|1|S|R|R|K|P P| | ||
|0|1|S|R|K K|P P| | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unrelated change. |
||
| Destination Connection ID (0..144) ... | ||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
|
@@ -3973,21 +3926,20 @@ Spin Bit (S): | |
: The sixth bit (0x20) of byte 0 is the Latency Spin Bit, set as described in | ||
{{!SPIN=I-D.ietf-quic-spin-exp}}. | ||
|
||
Reserved Bits (R): | ||
Reserved Bit (R): | ||
|
||
: The next two bits (those with a mask of 0x18) of byte 0 are reserved. These | ||
bits are protected using header protection (see Section 5.4 of | ||
{{QUIC-TLS}}). The value included prior to protection MUST be set to 0. An | ||
endpoint MUST treat receipt of a packet that has a non-zero value for these | ||
bits after removing protection as a connection error of type | ||
PROTOCOL_VIOLATION. | ||
: The next bit (0x10) of byte 0 is reserved. This bit is protected using header | ||
protection (see Section 5.4 of {{QUIC-TLS}}). The value included prior to | ||
protection MUST be set to 0. An endpoint MUST treat receipt of a packet that | ||
has a non-zero value for these bits after removing protection as a connection | ||
error of type PROTOCOL_VIOLATION. | ||
|
||
Key Phase (K): | ||
Key Update (K): | ||
|
||
: The next bit (0x04) of byte 0 indicates the key phase, which allows a | ||
recipient of a packet to identify the packet protection keys that are used to | ||
protect the packet. See {{QUIC-TLS}} for details. This bit is protected | ||
using header protection (see Section 5.4 of {{QUIC-TLS}}). | ||
: The next two bits (0x0c) of byte 0 are used to control key updates. These | ||
bits are used by the recipient of a packet to identify the keys that are used | ||
to protect the packet. See {{QUIC-TLS}} for details. These bits are | ||
protected using header protection (see Section 5.4 of {{QUIC-TLS}}). | ||
|
||
Packet Number Length (P): | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/are/is/