Skip to content

Commit f71a821

Browse files
committed
quic: packet protection
Encrypt and decrypt QUIC packets according to RFC 9001. For golang/go#58547 Change-Id: Ib7f824cf08f8520400bd38d3b3ab89e8a968114e Reviewed-on: https://go-review.googlesource.com/c/net/+/475438 Reviewed-by: Roland Shoemaker <roland@golang.org> Run-TryBot: Damien Neil <dneil@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Jonathan Amsterdam <jba@google.com>
1 parent 6488c8f commit f71a821

File tree

4 files changed

+432
-0
lines changed

4 files changed

+432
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module golang.org/x/net
33
go 1.17
44

55
require (
6+
golang.org/x/crypto v0.9.0
67
golang.org/x/sys v0.8.0
78
golang.org/x/term v0.8.0
89
golang.org/x/text v0.9.0

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
22
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
33
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
4+
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
5+
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
46
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
57
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
68
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
79
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
810
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
911
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
12+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
1013
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1114
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1215
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

internal/quic/packet_protection.go

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package quic
6+
7+
import (
8+
"crypto"
9+
"crypto/aes"
10+
"crypto/cipher"
11+
"crypto/sha256"
12+
"crypto/tls"
13+
"errors"
14+
"fmt"
15+
"hash"
16+
17+
"golang.org/x/crypto/chacha20"
18+
"golang.org/x/crypto/chacha20poly1305"
19+
"golang.org/x/crypto/cryptobyte"
20+
"golang.org/x/crypto/hkdf"
21+
)
22+
23+
var errInvalidPacket = errors.New("quic: invalid packet")
24+
25+
// keys holds the cryptographic material used to protect packets
26+
// at an encryption level and direction. (e.g., Initial client keys.)
27+
//
28+
// keys are not safe for concurrent use.
29+
type keys struct {
30+
// AEAD function used for packet protection.
31+
aead cipher.AEAD
32+
33+
// The header_protection function as defined in:
34+
// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.1
35+
//
36+
// This function takes a sample of the packet ciphertext
37+
// and returns a 5-byte mask which will be applied to the
38+
// protected portions of the packet header.
39+
headerProtection func(sample []byte) (mask [5]byte)
40+
41+
// IV used to construct the AEAD nonce.
42+
iv []byte
43+
}
44+
45+
// newKeys creates keys for a given cipher suite and secret.
46+
//
47+
// It returns an error if the suite is unknown.
48+
func newKeys(suite uint16, secret []byte) (keys, error) {
49+
switch suite {
50+
case tls.TLS_AES_128_GCM_SHA256:
51+
return newAESKeys(secret, crypto.SHA256, 128/8), nil
52+
case tls.TLS_AES_256_GCM_SHA384:
53+
return newAESKeys(secret, crypto.SHA384, 256/8), nil
54+
case tls.TLS_CHACHA20_POLY1305_SHA256:
55+
return newChaCha20Keys(secret), nil
56+
}
57+
return keys{}, fmt.Errorf("unknown cipher suite %x", suite)
58+
}
59+
60+
func newAESKeys(secret []byte, h crypto.Hash, keyBytes int) keys {
61+
// https://www.rfc-editor.org/rfc/rfc9001#section-5.1
62+
key := hkdfExpandLabel(h.New, secret, "quic key", nil, keyBytes)
63+
c, err := aes.NewCipher(key)
64+
if err != nil {
65+
panic(err)
66+
}
67+
aead, err := cipher.NewGCM(c)
68+
if err != nil {
69+
panic(err)
70+
}
71+
iv := hkdfExpandLabel(h.New, secret, "quic iv", nil, aead.NonceSize())
72+
// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.3
73+
hpKey := hkdfExpandLabel(h.New, secret, "quic hp", nil, keyBytes)
74+
hp, err := aes.NewCipher(hpKey)
75+
if err != nil {
76+
panic(err)
77+
}
78+
var scratch [aes.BlockSize]byte
79+
headerProtection := func(sample []byte) (mask [5]byte) {
80+
hp.Encrypt(scratch[:], sample)
81+
copy(mask[:], scratch[:])
82+
return mask
83+
}
84+
return keys{
85+
aead: aead,
86+
iv: iv,
87+
headerProtection: headerProtection,
88+
}
89+
}
90+
91+
func newChaCha20Keys(secret []byte) keys {
92+
// https://www.rfc-editor.org/rfc/rfc9001#section-5.1
93+
key := hkdfExpandLabel(sha256.New, secret, "quic key", nil, chacha20poly1305.KeySize)
94+
aead, err := chacha20poly1305.New(key)
95+
if err != nil {
96+
panic(err)
97+
}
98+
iv := hkdfExpandLabel(sha256.New, secret, "quic iv", nil, aead.NonceSize())
99+
// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.4
100+
hpKey := hkdfExpandLabel(sha256.New, secret, "quic hp", nil, chacha20.KeySize)
101+
headerProtection := func(sample []byte) [5]byte {
102+
counter := uint32(sample[3])<<24 | uint32(sample[2])<<16 | uint32(sample[1])<<8 | uint32(sample[0])
103+
nonce := sample[4:16]
104+
c, err := chacha20.NewUnauthenticatedCipher(hpKey, nonce)
105+
if err != nil {
106+
panic(err)
107+
}
108+
c.SetCounter(counter)
109+
var mask [5]byte
110+
c.XORKeyStream(mask[:], mask[:])
111+
return mask
112+
}
113+
return keys{
114+
aead: aead,
115+
iv: iv,
116+
headerProtection: headerProtection,
117+
}
118+
}
119+
120+
// https://www.rfc-editor.org/rfc/rfc9001#section-5.2-2
121+
var initialSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
122+
123+
// initialKeys returns the keys used to protect Initial packets.
124+
//
125+
// The Initial packet keys are derived from the Destination Connection ID
126+
// field in the client's first Initial packet.
127+
//
128+
// https://www.rfc-editor.org/rfc/rfc9001#section-5.2
129+
func initialKeys(cid []byte) (clientKeys, serverKeys keys) {
130+
initialSecret := hkdf.Extract(sha256.New, cid, initialSalt)
131+
clientInitialSecret := hkdfExpandLabel(sha256.New, initialSecret, "client in", nil, sha256.Size)
132+
clientKeys, err := newKeys(tls.TLS_AES_128_GCM_SHA256, clientInitialSecret)
133+
if err != nil {
134+
panic(err)
135+
}
136+
137+
serverInitialSecret := hkdfExpandLabel(sha256.New, initialSecret, "server in", nil, sha256.Size)
138+
serverKeys, err = newKeys(tls.TLS_AES_128_GCM_SHA256, serverInitialSecret)
139+
if err != nil {
140+
panic(err)
141+
}
142+
143+
return clientKeys, serverKeys
144+
}
145+
146+
const headerProtectionSampleSize = 16
147+
148+
// aeadOverhead is the difference in size between the AEAD output and input.
149+
// All cipher suites defined for use with QUIC have 16 bytes of overhead.
150+
const aeadOverhead = 16
151+
152+
// xorIV xors the packet protection IV with the packet number.
153+
func (k keys) xorIV(pnum packetNumber) {
154+
k.iv[len(k.iv)-8] ^= uint8(pnum >> 56)
155+
k.iv[len(k.iv)-7] ^= uint8(pnum >> 48)
156+
k.iv[len(k.iv)-6] ^= uint8(pnum >> 40)
157+
k.iv[len(k.iv)-5] ^= uint8(pnum >> 32)
158+
k.iv[len(k.iv)-4] ^= uint8(pnum >> 24)
159+
k.iv[len(k.iv)-3] ^= uint8(pnum >> 16)
160+
k.iv[len(k.iv)-2] ^= uint8(pnum >> 8)
161+
k.iv[len(k.iv)-1] ^= uint8(pnum)
162+
}
163+
164+
// initialized returns true if valid keys are available.
165+
func (k keys) initialized() bool {
166+
return k.aead != nil
167+
}
168+
169+
// discard discards the keys (in the sense that we won't use them any more,
170+
// not that the keys are securely erased).
171+
//
172+
// https://www.rfc-editor.org/rfc/rfc9001.html#section-4.9
173+
func (k *keys) discard() {
174+
*k = keys{}
175+
}
176+
177+
// protect applies packet protection to a packet.
178+
//
179+
// On input, hdr contains the packet header, pay the unencrypted payload,
180+
// pnumOff the offset of the packet number in the header, and pnum the untruncated
181+
// packet number.
182+
//
183+
// protect returns the result of appending the encrypted payload to hdr and
184+
// applying header protection.
185+
func (k keys) protect(hdr, pay []byte, pnumOff int, pnum packetNumber) []byte {
186+
k.xorIV(pnum)
187+
hdr = k.aead.Seal(hdr, k.iv, pay, hdr)
188+
k.xorIV(pnum)
189+
190+
// Apply header protection.
191+
pnumSize := int(hdr[0]&0x03) + 1
192+
sample := hdr[pnumOff+4:][:headerProtectionSampleSize]
193+
mask := k.headerProtection(sample)
194+
if isLongHeader(hdr[0]) {
195+
hdr[0] ^= mask[0] & 0x0f
196+
} else {
197+
hdr[0] ^= mask[0] & 0x1f
198+
}
199+
for i := 0; i < pnumSize; i++ {
200+
hdr[pnumOff+i] ^= mask[1+i]
201+
}
202+
203+
return hdr
204+
}
205+
206+
// unprotect removes packet protection from a packet.
207+
//
208+
// On input, pkt contains the full protected packet, pnumOff the offset of
209+
// the packet number in the header, and pnumMax the largest packet number
210+
// seen in the number space of this packet.
211+
//
212+
// unprotect removes header protection from the header in pkt, and returns
213+
// the unprotected payload and packet number.
214+
func (k keys) unprotect(pkt []byte, pnumOff int, pnumMax packetNumber) (pay []byte, num packetNumber, err error) {
215+
if len(pkt) < pnumOff+4+headerProtectionSampleSize {
216+
fmt.Println("too short")
217+
return nil, 0, errInvalidPacket
218+
}
219+
numpay := pkt[pnumOff:]
220+
sample := numpay[4:][:headerProtectionSampleSize]
221+
mask := k.headerProtection(sample)
222+
if isLongHeader(pkt[0]) {
223+
pkt[0] ^= mask[0] & 0x0f
224+
} else {
225+
pkt[0] ^= mask[0] & 0x1f
226+
}
227+
pnumLen := int(pkt[0]&0x03) + 1
228+
pnum := packetNumber(0)
229+
for i := 0; i < pnumLen; i++ {
230+
numpay[i] ^= mask[1+i]
231+
pnum = (pnum << 8) | packetNumber(numpay[i])
232+
}
233+
pnum = decodePacketNumber(pnumMax, pnum, pnumLen)
234+
235+
hdr := pkt[:pnumOff+pnumLen]
236+
pay = numpay[pnumLen:]
237+
k.xorIV(pnum)
238+
pay, err = k.aead.Open(pay[:0], k.iv, pay, hdr)
239+
k.xorIV(pnum)
240+
if err != nil {
241+
return nil, 0, err
242+
}
243+
244+
return pay, pnum, nil
245+
}
246+
247+
// hdkfExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1.
248+
//
249+
// Copied from crypto/tls/key_schedule.go.
250+
func hkdfExpandLabel(hash func() hash.Hash, secret []byte, label string, context []byte, length int) []byte {
251+
var hkdfLabel cryptobyte.Builder
252+
hkdfLabel.AddUint16(uint16(length))
253+
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
254+
b.AddBytes([]byte("tls13 "))
255+
b.AddBytes([]byte(label))
256+
})
257+
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
258+
b.AddBytes(context)
259+
})
260+
out := make([]byte, length)
261+
n, err := hkdf.Expand(hash, secret, hkdfLabel.BytesOrPanic()).Read(out)
262+
if err != nil || n != length {
263+
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
264+
}
265+
return out
266+
}

0 commit comments

Comments
 (0)