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 all 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 |
---|---|---|
|
@@ -1097,78 +1097,169 @@ TLS ClientHello. The server MAY retain these packets for later decryption in | |
anticipation of receiving a ClientHello. | ||
|
||
|
||
# Key Update | ||
# 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. The KEY_PHASE bit in the short header is used to | ||
indicate whether key updates have occurred. The KEY_PHASE bit is initially set | ||
to 0 and then inverted with each key update. | ||
Once the 1-RTT keys are established and confirmed through the use of the | ||
KEYS_ACTIVE frame, it is possible to update the keys used to protect packets. | ||
|
||
The KEY_PHASE bit allows a recipient to detect a change in keying material | ||
without necessarily 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. | ||
The Key Phase bit indicates 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 and toggled to signal each subsequent 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 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}}). | ||
|
||
An endpoint MUST NOT initiate more than one key update at a time. A new key | ||
cannot be used until the endpoint has received and successfully decrypted a | ||
packet with a matching KEY_PHASE. | ||
|
||
A receiving endpoint detects an update when the KEY_PHASE bit does not match | ||
what it is expecting. It creates a new secret (see Section 7.2 of {{!TLS13}}) | ||
and the corresponding read key and IV using the KDF function provided by TLS. | ||
The header protection key is not updated. | ||
|
||
If the packet can be decrypted and authenticated using the updated key and IV, | ||
then the keys the endpoint uses for packet protection are also updated. The | ||
next packet sent by the endpoint will then use the new keys. | ||
|
||
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. | ||
|
||
An endpoint SHOULD retain old keys for a period of no more than three times the | ||
Probe Timeout (PTO, see {{QUIC-RECOVERY}}). After this period, old keys and | ||
their corresponding secrets SHOULD be discarded. Retaining keys allow 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. | ||
|
||
This 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 a short time frame 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 Phase bit. | ||
|
||
~~~ | ||
Initiating Peer Responding Peer | ||
|
||
@M QUIC Frames | ||
New Keys -> @N | ||
@N QUIC Frames | ||
@M [0] QUIC Packets | ||
KEYS_ACTIVE | ||
--------> | ||
QUIC Frames @M | ||
New Keys -> @N | ||
QUIC Frames @N | ||
QUIC Packets [0] @M | ||
KEYS_ACTIVE | ||
<-------- | ||
... Update to @N | ||
@N [1] QUIC Packets | ||
--------> | ||
Update to @N ... | ||
QUIC Packets [1] @N | ||
KEYS_ACTIVE | ||
<-------- | ||
@N [1] QUIC Packets | ||
KEYS_ACTIVE | ||
... Key Update Permitted | ||
--------> | ||
Key Update Permitted ... | ||
~~~ | ||
{: #ex-key-update title="Key Update"} | ||
|
||
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. | ||
|
||
## 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 | ||
and using that to protect new packets. The endpoint creates a new write secret | ||
from the existing write secret as performed in Section 7.2 of {{!TLS13}}. This | ||
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 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 a key update prior to having received and | ||
successfully processed a KEYS_ACTIVE frame contained in a packet from the | ||
current key phase. This ensures that keys are available to both peers before | ||
another can be initiated. | ||
|
||
Note: | ||
|
||
: Changes in keys from Initial to Handshake and from Handshake to 1-RTT don't | ||
use this key update process. Key changes during the handshake do not need to | ||
wait for a KEYS_ACTIVE frame, they are driven solely by changes in the TLS | ||
handshake. The KEYS_ACTIVE frame is used to allow Initial and Handshake keys | ||
to be discarded when they are no longer needed and - in the case of the first | ||
1-RTT key phase - to enable the first key update. | ||
|
||
The endpoint that initiates a key update also updates the keys that it uses for | ||
receiving packets. These keys will be needed to process packets the peer sends | ||
after updating. An endpoint needs to retain old keys so that packets sent by | ||
the peer prior to receiving the key update can be processed. Once an endpoint | ||
has successfully processed a packet using the new keys, it MUST send a | ||
KEYS_ACTIVE frame, though endpoints 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. 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 sends a KEYS_ACTIVE frame can accept further key updates. A | ||
key update can happen even without seeing a KEYS_ACTIVE frame from the peer. If | ||
a packet is received with a key phase that differs from the value the endpoint | ||
used to protect the last packet it sent, the endpoint creates a new packet | ||
protection secret for reading and the corresponding key and IV. An endpoint | ||
uses the same key derivation process as its peer uses to generate keys for | ||
receiving. | ||
|
||
If the packet is successfully processed using the updated key and IV, then the | ||
keys the endpoint initiates a key update in response, as described in | ||
{{key-update-initiate}}. An endpoint that responds to a key update MUST send a | ||
KEYS_ACTIVE 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 use the new keys and include | ||
the KEYS_ACTIVE frame. If an endpoint detects a second update before it has | ||
sent any packets with updated keys or a KEYS_ACTIVE 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; using dummy 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. If this were to happen, wouldn't the connection be closed with a KEY_UPDATE_ERROR? Is there much value in the timing signal at this point? 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. Because packets can be injected. |
||
will generate no variation in the timing signal produced by attempting to remove | ||
packet protection, but all packets with an invalid Key Phase bit will be | ||
rejected. | ||
|
||
An endpoint might not receive an acknowledgment for the packet that contains a | ||
KEYS_ACTIVE before receiving another key update. | ||
|
||
|
||
## Using Old Keys {#key-update-old-keys} | ||
|
||
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. | ||
|
||
An endpoint always sends packets that are protected with the newest keys. Keys | ||
used for protecting packets that an endpoint sends can be discarded immediately | ||
after newer keys are available. | ||
|
||
After a key update, an endpoint MAY delay sending the KEYS_ACTIVE 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; endpoints MUST NOT defer sending of KEYS_ACTIVE | ||
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. |
||
Even if old keys are available, those keys MUST NOT be used to protect 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 current PTO. After this period, old read keys and their corresponding | ||
secrets SHOULD be discarded. | ||
|
||
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 connection error | ||
of type KEY_UPDATE_ERROR. | ||
|
||
|
||
## Key Update Frequency | ||
|
||
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 | ||
|
@@ -1394,15 +1485,15 @@ authenticated using packet protection; the entire packet header is part of the | |
authenticated additional data. Protected fields that are falsified or modified | ||
can only be detected once the packet protection is removed. | ||
|
||
An attacker could guess values for packet numbers and have an endpoint confirm | ||
guesses through timing side channels. Similarly, guesses for the packet number | ||
length can be trialed and exposed. If the recipient of a packet discards | ||
packets with duplicate packet numbers without attempting to remove packet | ||
protection they could reveal through timing side-channels that the packet number | ||
matches a received packet. For authentication to be free from side-channels, | ||
the entire process of header protection removal, packet number recovery, and | ||
packet protection removal MUST be applied together without timing and other | ||
side-channels. | ||
An attacker could guess values for packet numbers or key phase and have an | ||
endpoint confirm guesses through timing side channels. Similarly, guesses for | ||
the packet number length can be trialed and exposed. If the recipient of a | ||
packet discards packets with duplicate packet numbers without attempting to | ||
remove packet protection they could reveal through timing side-channels that the | ||
packet number matches a received packet. For authentication to be free from | ||
side-channels, the entire process of header protection removal, packet number | ||
recovery, and packet protection removal MUST be applied together without timing | ||
and other side-channels. | ||
|
||
For the sending of packets, construction and protection of packet payloads and | ||
packet numbers MUST be free from side-channels that would reveal the packet | ||
|
@@ -1441,6 +1532,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 | ||
|
||
|
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.
This is a bit confusing. The KEYS_ACTIVE sent above are for @m, right? If so, shouldn't the endpoint send a KEYS_ACTIVE for @n as soon as it updates to the new keys (on line 1133)?
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.
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
@M
and@N
get pinged all the time.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.
Poor Matt Mullenweg.... 😁