QUIC:修订间差异
无编辑摘要 |
|||
(未显示同一用户的21个中间版本) | |||
第14行: | 第14行: | ||
* Transport extensibility | * Transport extensibility | ||
* Optional unreliable delivery | * Optional unreliable delivery | ||
= Overview = | |||
== Reliablity == | |||
While UDP is not a reliable transport, QUIC adds a layer on top of UDP that introduces reliability. It offers re-transmissions of packets, congestion control, pacing and the other features otherwise present in TCP. | |||
Data sent over QUIC from one end-point will appear in the other end sooner or later, as long as the connection is maintained. | |||
By doing the fragmentation, retransmission and acknowledgements in QUIC.<ref>https://www.quora.com/How-does-QUIC-ensure-reliability-if-it-uses-UDP</ref> | |||
=== Streams === | |||
Similar to SCTP, SSH and HTTP/2, QUIC features separate logical streams within the physical connections. A number of parallel streams that can transfer data simultaneously over a single connection without affecting the other streams. | |||
A connection is a negotiated setup between two end-points similar to how a TCP connection works. A QUIC connection is made to a UDP port and IP address, but once established the connection is associated by its "connection ID". | |||
Over an established connection, either side can create streams and send data to the other end. '''Streams are delivered in-order and they are reliable, but different streams may be delivered out-of-order.''' | |||
[[Image:Quic-stream-order.webp|600px]] | |||
QUIC offers flow control on both connection and streams. | |||
==Fast handshakes == | |||
QUIC offers both 0-RTT and 1-RTT connection setups, meaning that at best QUIC needs no extra round-trips at all when setting up a new connection. The faster of those two, the 0-RTT handshake, only works if there has been a previous connection established to a host and a secret from that connection has been cached. | |||
QUIC allows a client to include data already in the 0-RTT handshake. This feature allows a client to deliver data to the peer as fast as it possibly can, and that then of course allows the server to respond and send data back even sooner. | |||
== Encryption == | |||
The transport security used in QUIC is using TLS 1.3 (RFC 8446) and there are never any unencrypted QUIC connections. | |||
=Algorithm= | |||
== UDP Datagram 1 - Client hello == | |||
=== Client key change generation=== | |||
The connection begins with the client generating a private/public keypair for key exchange. Key exchange is a technique where two parties can agree on the same number without an eavesdropper being able to tell what the number is. | |||
The private key is chosen by selecting an integer between 0 and 2^256-1. The client does this by generating 32 bytes (256 bits) of random data. The private key selected is<ref>https://quic.xargs.org/#client-key-exchange-generation</ref>: | |||
<syntaxhighlight lang="bash"> | |||
202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f | |||
</syntaxhighlight> | |||
The public key calculated is: | |||
<syntaxhighlight lang="bash"> | |||
358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254 | |||
</syntaxhighlight> | |||
===Client initialial key calculation === | |||
Next, the client continues to prepare for the connection by generating the encryption keys for the Initial packets. Because key exchange between client and server has not taken place there is limited security in these keys - any observer can derive the keys and read the traffic like the server will. Encrypting the Initial packets prevents certain kinds of attacks such as request forgery attacks. | |||
The client begins by generating 8 bytes of random data, in this case the bytes: | |||
<syntaxhighlight lang="bash"> | |||
0001020304050607 | |||
</syntaxhighlight> | |||
The client then derives encryption keys using the following process: | |||
<syntaxhighlight lang="bash"> | |||
initial_salt = 38762cf7f55934b34d179ae6a4c80cadccbb7f0a | |||
initial_random = (random bytes from client given above) | |||
initial_secret = HKDF-Extract(salt: initial_salt, key: initial_random) | |||
client_secret = HKDF-Expand-Label(key: initial_secret, label: "client in", ctx: "", len: 32) | |||
server_secret = HKDF-Expand-Label(key: initial_secret, label: "server in", ctx: "", len: 32) | |||
client_key = HKDF-Expand-Label(key: client_secret, label: "quic key", ctx: "", len: 16) | |||
server_key = HKDF-Expand-Label(key: server_secret, label: "quic key", ctx: "", len: 16) | |||
client_iv = HKDF-Expand-Label(key: client_secret, label: "quic iv", ctx: "", len: 12) | |||
server_iv = HKDF-Expand-Label(key: server_secret, label: "quic iv", ctx: "", len: 12) | |||
client_hp_key = HKDF-Expand-Label(key: client_secret, label: "quic hp", ctx: "", len: 16) | |||
server_hp_key = HKDF-Expand-Label(key: server_secret, label: "quic hp", ctx: "", len: 16) | |||
</syntaxhighlight> | |||
The use of the magic constant "<syntaxhighlight lang="bash" inline>38762cf7f55934b34d179ae6a4c80cadccbb7f0a</syntaxhighlight>" as the initial salt is interesting, as it is not derived from mathematical constants or cryptographic principles. It's the value of the first SHA-1 collision, co-discovered by Google researchers (QUIC itself was initially created, sponsored, and deployed by Google). | |||
This has introduced two new cryptographic concepts from TLS 1.3: | |||
* HKDF-Extract - given a salt and some bytes of key material create 256 bits (32 bytes) of new key material, with the input key material's entropy evenly distributed in the output. | |||
* HKDF-Expand-Label - given the inputs of key material, label, and context data, create a new key of the requested length. | |||
<syntaxhighlight lang="bash"> | |||
$ init_salt=38762cf7f55934b34d179ae6a4c80cadccbb7f0a | |||
$ init_dcid=0001020304050607 | |||
$ init_secret=$(./hkdf extract $init_salt $init_dcid) | |||
$ csecret=$(./hkdf expandlabel $init_secret "client in" "" 32) | |||
$ ssecret=$(./hkdf expandlabel $init_secret "server in" "" 32) | |||
$ client_init_key=$(./hkdf expandlabel $csecret "quic key" "" 16) | |||
$ server_init_key=$(./hkdf expandlabel $ssecret "quic key" "" 16) | |||
$ client_init_iv=$(./hkdf expandlabel $csecret "quic iv" "" 12) | |||
$ server_init_iv=$(./hkdf expandlabel $ssecret "quic iv" "" 12) | |||
$ client_init_hp=$(./hkdf expandlabel $csecret "quic hp" "" 16) | |||
$ server_init_hp=$(./hkdf expandlabel $ssecret "quic hp" "" 16) | |||
$ echo ckey: $client_init_key | |||
$ echo civ: $client_init_iv | |||
$ echo chp: $client_init_hp | |||
$ echo skey: $server_init_key | |||
$ echo siv: $server_init_iv | |||
$ echo shp: $server_init_hp | |||
ckey: b14b918124fda5c8d79847602fa3520b | |||
civ: ddbc15dea80925a55686a7df | |||
chp: 6df4e9d737cdf714711d7c617ee82981 | |||
skey: d77fc4056fcfa32bd1302469ee6ebf90 | |||
siv: fcb748e37ff79860faa07477 | |||
shp: 440b2725e91dc79b370711ef792faa3d | |||
</syntaxhighlight> | |||
From this we get the following encryption keys and IVs: | |||
<syntaxhighlight lang="bash"> | |||
client initial key: b14b918124fda5c8d79847602fa3520b | |||
client initial IV: ddbc15dea80925a55686a7df | |||
server initial key: d77fc4056fcfa32bd1302469ee6ebf90 | |||
server initial IV: fcb748e37ff79860faa07477 | |||
</syntaxhighlight> | |||
We also get the following "header protection keys", which will be explained below: | |||
client initial header protection key: 6df4e9d737cdf714711d7c617ee82981 | |||
server initial header protection key: 440b2725e91dc79b370711ef792faa3d | |||
At this point there is still no data sent over the network. | |||
The session begins with the client sending an "Initial" packet. This packet contains the "ClientHello" TLS record, used to begin the TLS 1.3 encrypted session. | |||
[[Image:client hello.webp|600px]] | |||
[[Image:client hello frame.png|600px]] | |||
=== Packet header === | |||
The packet begins with a header byte, which has header protection applied. Header protection is used to hide packet numbers and other information from outside observers. | |||
Header protection is applied by encrypting a sample of each packet's payload with the "header protection key", then XOR'ing certain bits and bytes in each packet with the resulting data. For "long" format packets such as this one, the protected sections are the lower 4 bits of this byte, and the bytes of the Packet Number (seen later). | |||
An example of how to compute header protection: | |||
<syntaxhighlight lang="bash"> | |||
### "client header protection key" from calc step above | |||
$ key=6df4e9d737cdf714711d7c617ee82981 | |||
### sample is taken from 16 bytes of payload starting | |||
### 4 bytes past the first byte of the packet number | |||
$ sample=ed78716be9711ba498b7ed868443bb2e | |||
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p | |||
ed9895bb15 | |||
### first byte of result is xor'd into lower 4 bits of this byte, | |||
### remaining bytes are xor'd one-for-one into the bytes of | |||
### the packet number (which in this packet is only one byte) | |||
</syntaxhighlight> | |||
=== dcid === | |||
The client has not yet received a connection ID chosen by the server. Instead it uses this field to provide the 8 bytes of random data for deriving Initial encryption keys. | |||
=== scid === | |||
The client uses this field to indicate its chosen connection ID to the server. | |||
=== Token === | |||
The client can use this field in some scenarios to provide a token requested by the server, such as to prove that its connection attempt is not spoofed. In this case, there is no token to provide, and the field is empty. | |||
=== Packet length === | |||
The client indicates how many bytes of encrypted payload are in the packet. This field is a variable length integer - the first two bits of the first byte indicate how many total bytes are in the integer. | |||
=== Packet number === | |||
This byte has header protection applied. | |||
=== Encrypted data === | |||
This data is encrypted with the client "Initial" traffic key. | |||
== UDP Datagram 2 - Server hello and handshake == | |||
=== Server key exchange generation === | |||
The private key: | |||
<syntaxhighlight lang="bash"> | |||
909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf | |||
</syntaxhighlight> | |||
The public key calculated is: | |||
<syntaxhighlight lang="bash"> | |||
9fd7ad6dcff4298dd3f96d5b1b2af910a0535b1488d7f8fabb349a982880b615 | |||
</syntaxhighlight> | |||
=== Server initial keys cacl === | |||
Next, the server performs its own calculation of the Initial traffic keys. It gets the 8 bytes of random data from the "Destination Connection ID" field from the client's first Initial packet: | |||
0001020304050607 | |||
and computes the same keys using the method shown in "Client Initial Keys Calc": | |||
<syntaxhighlight lang="bash"> | |||
client initial key: b14b918124fda5c8d79847602fa3520b | |||
client initial IV: ddbc15dea80925a55686a7df | |||
client initial header protection key: 6df4e9d737cdf714711d7c617ee82981 | |||
server initial key: d77fc4056fcfa32bd1302469ee6ebf90 | |||
server initial IV: fcb748e37ff79860faa07477 | |||
server initial header protection key: 440b2725e91dc79b370711ef792faa3d | |||
</syntaxhighlight> | |||
=== Server Hello === | |||
The server responds with an "Initial" packet in return. This packet contains the "ServerHello" TLS record, used to continue the TLS 1.3 encrypted session negotiation. | |||
[[Image: server hello packet.png|600px]] | |||
== UDP Datagram 3 - Server handshake finished == | |||
== UDP Datagram 4 - Acks == | |||
== UDP Datagram 5 - Client handshake finished, "ping" == | |||
== UDP Datagram 6 - "pong" == | |||
== UDP Datagram 7 - Acks == | |||
== UDP Datagram 8 - Close connection == | |||
2024年1月31日 (三) 09:58的最新版本
QUIC is a new multiplexed transport built on top of UDP. HTTP/3 is designed to take advantage of QUIC's features, including lack of Head-Of-Line blocking between streams.
The QUIC project started as an alternative to TCP+TLS+HTTP/2, with the goal of improving user experience, particularly page load times. The QUIC working group at the IETF defined a clear boundary between the transport(QUIC) and application(HTTP/3) layers, as well as migrating from QUIC Crypto to TLS 1.3.
Because TCP is implemented in operating system kernels and middleboxes, widely deploying significant changes to TCP is next to impossible. However, since QUIC is built on top of UDP and the transport functionality is encrypted, it suffers from no such limitations.
Key features of QUIC and HTTP/3 over TCP+TLS and HTTP/2 include[1]
- Reduced connection establishment time - 0 round trips in the common case
- Improved congestion control feedback
- Multiplexing without head of line blocking
- Connection migration
- Transport extensibility
- Optional unreliable delivery
Overview
Reliablity
While UDP is not a reliable transport, QUIC adds a layer on top of UDP that introduces reliability. It offers re-transmissions of packets, congestion control, pacing and the other features otherwise present in TCP. Data sent over QUIC from one end-point will appear in the other end sooner or later, as long as the connection is maintained.
By doing the fragmentation, retransmission and acknowledgements in QUIC.[2]
Streams
Similar to SCTP, SSH and HTTP/2, QUIC features separate logical streams within the physical connections. A number of parallel streams that can transfer data simultaneously over a single connection without affecting the other streams.
A connection is a negotiated setup between two end-points similar to how a TCP connection works. A QUIC connection is made to a UDP port and IP address, but once established the connection is associated by its "connection ID".
Over an established connection, either side can create streams and send data to the other end. Streams are delivered in-order and they are reliable, but different streams may be delivered out-of-order.
QUIC offers flow control on both connection and streams.
Fast handshakes
QUIC offers both 0-RTT and 1-RTT connection setups, meaning that at best QUIC needs no extra round-trips at all when setting up a new connection. The faster of those two, the 0-RTT handshake, only works if there has been a previous connection established to a host and a secret from that connection has been cached.
QUIC allows a client to include data already in the 0-RTT handshake. This feature allows a client to deliver data to the peer as fast as it possibly can, and that then of course allows the server to respond and send data back even sooner.
Encryption
The transport security used in QUIC is using TLS 1.3 (RFC 8446) and there are never any unencrypted QUIC connections.
Algorithm
UDP Datagram 1 - Client hello
Client key change generation
The connection begins with the client generating a private/public keypair for key exchange. Key exchange is a technique where two parties can agree on the same number without an eavesdropper being able to tell what the number is.
The private key is chosen by selecting an integer between 0 and 2^256-1. The client does this by generating 32 bytes (256 bits) of random data. The private key selected is[3]:
202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
The public key calculated is:
358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254
Client initialial key calculation
Next, the client continues to prepare for the connection by generating the encryption keys for the Initial packets. Because key exchange between client and server has not taken place there is limited security in these keys - any observer can derive the keys and read the traffic like the server will. Encrypting the Initial packets prevents certain kinds of attacks such as request forgery attacks.
The client begins by generating 8 bytes of random data, in this case the bytes:
0001020304050607
The client then derives encryption keys using the following process:
initial_salt = 38762cf7f55934b34d179ae6a4c80cadccbb7f0a
initial_random = (random bytes from client given above)
initial_secret = HKDF-Extract(salt: initial_salt, key: initial_random)
client_secret = HKDF-Expand-Label(key: initial_secret, label: "client in", ctx: "", len: 32)
server_secret = HKDF-Expand-Label(key: initial_secret, label: "server in", ctx: "", len: 32)
client_key = HKDF-Expand-Label(key: client_secret, label: "quic key", ctx: "", len: 16)
server_key = HKDF-Expand-Label(key: server_secret, label: "quic key", ctx: "", len: 16)
client_iv = HKDF-Expand-Label(key: client_secret, label: "quic iv", ctx: "", len: 12)
server_iv = HKDF-Expand-Label(key: server_secret, label: "quic iv", ctx: "", len: 12)
client_hp_key = HKDF-Expand-Label(key: client_secret, label: "quic hp", ctx: "", len: 16)
server_hp_key = HKDF-Expand-Label(key: server_secret, label: "quic hp", ctx: "", len: 16)
The use of the magic constant "38762cf7f55934b34d179ae6a4c80cadccbb7f0a
" as the initial salt is interesting, as it is not derived from mathematical constants or cryptographic principles. It's the value of the first SHA-1 collision, co-discovered by Google researchers (QUIC itself was initially created, sponsored, and deployed by Google).
This has introduced two new cryptographic concepts from TLS 1.3:
- HKDF-Extract - given a salt and some bytes of key material create 256 bits (32 bytes) of new key material, with the input key material's entropy evenly distributed in the output.
- HKDF-Expand-Label - given the inputs of key material, label, and context data, create a new key of the requested length.
$ init_salt=38762cf7f55934b34d179ae6a4c80cadccbb7f0a
$ init_dcid=0001020304050607
$ init_secret=$(./hkdf extract $init_salt $init_dcid)
$ csecret=$(./hkdf expandlabel $init_secret "client in" "" 32)
$ ssecret=$(./hkdf expandlabel $init_secret "server in" "" 32)
$ client_init_key=$(./hkdf expandlabel $csecret "quic key" "" 16)
$ server_init_key=$(./hkdf expandlabel $ssecret "quic key" "" 16)
$ client_init_iv=$(./hkdf expandlabel $csecret "quic iv" "" 12)
$ server_init_iv=$(./hkdf expandlabel $ssecret "quic iv" "" 12)
$ client_init_hp=$(./hkdf expandlabel $csecret "quic hp" "" 16)
$ server_init_hp=$(./hkdf expandlabel $ssecret "quic hp" "" 16)
$ echo ckey: $client_init_key
$ echo civ: $client_init_iv
$ echo chp: $client_init_hp
$ echo skey: $server_init_key
$ echo siv: $server_init_iv
$ echo shp: $server_init_hp
ckey: b14b918124fda5c8d79847602fa3520b
civ: ddbc15dea80925a55686a7df
chp: 6df4e9d737cdf714711d7c617ee82981
skey: d77fc4056fcfa32bd1302469ee6ebf90
siv: fcb748e37ff79860faa07477
shp: 440b2725e91dc79b370711ef792faa3d
From this we get the following encryption keys and IVs:
client initial key: b14b918124fda5c8d79847602fa3520b
client initial IV: ddbc15dea80925a55686a7df
server initial key: d77fc4056fcfa32bd1302469ee6ebf90
server initial IV: fcb748e37ff79860faa07477
We also get the following "header protection keys", which will be explained below: client initial header protection key: 6df4e9d737cdf714711d7c617ee82981 server initial header protection key: 440b2725e91dc79b370711ef792faa3d At this point there is still no data sent over the network.
The session begins with the client sending an "Initial" packet. This packet contains the "ClientHello" TLS record, used to begin the TLS 1.3 encrypted session.
Packet header
The packet begins with a header byte, which has header protection applied. Header protection is used to hide packet numbers and other information from outside observers.
Header protection is applied by encrypting a sample of each packet's payload with the "header protection key", then XOR'ing certain bits and bytes in each packet with the resulting data. For "long" format packets such as this one, the protected sections are the lower 4 bits of this byte, and the bytes of the Packet Number (seen later).
An example of how to compute header protection:
### "client header protection key" from calc step above
$ key=6df4e9d737cdf714711d7c617ee82981
### sample is taken from 16 bytes of payload starting
### 4 bytes past the first byte of the packet number
$ sample=ed78716be9711ba498b7ed868443bb2e
$ echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p
ed9895bb15
### first byte of result is xor'd into lower 4 bits of this byte,
### remaining bytes are xor'd one-for-one into the bytes of
### the packet number (which in this packet is only one byte)
dcid
The client has not yet received a connection ID chosen by the server. Instead it uses this field to provide the 8 bytes of random data for deriving Initial encryption keys.
scid
The client uses this field to indicate its chosen connection ID to the server.
Token
The client can use this field in some scenarios to provide a token requested by the server, such as to prove that its connection attempt is not spoofed. In this case, there is no token to provide, and the field is empty.
Packet length
The client indicates how many bytes of encrypted payload are in the packet. This field is a variable length integer - the first two bits of the first byte indicate how many total bytes are in the integer.
Packet number
This byte has header protection applied.
Encrypted data
This data is encrypted with the client "Initial" traffic key.
UDP Datagram 2 - Server hello and handshake
Server key exchange generation
The private key:
909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf
The public key calculated is:
9fd7ad6dcff4298dd3f96d5b1b2af910a0535b1488d7f8fabb349a982880b615
Server initial keys cacl
Next, the server performs its own calculation of the Initial traffic keys. It gets the 8 bytes of random data from the "Destination Connection ID" field from the client's first Initial packet: 0001020304050607 and computes the same keys using the method shown in "Client Initial Keys Calc":
client initial key: b14b918124fda5c8d79847602fa3520b
client initial IV: ddbc15dea80925a55686a7df
client initial header protection key: 6df4e9d737cdf714711d7c617ee82981
server initial key: d77fc4056fcfa32bd1302469ee6ebf90
server initial IV: fcb748e37ff79860faa07477
server initial header protection key: 440b2725e91dc79b370711ef792faa3d
Server Hello
The server responds with an "Initial" packet in return. This packet contains the "ServerHello" TLS record, used to continue the TLS 1.3 encrypted session negotiation.
UDP Datagram 3 - Server handshake finished
UDP Datagram 4 - Acks
UDP Datagram 5 - Client handshake finished, "ping"
UDP Datagram 6 - "pong"
UDP Datagram 7 - Acks
UDP Datagram 8 - Close connection
Ref: