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 3 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 | ||||
---|---|---|---|---|---|---|
|
@@ -864,7 +864,7 @@ the Packet Number field. The four least-significant bits of the first byte are | |||||
protected for packets with long headers; the five least significant bits of the | ||||||
first byte are protected for packets with short headers. For both header forms, | ||||||
this covers the reserved bits and the Packet Number Length field; the Key Update | ||||||
bits are also protected for packets with a short header. | ||||||
bit is also protected for packets with a short header. | ||||||
|
||||||
The same header protection key is used for the duration of the connection, with | ||||||
the value not changing after a key update (see {{key-update}}). This allows | ||||||
|
@@ -1100,55 +1100,45 @@ anticipation of receiving a ClientHello. | |||||
|
||||||
# Key Update {#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 Update field in the | ||||||
short header is used to indicate when key updates are permitted and when they | ||||||
have occurred. | ||||||
Once the 1-RTT keys are established and confirmed through the use of the | ||||||
KEYS_READY frame, it is possible to update the keys used to protect packets. | ||||||
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. To me, the name KEYS_READY seems to indicate that the keys are about to be used, not that they are in use now. I think I'd prefer something like KEYS_ACTIVE or KEY_IN_USE. Just a personal preference though. |
||||||
|
||||||
The low bit of the Key Update field (0x04) is the Key Phase bit. The key phase | ||||||
is used to indicate which packet protection keys are used to protect the packet. | ||||||
The Key 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 is used to indicate which packet protection keys are used to | ||||||
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 am confused. Line 866 mentions a Key Update bit, and here we see a Key Phase bit. Is that the same bit, or do we now have 2 bits? 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.
Suggested change
|
||||||
protect the packet. The Key Phase bit is initially set to 0 for the first set | ||||||
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.
Suggested change
|
||||||
of 1-RTT packets. The Key Phase bit is toggled to signal each 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.
Suggested change
|
||||||
|
||||||
The key phase 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 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. An | ||||||
endpoint MUST NOT initiate a key update unless they have received a packet with | ||||||
the current Key Phase and the Key Update Permitted bit set to 1. An endpoint | ||||||
MAY keep this value set to 0, forbidding key updates. An endpoint MUST NOT set | ||||||
this value to 1 until it successfully processes a packet with keys from the same | ||||||
key phase. Once this bit is set it MUST NOT be cleared for packets in the same | ||||||
key phase. | ||||||
|
||||||
Only packets that increase the largest received packet number are used to | ||||||
trigger key updates or changes in the Key Update Permitted bit. | ||||||
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 updates keys and decrypts the | ||||||
packet that contains the changed value. | ||||||
|
||||||
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}}). | ||||||
|
||||||
{{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. | ||||||
and @N. Values in brackets \[] indicate the value of Key Phase bit. | ||||||
|
||||||
~~~ | ||||||
Initiating Peer Responding Peer | ||||||
|
||||||
@M [10] QUIC Packets | ||||||
@M [0] QUIC Packets | ||||||
KEYS_READY | ||||||
--------> | ||||||
QUIC Packets [10] @M | ||||||
QUIC Packets [0] @M | ||||||
KEYS_READY | ||||||
<-------- | ||||||
... Update to @N | ||||||
@N [01] QUIC Packets | ||||||
@N [1] QUIC Packets | ||||||
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. 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. During a key update, the initiator doesn't update until it receives a packet in the new keys. If we sent the frame in the first packet we'd get the simultaneous update problem. That makes me a little more receptive to the idea that @kazuho suggests on the mailing list, but not much more. Having special rules for Handshake isn't a huge burden. p.s., I'm sure that github users 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. Poor Matt Mullenweg.... 😁 |
||||||
--------> | ||||||
Update to @N ... | ||||||
QUIC Packets [11] @N | ||||||
QUIC Packets [1] @N | ||||||
KEYS_READY | ||||||
<-------- | ||||||
@N [1] QUIC Packets | ||||||
KEYS_READY | ||||||
... Key Update Permitted | ||||||
@N [11] QUIC Packets | ||||||
--------> | ||||||
Key Update Permitted ... | ||||||
~~~ | ||||||
|
@@ -1165,87 +1155,75 @@ 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 clears the Key Update Permitted bit, and toggles the value of the | ||||||
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 subsequent | ||||||
key update can only be performed after the endpoint has successfully processed a | ||||||
packet with a matching key phase and the Key Update Permitted bit set. | ||||||
Together, these indicate that the key update was received and acted on. | ||||||
The endpoint toggles the value of the Key Phase bit, and uses the updated 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.
Suggested change
|
||||||
and IV to protect all subsequent packets. | ||||||
|
||||||
Once an endpoint has received and successfully processed packets with the same | ||||||
key phase, this indicates that the peer has also updated keys. The | ||||||
endpoint can then set the Key Update Permitted bit to 1 on packets it | ||||||
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}}. | ||||||
An endpoint MUST NOT initiate a key update prior to having received a KEYS_READY | ||||||
frame in a packet from the current key phase. A subsequent key update can only | ||||||
be performed after the endpoint has successfully processed a KEYS_READY frame | ||||||
from a packet with a matching key phase. This ensures that keys are available | ||||||
to both peers before another can be initiated. | ||||||
|
||||||
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 KEY_UDPATE_ERROR. | ||||||
Once an endpoint has successfully processed a packet with the same key phase, 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. "with the same key phase" as what? |
||||||
can send a KEYS_READY frame. Endpoints MAY defer sending a KEYS_READY frame | ||||||
after a key update (see {{key-update-old-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. 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 | ||||||
|
||||||
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. | ||||||
An endpoint that sends a KEYS_READY frame can accept further key updates. A key | ||||||
update can happen even without seeing a KEYS_READY frame from the peer. If a | ||||||
packet is received with a key phase that differs from the value the endpoint | ||||||
used to protect the packet containing its last KEYS_READY frame, the endpoint | ||||||
creates a new packet protection secret for reading and the corresponding key and | ||||||
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. Is this always the case? If I was using 0, and now start using 1 and send KEYS_ACTIVE with 1, I will continue to receive packets with key phase 0. That doesn't mean I need to create a new secret, since I haven't discarded the previous secret yet, right? |
||||||
IV. An endpoint uses the same key derivation process as its peer uses to | ||||||
generate keys for receiving. | ||||||
|
||||||
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 can be set to 1 on the next packet | ||||||
it sends. | ||||||
{{key-update-initiate}}. An endpoint that responds to a key update MUST send a | ||||||
KEYS_READY frame to indicate that it is both sending and receiving with updated | ||||||
keys, though it MAY defer sending the frame (see {{key-update-old-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. 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 | ||||||
connection error of type KEY_UDPATE_ERROR. | ||||||
has updated keys. The next packet that it sends use the new keys and include | ||||||
the KEYS_READY frame. If an endpoint detects a second update before it has sent | ||||||
any packets with updated keys or a KEYS_READY frame, it indicates that its peer | ||||||
has updated keys twice without awaiting a reciprocal update. An endpoint MUST | ||||||
treat consecutive key updates as a connection error of type KEY_UPDATE_ERROR. | ||||||
|
||||||
Endpoints responding to an apparent key update MUST NOT generate a timing | ||||||
side-channel signal that might indicate that the Key Phase bit was invalid (see | ||||||
{{header-protect-analysis}}). Endpoints can use dummy packet protection keys in | ||||||
place of discarded keys when key updates are not permitted. | ||||||
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. If you're going to mention this last, it's probably worth expanding on this a bit. What behavior might generate such a side-channel? How are dummy keys used to emulate normal behavior in those cases? 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. Text added. |
||||||
|
||||||
|
||||||
## 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. | ||||||
During a key update, packets protected with older keys might arrive if they were | ||||||
delayed by the network. If those old keys are available, then they can be used | ||||||
to remove packet protection. | ||||||
|
||||||
After a key update, an endpoint MAY delay sending the KEYS_READY frame by up to | ||||||
three times the Probe Timeout (PTO, see {{QUIC-RECOVERY}}) to minimize the | ||||||
number of active keys it maintains. During this time, an endpoint can use old | ||||||
keys to process delayed packets rather than enabling a new key update. This | ||||||
only applies to key updates that use the Key Phase bit; endpoints MUST NOT defer | ||||||
sending of KEYS_READY during and immediately after the handshake. | ||||||
|
||||||
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. | ||||||
Even if old keys are available, those keys MUST NOT be used to protect packets | ||||||
with packets that have higher packet numbers than packets that were protected | ||||||
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.
Suggested change
|
||||||
with newer keys. An endpoint that successfully removes protection with old 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. Would it make sense to mandate dropping write keys immediately, but permit retaining read 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. I don't want to mandate dropping keys so directly, but I will mention that it is possible. Note that there are cases during the handshake where write keys for different packet number spaces need to be kept. So this would be limited to 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. Why? It seems appropriate to close the connection here. |
||||||
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. | ||||||
|
||||||
An endpoint MAY keep the Key Update Permitted bit set to 0 until it discards old | ||||||
read keys to limit the number of keys it maintains. An endpoint MAY also | ||||||
prevent key update until it discards keys from the handshake, including any | ||||||
0-RTT keys. An endpoint SHOULD set the Key Update Permitted bit when possible. | ||||||
|
||||||
Once set, the Key Update Permitted bit MUST NOT be cleared for packets with the | ||||||
same key phase. An endpoint MAY treat receipt of a packet with the Key Update | ||||||
Permitted bit cleared as a connection error of type KEY_UPDATE_ERROR if the bit | ||||||
was previously set on packets protected with the same keys. | ||||||
|
||||||
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. | ||||||
the current PTO. After this period, old read keys and their corresponding | ||||||
secrets SHOULD be discarded. | ||||||
|
||||||
An endpoint that receives a packet protected with old keys that includes an | ||||||
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. Suggestion: "An endpoint that receives an acknowledgement for a packet protected with new keys in a packet protected with older keys MAY treat that as a ..." |
||||||
acknowledgement for a packet protected with newer keys MAY treat that as a | ||||||
connection error of type KEY_UPDATE_ERROR. | ||||||
|
||||||
|
||||||
## Key Update Frequency | ||||||
|
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.
See comments in Key Update section. I would vote for keeping the Key Phase name.