1
2
3
4
5
6
7 package quic
8
9 import (
10 "bytes"
11 "crypto/aes"
12 "crypto/cipher"
13 "crypto/rand"
14 "encoding/binary"
15 "net/netip"
16 "time"
17
18 "golang.org/x/crypto/chacha20poly1305"
19 )
20
21
22
23 var (
24 retrySecret = []byte{0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e}
25 retryNonce = []byte{0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb}
26 retryAEAD = func() cipher.AEAD {
27 c, err := aes.NewCipher(retrySecret)
28 if err != nil {
29 panic(err)
30 }
31 aead, err := cipher.NewGCM(c)
32 if err != nil {
33 panic(err)
34 }
35 return aead
36 }()
37 )
38
39
40 const retryTokenValidityPeriod = 5 * time.Second
41
42
43 type retryState struct {
44 aead cipher.AEAD
45 }
46
47 func (rs *retryState) init() error {
48
49
50 secret := make([]byte, chacha20poly1305.KeySize)
51 if _, err := rand.Read(secret); err != nil {
52 return err
53 }
54 aead, err := chacha20poly1305.NewX(secret)
55 if err != nil {
56 panic(err)
57 }
58 rs.aead = aead
59 return nil
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 func (rs *retryState) makeToken(now time.Time, srcConnID, origDstConnID []byte, addr netip.AddrPort) (token, newDstConnID []byte, err error) {
92 nonce := make([]byte, rs.aead.NonceSize())
93 if _, err := rand.Read(nonce); err != nil {
94 return nil, nil, err
95 }
96
97 var plaintext []byte
98 plaintext = binary.BigEndian.AppendUint64(plaintext, uint64(now.Unix()))
99 plaintext = append(plaintext, origDstConnID...)
100
101 token = append(token, nonce[maxConnIDLen:]...)
102 token = rs.aead.Seal(token, nonce, plaintext, rs.additionalData(srcConnID, addr))
103 return token, nonce[:maxConnIDLen], nil
104 }
105
106 func (rs *retryState) validateToken(now time.Time, token, srcConnID, dstConnID []byte, addr netip.AddrPort) (origDstConnID []byte, ok bool) {
107 tokenNonceLen := rs.aead.NonceSize() - maxConnIDLen
108 if len(token) < tokenNonceLen {
109 return nil, false
110 }
111 nonce := append([]byte{}, dstConnID...)
112 nonce = append(nonce, token[:tokenNonceLen]...)
113 ciphertext := token[tokenNonceLen:]
114
115 plaintext, err := rs.aead.Open(nil, nonce, ciphertext, rs.additionalData(srcConnID, addr))
116 if err != nil {
117 return nil, false
118 }
119 if len(plaintext) < 8 {
120 return nil, false
121 }
122 when := time.Unix(int64(binary.BigEndian.Uint64(plaintext)), 0)
123 origDstConnID = plaintext[8:]
124
125
126
127 if d := abs(now.Sub(when)); d > retryTokenValidityPeriod {
128 return nil, false
129 }
130
131 return origDstConnID, true
132 }
133
134 func (rs *retryState) additionalData(srcConnID []byte, addr netip.AddrPort) []byte {
135 var additional []byte
136 additional = appendUint8Bytes(additional, srcConnID)
137 additional = append(additional, addr.Addr().AsSlice()...)
138 additional = binary.BigEndian.AppendUint16(additional, addr.Port())
139 return additional
140 }
141
142 func (e *Endpoint) validateInitialAddress(now time.Time, p genericLongPacket, addr netip.AddrPort) (origDstConnID []byte, ok bool) {
143
144 token, n := consumeUint8Bytes(p.data)
145 if n < 0 {
146
147
148
149 return nil, false
150 }
151 if len(token) == 0 {
152
153
154 e.sendRetry(now, p, addr)
155 return nil, false
156 }
157 origDstConnID, ok = e.retry.validateToken(now, token, p.srcConnID, p.dstConnID, addr)
158 if !ok {
159
160
161
162 e.sendConnectionClose(p, addr, errInvalidToken)
163 return nil, false
164 }
165 return origDstConnID, true
166 }
167
168 func (e *Endpoint) sendRetry(now time.Time, p genericLongPacket, addr netip.AddrPort) {
169 token, srcConnID, err := e.retry.makeToken(now, p.srcConnID, p.dstConnID, addr)
170 if err != nil {
171 return
172 }
173 b := encodeRetryPacket(p.dstConnID, retryPacket{
174 dstConnID: p.srcConnID,
175 srcConnID: srcConnID,
176 token: token,
177 })
178 e.sendDatagram(b, addr)
179 }
180
181 type retryPacket struct {
182 dstConnID []byte
183 srcConnID []byte
184 token []byte
185 }
186
187 func encodeRetryPacket(originalDstConnID []byte, p retryPacket) []byte {
188
189
190
191
192
193
194
195 var b []byte
196 b = appendUint8Bytes(b, originalDstConnID)
197 start := len(b)
198 b = append(b, headerFormLong|fixedBit|longPacketTypeRetry)
199 b = binary.BigEndian.AppendUint32(b, quicVersion1)
200 b = appendUint8Bytes(b, p.dstConnID)
201 b = appendUint8Bytes(b, p.srcConnID)
202 b = append(b, p.token...)
203 b = retryAEAD.Seal(b, retryNonce, nil, b)
204 return b[start:]
205 }
206
207 func parseRetryPacket(b, origDstConnID []byte) (p retryPacket, ok bool) {
208 const retryIntegrityTagLength = 128 / 8
209
210 lp, ok := parseGenericLongHeaderPacket(b)
211 if !ok {
212 return retryPacket{}, false
213 }
214 if len(lp.data) < retryIntegrityTagLength {
215 return retryPacket{}, false
216 }
217 gotTag := lp.data[len(lp.data)-retryIntegrityTagLength:]
218
219
220
221
222 pseudo := appendUint8Bytes(nil, origDstConnID)
223 pseudo = append(pseudo, b[:len(b)-retryIntegrityTagLength]...)
224 wantTag := retryAEAD.Seal(nil, retryNonce, nil, pseudo)
225 if !bytes.Equal(gotTag, wantTag) {
226 return retryPacket{}, false
227 }
228
229 token := lp.data[:len(lp.data)-retryIntegrityTagLength]
230 return retryPacket{
231 dstConnID: lp.dstConnID,
232 srcConnID: lp.srcConnID,
233 token: token,
234 }, true
235 }
236
View as plain text