/
packet_protection.go
535 lines (484 loc) · 16.6 KB
/
packet_protection.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.21
package quic
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"crypto/tls"
"errors"
"hash"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/hkdf"
)
var errInvalidPacket = errors.New("quic: invalid packet")
// headerProtectionSampleSize is the size of the ciphertext sample used for header protection.
// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.2
const headerProtectionSampleSize = 16
// aeadOverhead is the difference in size between the AEAD output and input.
// All cipher suites defined for use with QUIC have 16 bytes of overhead.
const aeadOverhead = 16
// A headerKey applies or removes header protection.
// https://www.rfc-editor.org/rfc/rfc9001#section-5.4
type headerKey struct {
hp headerProtection
}
func (k headerKey) isSet() bool {
return k.hp != nil
}
func (k *headerKey) init(suite uint16, secret []byte) {
h, keySize := hashForSuite(suite)
hpKey := hkdfExpandLabel(h.New, secret, "quic hp", nil, keySize)
switch suite {
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384:
c, err := aes.NewCipher(hpKey)
if err != nil {
panic(err)
}
k.hp = &aesHeaderProtection{cipher: c}
case tls.TLS_CHACHA20_POLY1305_SHA256:
k.hp = chaCha20HeaderProtection{hpKey}
default:
panic("BUG: unknown cipher suite")
}
}
// protect applies header protection.
// pnumOff is the offset of the packet number in the packet.
func (k headerKey) protect(hdr []byte, pnumOff int) {
// Apply header protection.
pnumSize := int(hdr[0]&0x03) + 1
sample := hdr[pnumOff+4:][:headerProtectionSampleSize]
mask := k.hp.headerProtection(sample)
if isLongHeader(hdr[0]) {
hdr[0] ^= mask[0] & 0x0f
} else {
hdr[0] ^= mask[0] & 0x1f
}
for i := 0; i < pnumSize; i++ {
hdr[pnumOff+i] ^= mask[1+i]
}
}
// unprotect removes header protection.
// pnumOff is the offset of the packet number in the packet.
// pnumMax is the largest packet number seen in the number space of this packet.
func (k headerKey) unprotect(pkt []byte, pnumOff int, pnumMax packetNumber) (hdr, pay []byte, pnum packetNumber, _ error) {
if len(pkt) < pnumOff+4+headerProtectionSampleSize {
return nil, nil, 0, errInvalidPacket
}
numpay := pkt[pnumOff:]
sample := numpay[4:][:headerProtectionSampleSize]
mask := k.hp.headerProtection(sample)
if isLongHeader(pkt[0]) {
pkt[0] ^= mask[0] & 0x0f
} else {
pkt[0] ^= mask[0] & 0x1f
}
pnumLen := int(pkt[0]&0x03) + 1
pnum = packetNumber(0)
for i := 0; i < pnumLen; i++ {
numpay[i] ^= mask[1+i]
pnum = (pnum << 8) | packetNumber(numpay[i])
}
pnum = decodePacketNumber(pnumMax, pnum, pnumLen)
hdr = pkt[:pnumOff+pnumLen]
pay = numpay[pnumLen:]
return hdr, pay, pnum, nil
}
// headerProtection is the header_protection function as defined in:
// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.1
//
// This function takes a sample of the packet ciphertext
// and returns a 5-byte mask which will be applied to the
// protected portions of the packet header.
type headerProtection interface {
headerProtection(sample []byte) (mask [5]byte)
}
// AES-based header protection.
// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.3
type aesHeaderProtection struct {
cipher cipher.Block
scratch [aes.BlockSize]byte
}
func (hp *aesHeaderProtection) headerProtection(sample []byte) (mask [5]byte) {
hp.cipher.Encrypt(hp.scratch[:], sample)
copy(mask[:], hp.scratch[:])
return mask
}
// ChaCha20-based header protection.
// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.4
type chaCha20HeaderProtection struct {
key []byte
}
func (hp chaCha20HeaderProtection) headerProtection(sample []byte) (mask [5]byte) {
counter := uint32(sample[3])<<24 | uint32(sample[2])<<16 | uint32(sample[1])<<8 | uint32(sample[0])
nonce := sample[4:16]
c, err := chacha20.NewUnauthenticatedCipher(hp.key, nonce)
if err != nil {
panic(err)
}
c.SetCounter(counter)
c.XORKeyStream(mask[:], mask[:])
return mask
}
// A packetKey applies or removes packet protection.
// https://www.rfc-editor.org/rfc/rfc9001#section-5.1
type packetKey struct {
aead cipher.AEAD // AEAD function used for packet protection.
iv []byte // IV used to construct the AEAD nonce.
}
func (k *packetKey) init(suite uint16, secret []byte) {
// https://www.rfc-editor.org/rfc/rfc9001#section-5.1
h, keySize := hashForSuite(suite)
key := hkdfExpandLabel(h.New, secret, "quic key", nil, keySize)
switch suite {
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384:
k.aead = newAESAEAD(key)
case tls.TLS_CHACHA20_POLY1305_SHA256:
k.aead = newChaCha20AEAD(key)
default:
panic("BUG: unknown cipher suite")
}
k.iv = hkdfExpandLabel(h.New, secret, "quic iv", nil, k.aead.NonceSize())
}
func newAESAEAD(key []byte) cipher.AEAD {
c, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
aead, err := cipher.NewGCM(c)
if err != nil {
panic(err)
}
return aead
}
func newChaCha20AEAD(key []byte) cipher.AEAD {
var err error
aead, err := chacha20poly1305.New(key)
if err != nil {
panic(err)
}
return aead
}
func (k packetKey) protect(hdr, pay []byte, pnum packetNumber) []byte {
k.xorIV(pnum)
defer k.xorIV(pnum)
return k.aead.Seal(hdr, k.iv, pay, hdr)
}
func (k packetKey) unprotect(hdr, pay []byte, pnum packetNumber) (dec []byte, err error) {
k.xorIV(pnum)
defer k.xorIV(pnum)
return k.aead.Open(pay[:0], k.iv, pay, hdr)
}
// xorIV xors the packet protection IV with the packet number.
func (k packetKey) xorIV(pnum packetNumber) {
k.iv[len(k.iv)-8] ^= uint8(pnum >> 56)
k.iv[len(k.iv)-7] ^= uint8(pnum >> 48)
k.iv[len(k.iv)-6] ^= uint8(pnum >> 40)
k.iv[len(k.iv)-5] ^= uint8(pnum >> 32)
k.iv[len(k.iv)-4] ^= uint8(pnum >> 24)
k.iv[len(k.iv)-3] ^= uint8(pnum >> 16)
k.iv[len(k.iv)-2] ^= uint8(pnum >> 8)
k.iv[len(k.iv)-1] ^= uint8(pnum)
}
// A fixedKeys is a header protection key and fixed packet protection key.
// The packet protection key is fixed (it does not update).
//
// Fixed keys are used for Initial and Handshake keys, which do not update.
type fixedKeys struct {
hdr headerKey
pkt packetKey
}
func (k *fixedKeys) init(suite uint16, secret []byte) {
k.hdr.init(suite, secret)
k.pkt.init(suite, secret)
}
func (k fixedKeys) isSet() bool {
return k.hdr.hp != nil
}
// protect applies packet protection to a packet.
//
// On input, hdr contains the packet header, pay the unencrypted payload,
// pnumOff the offset of the packet number in the header, and pnum the untruncated
// packet number.
//
// protect returns the result of appending the encrypted payload to hdr and
// applying header protection.
func (k fixedKeys) protect(hdr, pay []byte, pnumOff int, pnum packetNumber) []byte {
pkt := k.pkt.protect(hdr, pay, pnum)
k.hdr.protect(pkt, pnumOff)
return pkt
}
// unprotect removes packet protection from a packet.
//
// On input, pkt contains the full protected packet, pnumOff the offset of
// the packet number in the header, and pnumMax the largest packet number
// seen in the number space of this packet.
//
// unprotect removes header protection from the header in pkt, and returns
// the unprotected payload and packet number.
func (k fixedKeys) unprotect(pkt []byte, pnumOff int, pnumMax packetNumber) (pay []byte, num packetNumber, err error) {
hdr, pay, pnum, err := k.hdr.unprotect(pkt, pnumOff, pnumMax)
if err != nil {
return nil, 0, err
}
pay, err = k.pkt.unprotect(hdr, pay, pnum)
if err != nil {
return nil, 0, err
}
return pay, pnum, nil
}
// A fixedKeyPair is a read/write pair of fixed keys.
type fixedKeyPair struct {
r, w fixedKeys
}
func (k *fixedKeyPair) discard() {
*k = fixedKeyPair{}
}
func (k *fixedKeyPair) canRead() bool {
return k.r.isSet()
}
func (k *fixedKeyPair) canWrite() bool {
return k.w.isSet()
}
// An updatingKeys is a header protection key and updatable packet protection key.
// updatingKeys are used for 1-RTT keys, where the packet protection key changes
// over the lifetime of a connection.
// https://www.rfc-editor.org/rfc/rfc9001#section-6
type updatingKeys struct {
suite uint16
hdr headerKey
pkt [2]packetKey // current, next
nextSecret []byte // secret used to generate pkt[1]
}
func (k *updatingKeys) init(suite uint16, secret []byte) {
k.suite = suite
k.hdr.init(suite, secret)
// Initialize pkt[1] with secret_0, and then call update to generate secret_1.
k.pkt[1].init(suite, secret)
k.nextSecret = secret
k.update()
}
// update performs a key update.
// The current key in pkt[0] is discarded.
// The next key in pkt[1] becomes the current key.
// A new next key is generated in pkt[1].
func (k *updatingKeys) update() {
k.nextSecret = updateSecret(k.suite, k.nextSecret)
k.pkt[0] = k.pkt[1]
k.pkt[1].init(k.suite, k.nextSecret)
}
func updateSecret(suite uint16, secret []byte) (nextSecret []byte) {
h, _ := hashForSuite(suite)
return hkdfExpandLabel(h.New, secret, "quic ku", nil, len(secret))
}
// An updatingKeyPair is a read/write pair of updating keys.
//
// We keep two keys (current and next) in both read and write directions.
// When an incoming packet's phase matches the current phase bit,
// we unprotect it using the current keys; otherwise we use the next keys.
//
// When updating=false, outgoing packets are protected using the current phase.
//
// An update is initiated and updating is set to true when:
// - we decide to initiate a key update; or
// - we successfully unprotect a packet using the next keys,
// indicating the peer has initiated a key update.
//
// When updating=true, outgoing packets are protected using the next phase.
// We do not change the current phase bit or generate new keys yet.
//
// The update concludes when we receive an ACK frame for a packet sent
// with the next keys. At this time, we set updating to false, flip the
// phase bit, and update the keys. This permits us to handle up to 1-RTT
// of reordered packets before discarding the previous phase's keys after
// an update.
type updatingKeyPair struct {
phase uint8 // current key phase (r.pkt[0], w.pkt[0])
updating bool
authFailures int64 // total packet unprotect failures
minSent packetNumber // min packet number sent since entering the updating state
minReceived packetNumber // min packet number received in the next phase
updateAfter packetNumber // packet number after which to initiate key update
r, w updatingKeys
}
func (k *updatingKeyPair) init() {
// 1-RTT packets until the first key update.
//
// We perform the first key update early in the connection so a peer
// which does not support key updates will fail rapidly,
// rather than after the connection has been long established.
k.updateAfter = 1000
}
func (k *updatingKeyPair) canRead() bool {
return k.r.hdr.hp != nil
}
func (k *updatingKeyPair) canWrite() bool {
return k.w.hdr.hp != nil
}
// handleAckFor finishes a key update after receiving an ACK for a packet in the next phase.
func (k *updatingKeyPair) handleAckFor(pnum packetNumber) {
if k.updating && pnum >= k.minSent {
k.updating = false
k.phase ^= keyPhaseBit
k.r.update()
k.w.update()
}
}
// needAckEliciting reports whether we should send an ack-eliciting packet in the next phase.
// The first packet sent in a phase is ack-eliciting, since the peer must acknowledge a
// packet in the new phase for us to finish the update.
func (k *updatingKeyPair) needAckEliciting() bool {
return k.updating && k.minSent == maxPacketNumber
}
// protect applies packet protection to a packet.
// Parameters and returns are as for fixedKeyPair.protect.
func (k *updatingKeyPair) protect(hdr, pay []byte, pnumOff int, pnum packetNumber) []byte {
var pkt []byte
if k.updating {
hdr[0] |= k.phase ^ keyPhaseBit
pkt = k.w.pkt[1].protect(hdr, pay, pnum)
k.minSent = min(pnum, k.minSent)
} else {
hdr[0] |= k.phase
pkt = k.w.pkt[0].protect(hdr, pay, pnum)
if pnum >= k.updateAfter {
// Initiate a key update, starting with the next packet we send.
//
// We do this after protecting the current packet
// to allow Conn.appendFrames to ensure that the first packet sent
// in the new phase is ack-eliciting.
k.updating = true
k.minSent = maxPacketNumber
k.minReceived = maxPacketNumber
// The lowest confidentiality limit for a supported AEAD is 2^23 packets.
// https://www.rfc-editor.org/rfc/rfc9001#section-6.6-5
//
// Schedule our next update for half that.
k.updateAfter += (1 << 22)
}
}
k.w.hdr.protect(pkt, pnumOff)
return pkt
}
// unprotect removes packet protection from a packet.
// Parameters and returns are as for fixedKeyPair.unprotect.
func (k *updatingKeyPair) unprotect(pkt []byte, pnumOff int, pnumMax packetNumber) (pay []byte, pnum packetNumber, err error) {
hdr, pay, pnum, err := k.r.hdr.unprotect(pkt, pnumOff, pnumMax)
if err != nil {
return nil, 0, err
}
// To avoid timing signals that might indicate the key phase bit is invalid,
// we always attempt to unprotect the packet with one key.
//
// If the key phase bit matches and the packet number doesn't come after
// the start of an in-progress update, use the current phase.
// Otherwise, use the next phase.
if hdr[0]&keyPhaseBit == k.phase && (!k.updating || pnum < k.minReceived) {
pay, err = k.r.pkt[0].unprotect(hdr, pay, pnum)
} else {
pay, err = k.r.pkt[1].unprotect(hdr, pay, pnum)
if err == nil {
if !k.updating {
// The peer has initiated a key update.
k.updating = true
k.minSent = maxPacketNumber
k.minReceived = pnum
} else {
k.minReceived = min(pnum, k.minReceived)
}
}
}
if err != nil {
k.authFailures++
if k.authFailures >= aeadIntegrityLimit(k.r.suite) {
return nil, 0, localTransportError{code: errAEADLimitReached}
}
return nil, 0, err
}
return pay, pnum, nil
}
// aeadIntegrityLimit returns the integrity limit for an AEAD:
// The maximum number of received packets that may fail authentication
// before closing the connection.
//
// https://www.rfc-editor.org/rfc/rfc9001#section-6.6-4
func aeadIntegrityLimit(suite uint16) int64 {
switch suite {
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384:
return 1 << 52
case tls.TLS_CHACHA20_POLY1305_SHA256:
return 1 << 36
default:
panic("BUG: unknown cipher suite")
}
}
// https://www.rfc-editor.org/rfc/rfc9001#section-5.2-2
var initialSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
// initialKeys returns the keys used to protect Initial packets.
//
// The Initial packet keys are derived from the Destination Connection ID
// field in the client's first Initial packet.
//
// https://www.rfc-editor.org/rfc/rfc9001#section-5.2
func initialKeys(cid []byte, side connSide) fixedKeyPair {
initialSecret := hkdf.Extract(sha256.New, cid, initialSalt)
var clientKeys fixedKeys
clientSecret := hkdfExpandLabel(sha256.New, initialSecret, "client in", nil, sha256.Size)
clientKeys.init(tls.TLS_AES_128_GCM_SHA256, clientSecret)
var serverKeys fixedKeys
serverSecret := hkdfExpandLabel(sha256.New, initialSecret, "server in", nil, sha256.Size)
serverKeys.init(tls.TLS_AES_128_GCM_SHA256, serverSecret)
if side == clientSide {
return fixedKeyPair{r: serverKeys, w: clientKeys}
} else {
return fixedKeyPair{w: serverKeys, r: clientKeys}
}
}
// checkCipherSuite returns an error if suite is not a supported cipher suite.
func checkCipherSuite(suite uint16) error {
switch suite {
case tls.TLS_AES_128_GCM_SHA256:
case tls.TLS_AES_256_GCM_SHA384:
case tls.TLS_CHACHA20_POLY1305_SHA256:
default:
return errors.New("invalid cipher suite")
}
return nil
}
func hashForSuite(suite uint16) (h crypto.Hash, keySize int) {
switch suite {
case tls.TLS_AES_128_GCM_SHA256:
return crypto.SHA256, 128 / 8
case tls.TLS_AES_256_GCM_SHA384:
return crypto.SHA384, 256 / 8
case tls.TLS_CHACHA20_POLY1305_SHA256:
return crypto.SHA256, chacha20.KeySize
default:
panic("BUG: unknown cipher suite")
}
}
// hdkfExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1.
//
// Copied from crypto/tls/key_schedule.go.
func hkdfExpandLabel(hash func() hash.Hash, secret []byte, label string, context []byte, length int) []byte {
var hkdfLabel cryptobyte.Builder
hkdfLabel.AddUint16(uint16(length))
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes([]byte("tls13 "))
b.AddBytes([]byte(label))
})
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(context)
})
out := make([]byte, length)
n, err := hkdf.Expand(hash, secret, hkdfLabel.BytesOrPanic()).Read(out)
if err != nil || n != length {
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
}
return out
}