Before Sending Data (Client Key Exchange Generatio)

Here we adopt X25519 algorithms to generate private and public key (pk, sk), length(sk) = 256, namely the sk is a random integer from (0, $2^{256}-1$)

1
2
3
4
5
6
7
8
9
10
11
12
13
The public key calculation can be confirmed at the command line:
### requires openssl 1.1.0 or higher
$ openssl pkey -noout -text < client-ephemeral-private.key

X25519 Private-Key:
priv:
20:21:22:23:24:25:26:27:28:29:2a:2b:2c:2d:2e:
2f:30:31:32:33:34:35:36:37:38:39:3a:3b:3c:3d:
3e:3f
pub:
35:80:72:d6:36:58:80:d1:ae:ea:32:9a:df:91:21:
38:38:51:ed:21:a2:8e:3b:75:e9:65:d0:d2:cd:16:
62:54

So the sk is 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f

pk is 358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254

Before sending data (Client Initial Key Calculation)

The encryption key is derived as following processes:

1
2
3
4
5
6
7
8
9
10
11
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)

Where

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.

Let’s download a open-source HKDF tool using from https://quic.xargs.org/files/hkdf.sh

Then perform the following code to realize the above key derivation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

Here the ckey and skey are the data encryption keys for the client and server, the chp and shp are the initial header protection keys for the client and server.

The first data is sent below.

UDP Datagram 1: Client Hello

Client Initial Packet

The session gets started from the client sending an “initial” packet. The packet includes a “ClientHello” TLS 1.3 record, to begin the TLS 1.3 encrypted session. (Because QUIC contains TLS 1.3 protocol)

Packet Header Byte

Where header protection applies, this byte is used to hide packet numbers and other information.

Header protection is applied by encrypting a sample of each packet’s payload, with the “header protection key”, then using the resulting data to XOR some certain bits and bytes.

The original client initial packet is c0

Val Meaning
MSB 1 Long header format
1 Fixed bit (always set)
00 Packet type: Initial
00 Reserved (always unset)
LSB 00 Packet Number field length (indicates the “Packet Number” field below will have length of one byte)

Now we calculate the header protection

1
2
3
4
5
6
7
8
9
10
11
12
13
### "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这里是指PN后面4个字节再后面的16个字节。
sample=ed78716be9711ba498b7ed868443bb2e
echo $sample | xxd -r -p | openssl aes-128-ecb -K $key | head -c 5 | xxd -p

output: ed9895bb15
### first byte of packet is xor'd into lower 4 bits of this byte, namely ed \xor 0d = e0
### remaining bytes are xor'd one-for-one(一对一) into the bytes of
### the packet number (which in this packet is only one byte), so 3 bytes remained.

### So here, c0 \xor d = cd

QUIC Version

00 00 00 01: 4 bytes, version 1.

Destination Connection ID

The client has not received a connection ID chosen by the server, so it uses this field to generate the 8 bytes of random data for deriving initial encryption keys, as explained in “Initial Keys Calc“ above, we can see it is 0001020304050607

08 00 01 02 03 04 05 06 07

Here 08 - 8 bytes of connection ID follows

Source Connection ID (c_cid)

The client chooses a connection ID and indicateds it to the server.

05 63 5f 63 69 64

05 - 5 bytes of connection ID follows

63 5f 63 69 64 - the connection ID “c_cid”

Token (Not useful here)

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.

00: 0 bytes of token data follows

Packet Length

41 03

The client indicates how many bytes of encrypted payload are in the packet. This length is variable - the first two bits of the first byte incidates how many total bits are in the length number. Namely 4 = 0100 starts with 01, shows there should be 2 bytes for length (a fixed rule).

Packet Number

98 (being protected), original one is 00

1
2
output: ed9895bb15
### we use 98 \xor 00 = 98

So I think the only useful two in the Client Hello, is the c_id and init_dcid.

Encrypted Data

1
1c 36 a7 ed 78 71 6b e9 71 1b a4 98 b7 ed 86 84 43 bb 2e 0c 51 4d 4d 84 8e ad cc 7a 00 d2 5c e9 f9 af a4 83 97 80 88 de 83 6b e6 8c 0b 32 a2 45 95 d7 81 3e a5 41 4a 91 99 32 9a 6d 9f 7f 76 0d d8 bb 24 9b f3 f5 3d 9a 77 fb b7 b3 95 b8 d6 6d 78 79 a5 1f e5 9e f9 60 1f 79 99 8e b3 56 8e 1f dc 78 9f 64 0a ca b3 85 8a 82 ef 29 30 fa 5c e1 4b 5b 9e a0 bd b2 9f 45 72 da 85 aa 3d ef 39 b7 ef af ff a0 74 b9 26 70 70 d5 0b 5d 07 84 2e 49 bb a3 bc 78 7f f2 95 d6 ae 3b 51 43 05 f1 02 af e5 a0 47 b3 fb 4c 99 eb 92 a2 74 d2 44 d6 04 92 c0 e2 e6 e2 12 ce f0 f9 e3 f6 2e fd 09 55 e7 1c 76 8a a6 bb 3c d8 0b bb 37 55 c8 b7 eb ee 32 71 2f 40 f2 24 51 19 48 70 21 b4 b8 4e 15 65 e3 ca 31 96 7a c8 60 4d 40 32 17 0d ec 28 0a ee fa 09 5d 08

This data is encrypted with the client “Initial” traffic key.

Auth Tag

1
b3 b7 24 1e f6 64 6a 6c 86 e5 c6 2c e0 8b e0 99

This is the AEAD authentication tag that confirms the integrity of the encrypted data and the packet header. It is produced by the encryption algorithm, and consumed by the decryption algorithm.

We can use this code to decrypt the encrypted data, as below:

1
2
3
4
5
6
7
8
9
10
key=b14b918124fda5c8d79847602fa3520b
iv=ddbc15dea80925a55686a7df
recdata=c00000000108000102030405060705635f63696400410300
authtag=b3b7241ef6646a6c86e5c62ce08be099
recordnum=0
cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
###其中msg1就是上面我们的加密数据,我们自己写进去即可

TLS: ClientHello (Starts encrypted session)

The encrypted session begins with the client saying “Hello”. The client provides information including the following:

TLS Handshake Header

Client Version

03 03

A protocol version of “3,3” (meaning TLS 1.2) is given. Because middleboxes have been created and widely deployed that do not allow protocol versions that they do not recognize, all TLS 1.3 sessions indicate version TLS 1.2 in this field. This field is no longer used, instead version negotiation is performed using the “Supported Versions” extension below.

Client Random

32 random bytes

Session ID (legacy, not used)

00

Cipher Suites: important!

Compression Methods (Not used in TLS 1.3!)

01 00

Extensions Length

00 bb: means 187 bytes extensions.

Extension - Server Name (SNI)

Without this, a HTTPS server would not be able to provide service for multiple hostnames on a single IP, because it couldn’t know which hostname’s certificate should be sent to the client, until after the TLS session was negotiated and the HTTP request was made.

00 00 - assigned value for extension “server name”
00 18 - 0x18 (24) bytes of “server name” extension data follows
00 16 - 0x16 (22) bytes of first (and only) list entry follows
00 - list entry is type 0x00 “DNS hostname”
00 13 - 0x13 (19) bytes of hostname follows

Extension - Supported Groups

00 0a 00 08 00 06 00 1d 00 17 00 18

00 0a assigned value for extension “supported groups”

00 088 bytes of “supported group” extension data follows

00 066 bytes of data are in the curves list

The client has indicated that it supports elliptic curve (EC) cryptography for three curve types: x25519, secp256r1, secp384r1.

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.

Extension - ALPN (Application Layer Protocol Negotiation)

QUIC to negotiate supported protocols and versions between server and client, might be “http/1.1”, “h2” (HTTP/2), or “h3” (HTTP/3).

Extension - Signature Algorithms

This can influence the certificate that the server presents to the client, as well as the signature that is sent by the server in the CertificateVerify record.

like 02 01assigned value for RSA-PKCS1-SHA1, 04 03assigned value for ECDSA-SECP256r1-SHA256.

Extension - Key Share

The client sends one or more ephemeral public keys using algorithms that it thinks the server will support.

This allows the rest of the handshake after the ClientHello and ServerHello messages to be encrypted. Since in the previous protocols, the handshake messages are sent in the clear.

00 33 - assigned value for extension “Key Share”
00 26 - 0x26 (38) bytes of “Key Share” extension data follows
00 24 - 0x24 (36) bytes of key share data follows
00 1d - assigned value for x25519 (key exchange via curve25519)
00 20 - 0x20 (32) bytes of public key follows
35 80 ... 62 54 - public key from the step “Client Key Exchange Generation”

Extension - PSK Key Exchange Modes

The client indicates the modes available for establishing keys from pre-shared keys (PSKs). Since we do not use PSKs in this session, this extension has no effect.

00 2d - assigned value for extension “PSK Key Exchange Modes”
00 02 - 2 bytes of “PSK Key Exchange Modes” extension data follows
01 - 1 bytes of exchange modes follow
01 - assigned value for “PSK with (EC)DHE key establishment”

Extension - Supported Versions

Because previously we only indicate it is TLS 1.2, we need to let the server know we are using TLS 1.3

00 2b - assigned value for extension “Supported Versions”
00 03 - 3 bytes of “Supported Versions” extension data follows
02 - 2 bytes of TLS version follows
03 04 - assigned value for TLS 1.3

Extension - QUIC Transport Parameters

We put the client’s QUIC other configuration values into this record, instead of the headers of the initial packet because all data in TLS records is protected from tampering by malicious actors.

For example:

max_udp_payload_size: 65527
initial_max_data: 10485760
initial_max_stream_data_bidi_local: 1048576
initial_max_stream_data_bidi_remote: 1048576
initial_max_stream_data_uni: 1048576
initial_max_streams_bidi: 10
initial_max_streams_uni: 10
ack_delay_exponent: 3
initial_source_connection_id: “c_cid”: 63 5f 63 69 64 - a copy of the source connection ID from the packet header: “c_cid”

To sum up, this is just the TLS 1.3 packet with more extensions for QUIC connection~

Padding

Any datagram sent by the client that contains an Initial packet must be padded to a length of 1200 bytes. This library does it by appending nul bytes to the datagram.

Server Key Exchange Generation

The server creates its own private/public keypair for key exchange. The private key can be downloaded from https://quic.xargs.org/files/server-ephemeral-private.key

1
2
3
4
5
6
7
8
9
10
openssl pkey -noout -text < server-ephemeral-private.key
X25519 Private-Key:
priv:
90:91:92:93:94:95:96:97:98:99:9a:9b:9c:9d:9e:
9f:a0:a1:a2:a3:a4:a5:a6:a7:a8:a9:aa:ab:ac:ad:
ae:af
pub:
9f:d7:ad:6d:cf:f4:29:8d:d3:f9:6d:5b:1b:2a:f9:
10:a0:53:5b:14:88:d7:f8:fa:bb:34:9a:98:28:80:
b6:15

Server Initial Key Calculation

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

It computes the same keys using the method shown in “Client Initial Keys Calc”.

1
2
3
4
5
6
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

UDP Datagram 2: ServerHello and Handshake

Server Initial Packet

Packet Header Byte (also with header protection)

QUIC Version

Destination Connection ID (c_cid)

The client’s chosen connection ID is repeated back to it, just the “c_cid”.

Source Connection ID (the first time to choose s_cid)

The server uses this field to indicate its chosen connection ID to the client.

05 - 5 bytes of connection ID follows
73 5f 63 69 64 - the connection ID “s_cid”

Token (unused)

Packet Length

the same as that in the ClientHello.

Packet Number(also protected using the same way)

Encrypted Data

This data is encrypted with the server “initial” traffic key!.

Auth Tag

f0 b5 17 a9 26 d6 2a 54 a9 29 41 36 b1 43 b0 33

This is the AEAD authentication tag that confirms the integrity of the encrypted data and the packet header. It is produced by the encryption algorithm, and consumed by the decryption algorithm.

Decryption

This data is encrypted using the server “Initial” traffic key and IV that were generated during the “Initial Keys Calc” step. The IV will be modified by XOR’ing it by the count of records that have already been encrypted with this key, which in this case is 0. The process also takes as input the 21 bytes of header at the beginning of this packet, as authenticated data that must match for decryption to succeed. The decryption and encryption is based on AEAD cryptography, for example, poly1305-AES, or AES-128-GCM, are both AEAD encryption. That is, associated data will be used to encrypt and decrypt, also authenticate that the encryption and decryption match each other. By the way, the associated data is not encrypted.

Here we show the code

1
2
3
4
5
6
7
8
9
10
11
12
key=d77fc4056fcfa32bd1302469ee6ebf90
iv=fcb748e37ff79860faa07477
### from this record
recdata=c00000000105635f63696405735f63696400407500 # it is associated data
authtag=f0b517a926d62a54a9294136b143b033
recordnum=0 ###used to XOR the IV
### may need to add -I and -L flags for include and lib dirs
cc -o aes_128_gcm_decrypt aes_128_gcm_decrypt.c -lssl -lcrypto
cat /tmp/msg1 \
| ./aes_128_gcm_decrypt $iv $recordnum $key $recdata $authtag \
| hexdump -C
### complete!

ACK frame

02 00 42 40 00 00

The server acknowledges receipt of the client’s Initial packet 0 (packet number 0).

02 frame type “ACK”, 00largest packet being acknowledged

42 40 - ack_delay: variable-length integer giving the amount of time this ack was delayed in sending, in microseconds. Multiply by $2^{ack_delay_exponent}$, giving a value of 64 * 8 = 512 µseconds (cannot understand also)

00 ACK Range Count: how many inconsecutive packets need acknowledging. In this case, only one packet is received, which means no inconsecutive packet needs confirming.

00 First ACK Range: It means, before the largest acknowledged packet (0), how many consecutive packets have been confirmed. Now it is 0.

CRYPTO frame header

06 00 40 5a

CRYPTO frames create a single stream of bytes used by TLS to establish a secure connection. This means the following bytes are ServerHello TLS Record!

06 - frame type “CRYPTO”

00 - variable-length integer, offset of the crypto stream data being provided (0 bytes)

40 5a - variable length integer (first two bits indicate 2-byte integer) showing crypto stream data length of 0x5A (90) bytes

So this is all the server initial Packet! The following bytes contain TLS information, also called TLS Server Hello. So, it is just that QUIC header contains a TLS ServerHello

TLS Handshake Header (just hello)

Server Version

03 03

Server Random

The server provides 32 bytes of random data. This data will be used later in the session.

Session ID (not used, 00)

This is a legacy field and is not used in QUIC.

Cipher Suite

13 01The server has selected cipher suite 0x1301 (TLS_AES_128_GCM_SHA256) from the list of options given by the client.

Compression Method (Here the server selected null 00)

Extensions Length

The server has returned a list of extensions to the client. The server only allows to send the extensions that also appear in the client’s hello message!

Extension - Key Share

The server sends a public key using the algorithm sent by the client.

Notice: Once this is sent encryption keys can be calculated and the rest of the handshake will be encrypted

This extension includes the public key from the step “Server Key Exchange Generation”

Extension - Supported Versions (indicated TLS 1.3)

Server Handshake Key Calculation

It uses the following information in this calculation:
client public key (from Client Hello)
server private key (from Server Key Exchange Generation)
SHA256 hash of ClientHello and ServerHello

Here is the code used https://quic.xargs.org/files/curve25519-mult.c

1
2
3
4
5
6
cc -o curve25519-mult curve25519-mult.c
./curve25519-mult server-ephemeral-private.key \
client-ephemeral-public.key | hexdump
output:
0000000 4adf 1b29 1eaa cfb7 93a6 294b 74b4 adba
0000010 9726 9fe2 921f cc0d c877 a0a0 4488 2476

It then calculates the SHA256 hash of all handshake messages to this point (ClientHello and ServerHello).

Note: The hash does not include the 6-byte CRYPTO frame headers. This “hello_hash” is ff788f9ed09e60d8142ac10a8931cdb6a3726278d3acdba54d9d9ffc7326611b

1
cat crypto_clienthello crypto_serverhello | openssl sha256

We then feed the hash and the shared secret into a set of key derivation operations, designed to protect against known and possible attacks, we also use the HKDF tool as mentioned above.

The logic is:

1
2
3
4
5
6
7
8
9
10
early_secret = HKDF-Extract(salt=00, key=00...)
empty_hash = SHA256("")
derived_secret = HKDF-Expand-Label(key: early_secret, label: "derived", ctx: empty_hash, len: 32)
handshake_secret = HKDF-Extract(salt: derived_secret, key: shared_secret)
client_secret = HKDF-Expand-Label(key: handshake_secret, label: "c hs traffic", ctx: hello_hash, len: 32)
server_secret = HKDF-Expand-Label(key: handshake_secret, label: "s hs traffic", ctx: hello_hash, 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)

The shell commands are:

1
2
3
4
5
6
7
hello_hash=ff788f9ed09e60d8142ac10a8931cdb6a3726278d3acdba54d9d9ffc7326611b
shared_secret=df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624
zero_key=0000000000000000000000000000000000000000000000000000000000000000
early_secret=$(./hkdf extract 00 $zero_key)
empty_hash=$(openssl sha256 < /dev/null | sed -e 's/.* //')
derived_secret=$(./hkdf expandlabel $early_secret "derived" $empty_hash 32)
handshake_secret=$(./hkdf extract $derived_secret $shared_secret)

Server Handshake Packet

Packet Header Byte (The same way to compute header protection)

QUIC Version

Destination Connection ID (c_cid again)

Source Connection ID (s_cid again)

Packet Length

Packet Number (with header protection)

Encrypted Data(using handshake traffic key generated from Server Handshake Packet)

This data is encrypted with the server “Handshake” traffic key.

Auth Tag

This is the AEAD authentication tag (associated data?) that confirms the integrity of the encrypted data and the packet header.

Decryption