ALL

Solo para leer y verificar. No puedes copiar, usar, modificar ni redistribuir este código. Aviso completo en la página principal.

tunnel/sni_test.go

SHA-256: 8881562f3b74f552c82ea3ce7f8b5f3127a805ba2e288223c1add619c393455c
412 líneas · 15759 bytes · Ver en GitHub

1package main
2
3import (
4 "bytes"
5 "crypto/tls"
6 "errors"
7 "io"
8 mrand "math/rand"
9 "net"
10 "os"
11 "strings"
12 "testing"
13 "testing/iotest"
14 "time"
15)
16
17// captureClientHello returns the first TLS record a real crypto/tls client
18// sends (its ClientHello), exactly as it appears on the wire.
19func captureClientHello(t testing.TB, cfg *tls.Config) []byte {
20 t.Helper()
21 c1, c2 := net.Pipe()
22 defer c1.Close()
23 defer c2.Close()
24 go func() { _ = tls.Client(c1, cfg).Handshake() }()
25 _ = c2.SetReadDeadline(time.Now().Add(5 * time.Second))
26 hdr := make([]byte, 5)
27 if _, err := io.ReadFull(c2, hdr); err != nil {
28 t.Fatal(err)
29 }
30 rec := make([]byte, int(hdr[3])<<8|int(hdr[4]))
31 if _, err := io.ReadFull(c2, rec); err != nil {
32 t.Fatal(err)
33 }
34 return append(hdr, rec...)
35}
36
37func realHello(t testing.TB, serverName string) []byte {
38 return captureClientHello(t, &tls.Config{ServerName: serverName, InsecureSkipVerify: true, NextProtos: []string{"http/1.1"}})
39}
40
41// refragment rewraps the handshake payload of a single-record ClientHello
42// into records of at most n payload bytes each.
43func refragment(hello []byte, n int) []byte {
44 payload := hello[5:]
45 var out []byte
46 for len(payload) > 0 {
47 k := min(n, len(payload))
48 out = append(out, 22, 3, 3, byte(k>>8), byte(k))
49 out = append(out, payload[:k]...)
50 payload = payload[k:]
51 }
52 return out
53}
54
55func TestScanSNI_RealClientHellos(t *testing.T) {
56 cases := []struct{ in, want string }{
57 {"dev1.example.test", "dev1.example.test"},
58 {"Dev1.Example.TEST", "dev1.example.test"}, // normalized
59 {"nodo-abc123.example.test", "nodo-abc123.example.test"},
60 {"", ""}, // no SNI at all
61 {"127.0.0.1", ""}, // Go omits SNI for IP literals
62 }
63 for _, tc := range cases {
64 t.Run(tc.in, func(t *testing.T) {
65 res := scanClientHello(realHello(t, tc.in))
66 if res.err != nil || !res.complete || res.sni != tc.want {
67 t.Fatalf("got %+v, want sni %q", res, tc.want)
68 }
69 })
70 }
71}
72
73// Modern clients (Chrome, Go 1.24+, OpenSSL 3.5) offer the X25519MLKEM768
74// hybrid key share, which makes the ClientHello well over a kilobyte.
75func TestScanSNI_LargePostQuantumClientHello(t *testing.T) {
76 hello := captureClientHello(t, &tls.Config{
77 ServerName: "dev1.example.test", InsecureSkipVerify: true,
78 CurvePreferences: []tls.CurveID{tls.X25519MLKEM768, tls.X25519},
79 })
80 if len(hello) < 1200 {
81 t.Fatalf("expected a large (ML-KEM) hello, got %d bytes", len(hello))
82 }
83 if res := scanClientHello(hello); res.err != nil || !res.complete || res.sni != "dev1.example.test" {
84 t.Fatalf("%+v", res)
85 }
86 // ... split in half across two records, and arriving a few bytes at a time.
87 if res := scanClientHello(refragment(hello, len(hello)/2)); res.err != nil || res.sni != "dev1.example.test" {
88 t.Fatalf("two records: %+v", res)
89 }
90 peeked, sni, err := readClientHello(byteConn{r: iotest.OneByteReader(bytes.NewReader(hello))}, time.Second)
91 if err != nil || sni != "dev1.example.test" || !bytes.Equal(peeked, hello) {
92 t.Fatalf("one-byte reads: sni=%q err=%v", sni, err)
93 }
94}
95
96// A padded hello close to the 16 KiB record limit (RFC 7685 padding, ECH).
97func TestScanSNI_HugePaddedHello(t *testing.T) {
98 pad := make([]byte, 14000)
99 ext := append([]byte{0, 21, byte(len(pad) >> 8), byte(len(pad))}, pad...) // padding extension
100 ext = append(ext, sniExt("dev1.example.test")...)
101 hello := buildHello(ext, true)
102 if len(hello) < 14000 || len(hello) > maxRecordPayload {
103 t.Fatalf("test hello is %d bytes", len(hello))
104 }
105 peeked, sni, err := readClientHello(byteConn{r: bytes.NewReader(hello)}, time.Second)
106 if err != nil || sni != "dev1.example.test" || !bytes.Equal(peeked, hello) {
107 t.Fatalf("sni=%q err=%v", sni, err)
108 }
109}
110
111func TestScanSNI_EveryPrefixNeedsMore(t *testing.T) {
112 hello := realHello(t, "dev1.example.test")
113 for n := 0; n < len(hello); n++ {
114 res := scanClientHello(hello[:n])
115 if res.err != nil || res.complete || res.need <= n {
116 t.Fatalf("prefix %d/%d: got %+v", n, len(hello), res)
117 }
118 }
119 if res := scanClientHello(hello); !res.complete || res.sni != "dev1.example.test" {
120 t.Fatalf("full: %+v", res)
121 }
122}
123
124func TestScanSNI_HelloSplitAcrossRecords(t *testing.T) {
125 hello := realHello(t, "dev1.example.test")
126 payload := len(hello) - 5
127 for _, n := range []int{payload/maxHelloRecords + 1, 100, 300, 1000, payload} {
128 frag := refragment(hello, n)
129 res := scanClientHello(frag)
130 if res.err != nil || !res.complete || res.sni != "dev1.example.test" {
131 t.Fatalf("records of %d bytes: %+v", n, res)
132 }
133 // no decision before the last byte
134 for i := 0; i < len(frag); i++ {
135 if r := scanClientHello(frag[:i]); r.complete || r.err != nil {
136 t.Fatalf("records of %d bytes: decided early at %d/%d: %+v", n, i, len(frag), r)
137 }
138 }
139 }
140 // Absurdly fragmented hellos (more records than the cap) are refused.
141 if res := scanClientHello(refragment(hello, 1)); !errors.Is(res.err, errHelloMalformed) {
142 t.Fatalf("1-byte records: %+v", res)
143 }
144}
145
146func TestScanSNI_TrailingBytesAreIgnored(t *testing.T) {
147 hello := realHello(t, "dev1.example.test")
148 res := scanClientHello(append(append([]byte(nil), hello...), []byte("\x17\x03\x03\x00\x05trail")...))
149 if res.err != nil || !res.complete || res.sni != "dev1.example.test" {
150 t.Fatalf("%+v", res)
151 }
152}
153
154// buildHello assembles a ClientHello record from parts, for hostile cases.
155func buildHello(extensions []byte, withExt bool) []byte {
156 var body []byte
157 body = append(body, 3, 3) // legacy_version
158 body = append(body, make([]byte, 32)...) // random
159 body = append(body, 0) // session id len
160 body = append(body, 0, 2, 0x13, 0x01) // one cipher suite
161 body = append(body, 1, 0) // compression
162 if withExt {
163 body = append(body, byte(len(extensions)>>8), byte(len(extensions)))
164 body = append(body, extensions...)
165 }
166 hs := append([]byte{1, byte(len(body) >> 16), byte(len(body) >> 8), byte(len(body))}, body...)
167 return append([]byte{22, 3, 1, byte(len(hs) >> 8), byte(len(hs))}, hs...)
168}
169
170func sniExt(names ...string) []byte {
171 var list []byte
172 for _, n := range names {
173 list = append(list, 0, byte(len(n)>>8), byte(len(n)))
174 list = append(list, n...)
175 }
176 data := append([]byte{byte(len(list) >> 8), byte(len(list))}, list...)
177 return append([]byte{0, 0, byte(len(data) >> 8), byte(len(data))}, data...)
178}
179
180func TestScanSNI_HandBuiltHellos(t *testing.T) {
181 good := scanClientHello(buildHello(sniExt("a.example.test"), true))
182 if good.err != nil || good.sni != "a.example.test" {
183 t.Fatalf("baseline: %+v", good)
184 }
185 noExt := scanClientHello(buildHello(nil, false))
186 if noExt.err != nil || !noExt.complete || noExt.sni != "" {
187 t.Fatalf("no extensions: %+v", noExt)
188 }
189 otherExtOnly := scanClientHello(buildHello([]byte{0, 43, 0, 3, 2, 3, 4}, true))
190 if otherExtOnly.err != nil || otherExtOnly.sni != "" {
191 t.Fatalf("other extension only: %+v", otherExtOnly)
192 }
193}
194
195func TestScanSNI_Rejects(t *testing.T) {
196 dupExt := append(sniExt("a.example.test"), sniExt("b.example.test")...)
197 rec := func(typ byte, ver byte, payload []byte) []byte {
198 return append([]byte{typ, 3, ver, byte(len(payload) >> 8), byte(len(payload))}, payload...)
199 }
200 var tooManyRecords []byte
201 for _, b := range append([]byte{1, 0, 0, 200}, make([]byte, 40)...) {
202 tooManyRecords = append(tooManyRecords, rec(22, 1, []byte{b})...)
203 }
204 cases := []struct {
205 name string
206 in []byte
207 want error
208 }{
209 {"http on 443", []byte("GET / HTTP/1.1\r\nHost: x\r\n\r\n"), errNotTLS},
210 {"ssl2-style", []byte{0x80, 0x2e, 0x01, 0x00, 0x02}, errNotTLS},
211 {"application data record", rec(23, 3, []byte{1, 2, 3}), errNotTLS},
212 {"bad record major version", append([]byte{22, 7, 3, 0, 4}, 1, 0, 0, 0), errNotTLS},
213 {"zero length record", []byte{22, 3, 1, 0, 0}, errHelloMalformed},
214 {"oversize record", []byte{22, 3, 1, 0xff, 0xff}, errHelloMalformed},
215 {"server hello", rec(22, 3, []byte{2, 0, 0, 0}), errNotClientHello},
216 {"hello longer than cap", rec(22, 3, []byte{1, 0xff, 0xff, 0xff}), errHelloTooLarge},
217 {"body shorter than its fields", rec(22, 3, []byte{1, 0, 0, 5, 3, 3, 0, 0, 0}), errHelloMalformed},
218 {"duplicate server_name extension", buildHello(dupExt, true), errHelloMalformed},
219 {"two host_names in one extension", buildHello(sniExt("a.example.test", "b.example.test"), true), errHelloMalformed},
220 {"space in name", buildHello(sniExt("a b.example.test"), true), errBadSNI},
221 {"NUL in name", buildHello(sniExt("a\x00b.example.test"), true), errBadSNI},
222 {"slash in name", buildHello(sniExt("a/b.example.test"), true), errBadSNI},
223 {"empty name", buildHello(sniExt(""), true), errBadSNI},
224 {"ip literal name", buildHello(sniExt("192.0.2.1"), true), errBadSNI},
225 {"label over 63", buildHello(sniExt(strings.Repeat("a", 64)+".example.test"), true), errBadSNI},
226 {"empty label", buildHello(sniExt("a..example.test"), true), errBadSNI},
227 {"leading dot", buildHello(sniExt(".example.test"), true), errBadSNI},
228 {"name over 253", buildHello(sniExt(strings.Repeat("abcdefgh.", 30)+"test"), true), errBadSNI},
229 {"extension length overruns", buildHello([]byte{0, 0, 0xff, 0xff, 1}, true), errHelloMalformed},
230 {"too many records", tooManyRecords, errHelloMalformed},
231 }
232 for _, tc := range cases {
233 t.Run(tc.name, func(t *testing.T) {
234 res := scanClientHello(tc.in)
235 if !errors.Is(res.err, tc.want) {
236 t.Fatalf("got %+v, want error %v", res, tc.want)
237 }
238 })
239 }
240}
241
242// Mutating a valid hello in every way must never panic or run out of bounds.
243func TestScanSNI_NeverPanicsOnMutations(t *testing.T) {
244 hello := realHello(t, "dev1.example.test")
245 rng := mrand.New(mrand.NewSource(1))
246 for i := 0; i < 30000; i++ {
247 b := append([]byte(nil), hello...)
248 for k := rng.Intn(4); k >= 0; k-- {
249 b[rng.Intn(len(b))] = byte(rng.Intn(256))
250 }
251 if rng.Intn(3) == 0 {
252 b = b[:rng.Intn(len(b)+1)]
253 }
254 res := scanClientHello(b)
255 if res.complete && res.err == nil && res.sni != "" {
256 if n, ok := normalizeName(res.sni); !ok || n != res.sni {
257 t.Fatalf("returned an unnormalized name %q", res.sni)
258 }
259 }
260 }
261}
262
263func FuzzScanClientHello(f *testing.F) {
264 f.Add(realHello(f, "dev1.example.test"))
265 f.Add(realHello(f, ""))
266 f.Add(buildHello(sniExt("a.example.test"), true))
267 f.Add([]byte("GET / HTTP/1.1\r\n\r\n"))
268 f.Fuzz(func(t *testing.T, data []byte) {
269 res := scanClientHello(data)
270 if res.complete && res.err == nil && res.sni != "" {
271 if n, ok := normalizeName(res.sni); !ok || n != res.sni {
272 t.Fatalf("returned an unnormalized name %q", res.sni)
273 }
274 }
275 if !res.complete && res.err == nil && res.need <= len(data) {
276 t.Fatalf("need %d not beyond len %d", res.need, len(data))
277 }
278 })
279}
280
281// byteConn is a net.Conn that serves a reader (for readClientHello tests).
282type byteConn struct {
283 net.Conn
284 r io.Reader
285}
286
287func (b byteConn) Read(p []byte) (int, error) { return b.r.Read(p) }
288func (b byteConn) SetReadDeadline(time.Time) error { return nil }
289
290func TestReadClientHello_OneByteReads(t *testing.T) {
291 hello := realHello(t, "dev1.example.test")
292 trail := []byte("EXTRA")
293 in := append(append([]byte(nil), hello...), trail...)
294 peeked, sni, err := readClientHello(byteConn{r: iotest.OneByteReader(bytes.NewReader(in))}, time.Second)
295 if err != nil || sni != "dev1.example.test" {
296 t.Fatalf("sni=%q err=%v", sni, err)
297 }
298 if !bytes.Equal(peeked, hello) { // one-byte reads cannot over-read past the hello
299 t.Fatalf("peeked %d bytes, want exactly the %d-byte hello", len(peeked), len(hello))
300 }
301}
302
303func TestReadClientHello_ReturnsEveryByteRead(t *testing.T) {
304 hello := realHello(t, "dev1.example.test")
305 in := append(append([]byte(nil), hello...), []byte("COALESCED-AFTER-HELLO")...)
306 peeked, sni, err := readClientHello(byteConn{r: bytes.NewReader(in)}, time.Second)
307 if err != nil || sni != "dev1.example.test" {
308 t.Fatalf("sni=%q err=%v", sni, err)
309 }
310 if !bytes.Equal(peeked, in) { // everything read must be replayed to the device
311 t.Fatalf("peeked %d bytes, want all %d", len(peeked), len(in))
312 }
313}
314
315func TestReadClientHello_SplitReadsOverPipe(t *testing.T) {
316 hello := realHello(t, "dev1.example.test")
317 c1, c2 := net.Pipe()
318 defer c1.Close()
319 defer c2.Close()
320 go func() {
321 for i := 0; i < len(hello); i += 3 {
322 _, _ = c1.Write(hello[i:min(i+3, len(hello))])
323 time.Sleep(200 * time.Microsecond)
324 }
325 }()
326 peeked, sni, err := readClientHello(c2, 5*time.Second)
327 if err != nil || sni != "dev1.example.test" || !bytes.Equal(peeked, hello) {
328 t.Fatalf("sni=%q err=%v peeked=%d/%d", sni, err, len(peeked), len(hello))
329 }
330}
331
332func TestReadClientHello_MissingSNIIsNotAnError(t *testing.T) {
333 peeked, sni, err := readClientHello(byteConn{r: bytes.NewReader(realHello(t, ""))}, time.Second)
334 if err != nil || sni != "" || len(peeked) == 0 {
335 t.Fatalf("sni=%q err=%v", sni, err)
336 }
337}
338
339func TestReadClientHello_FailureModes(t *testing.T) {
340 t.Run("timeout", func(t *testing.T) {
341 c1, c2 := net.Pipe()
342 defer c1.Close()
343 defer c2.Close()
344 _, _, err := readClientHello(c2, 100*time.Millisecond)
345 if !errors.Is(err, os.ErrDeadlineExceeded) {
346 t.Fatalf("got %v", err)
347 }
348 })
349 t.Run("eof before hello", func(t *testing.T) {
350 hello := realHello(t, "x.example.test")
351 _, _, err := readClientHello(byteConn{r: bytes.NewReader(hello[:20])}, time.Second)
352 if !errors.Is(err, io.EOF) {
353 t.Fatalf("got %v", err)
354 }
355 })
356 t.Run("not tls fails fast", func(t *testing.T) {
357 _, _, err := readClientHello(byteConn{r: strings.NewReader("GET / HTTP/1.1\r\n")}, time.Second)
358 if !errors.Is(err, errNotTLS) {
359 t.Fatalf("got %v", err)
360 }
361 })
362 t.Run("stream of records beyond the cap", func(t *testing.T) {
363 endless := io.MultiReader(bytes.NewReader([]byte{22, 3, 1, 0x3f, 0xff, 1, 0, 0xff, 0xff}), iotest.ErrReader(io.ErrUnexpectedEOF))
364 _, _, err := readClientHello(byteConn{r: endless}, time.Second)
365 if err == nil {
366 t.Fatal("expected an error")
367 }
368 })
369}
370
371func TestNormalizeName(t *testing.T) {
372 ok := map[string]string{
373 "Example.COM": "example.com", "a.b.c.": "a.b.c",
374 "nodo-abc123.example.test": "nodo-abc123.example.test",
375 "localhost": "localhost", "psrhz6n5wpzbm6yfkngo.devices.all-e2e.test": "psrhz6n5wpzbm6yfkngo.devices.all-e2e.test",
376 "PSRHZ6N5WPZBM6YFKNGO.Devices.ALL-E2E.test.": "psrhz6n5wpzbm6yfkngo.devices.all-e2e.test",
377 "xn--bcher-kva.example": "xn--bcher-kva.example",
378 }
379 for in, want := range ok {
380 if got, good := normalizeName(in); !good || got != want {
381 t.Errorf("%q -> %q,%v want %q", in, got, good, want)
382 }
383 }
384 long63, long64 := strings.Repeat("a", 63), strings.Repeat("a", 64)
385 for _, in := range []string{
386 "", ".", "..", "a..b", "a.b..", ".a.b", "a b", "a/b", "::1", "127.0.0.1", "[::1]", "é.example.test", "a\x00b", "a\tb", "a\nb", "a.b\n",
387 "x_y.example.test", "_acme-challenge.example.test", "-a.example.test", "a-.example.test", "a.-b.test", "a.b-.test", "a*.example.test",
388 "192.0.2.4", "1.2.3", "0x7f.1", "a.123", "999", long64 + ".test", strings.Repeat(long63+".", 4) + "test", "a.b.c:443", "a" + "@b.test", "a b.test", "a.test",
389 } {
390 if got, good := normalizeName(in); good {
391 t.Errorf("%q should be rejected, got %q", in, got)
392 }
393 }
394 if _, good := normalizeName(long63 + ".test"); !good {
395 t.Error("a 63-byte label is valid")
396 }
397}
398
399func TestScanSNI_BytesAfterTheExtensionsAreAHelloThatLiesAboutItsLength(t *testing.T) {
400 hello := buildHello(sniExt("a.example.test"), true)
401 // grow the handshake message by two bytes that no field accounts for
402 lying := append([]byte(nil), hello...)
403 lying = append(lying, 0xde, 0xad)
404 hsLen := int(lying[6])<<16 | int(lying[7])<<8 | int(lying[8])
405 hsLen += 2
406 lying[6], lying[7], lying[8] = byte(hsLen>>16), byte(hsLen>>8), byte(hsLen)
407 recLen := int(lying[3])<<8 | int(lying[4]) + 2
408 lying[3], lying[4] = byte(recLen>>8), byte(recLen)
409 if res := scanClientHello(lying); res.err == nil {
410 t.Fatalf("accepted: %+v", res)
411 }
412}