Source file
src/crypto/tls/bogo_shim_test.go
1
2
3
4
5 package tls
6
7 import (
8 "bytes"
9 "crypto/internal/cryptotest"
10 "crypto/x509"
11 "encoding/base64"
12 "encoding/json"
13 "encoding/pem"
14 "flag"
15 "fmt"
16 "internal/byteorder"
17 "internal/testenv"
18 "io"
19 "log"
20 "net"
21 "os"
22 "path/filepath"
23 "runtime"
24 "slices"
25 "strconv"
26 "strings"
27 "testing"
28
29 "golang.org/x/crypto/cryptobyte"
30 )
31
32 var (
33 port = flag.String("port", "", "")
34 server = flag.Bool("server", false, "")
35
36 isHandshakerSupported = flag.Bool("is-handshaker-supported", false, "")
37
38 keyfile = flag.String("key-file", "", "")
39 certfile = flag.String("cert-file", "", "")
40
41 trustCert = flag.String("trust-cert", "", "")
42
43 minVersion = flag.Int("min-version", VersionSSL30, "")
44 maxVersion = flag.Int("max-version", VersionTLS13, "")
45 expectVersion = flag.Int("expect-version", 0, "")
46
47 noTLS1 = flag.Bool("no-tls1", false, "")
48 noTLS11 = flag.Bool("no-tls11", false, "")
49 noTLS12 = flag.Bool("no-tls12", false, "")
50 noTLS13 = flag.Bool("no-tls13", false, "")
51
52 requireAnyClientCertificate = flag.Bool("require-any-client-certificate", false, "")
53
54 shimWritesFirst = flag.Bool("shim-writes-first", false, "")
55
56 resumeCount = flag.Int("resume-count", 0, "")
57
58 curves = flagStringSlice("curves", "")
59 expectedCurve = flag.String("expect-curve-id", "", "")
60
61 shimID = flag.Uint64("shim-id", 0, "")
62 _ = flag.Bool("ipv6", false, "")
63
64 echConfigListB64 = flag.String("ech-config-list", "", "")
65 expectECHAccepted = flag.Bool("expect-ech-accept", false, "")
66 expectHRR = flag.Bool("expect-hrr", false, "")
67 expectNoHRR = flag.Bool("expect-no-hrr", false, "")
68 expectedECHRetryConfigs = flag.String("expect-ech-retry-configs", "", "")
69 expectNoECHRetryConfigs = flag.Bool("expect-no-ech-retry-configs", false, "")
70 onInitialExpectECHAccepted = flag.Bool("on-initial-expect-ech-accept", false, "")
71 _ = flag.Bool("expect-no-ech-name-override", false, "")
72 _ = flag.String("expect-ech-name-override", "", "")
73 _ = flag.Bool("reverify-on-resume", false, "")
74 onResumeECHConfigListB64 = flag.String("on-resume-ech-config-list", "", "")
75 _ = flag.Bool("on-resume-expect-reject-early-data", false, "")
76 onResumeExpectECHAccepted = flag.Bool("on-resume-expect-ech-accept", false, "")
77 _ = flag.Bool("on-resume-expect-no-ech-name-override", false, "")
78 expectedServerName = flag.String("expect-server-name", "", "")
79 echServerConfig = flagStringSlice("ech-server-config", "")
80 echServerKey = flagStringSlice("ech-server-key", "")
81 echServerRetryConfig = flagStringSlice("ech-is-retry-config", "")
82
83 expectSessionMiss = flag.Bool("expect-session-miss", false, "")
84
85 _ = flag.Bool("enable-early-data", false, "")
86 _ = flag.Bool("on-resume-expect-accept-early-data", false, "")
87 _ = flag.Bool("expect-ticket-supports-early-data", false, "")
88 onResumeShimWritesFirst = flag.Bool("on-resume-shim-writes-first", false, "")
89
90 advertiseALPN = flag.String("advertise-alpn", "", "")
91 expectALPN = flag.String("expect-alpn", "", "")
92 rejectALPN = flag.Bool("reject-alpn", false, "")
93 declineALPN = flag.Bool("decline-alpn", false, "")
94 expectAdvertisedALPN = flag.String("expect-advertised-alpn", "", "")
95 selectALPN = flag.String("select-alpn", "", "")
96
97 hostName = flag.String("host-name", "", "")
98
99 verifyPeer = flag.Bool("verify-peer", false, "")
100 _ = flag.Bool("use-custom-verify-callback", false, "")
101 )
102
103 type stringSlice []string
104
105 func flagStringSlice(name, usage string) *stringSlice {
106 f := &stringSlice{}
107 flag.Var(f, name, usage)
108 return f
109 }
110
111 func (saf *stringSlice) String() string {
112 return strings.Join(*saf, ",")
113 }
114
115 func (saf *stringSlice) Set(s string) error {
116 *saf = append(*saf, s)
117 return nil
118 }
119
120 func bogoShim() {
121 if *isHandshakerSupported {
122 fmt.Println("No")
123 return
124 }
125
126 cfg := &Config{
127 ServerName: "test",
128
129 MinVersion: uint16(*minVersion),
130 MaxVersion: uint16(*maxVersion),
131
132 ClientSessionCache: NewLRUClientSessionCache(0),
133
134 GetConfigForClient: func(chi *ClientHelloInfo) (*Config, error) {
135
136 if *expectAdvertisedALPN != "" {
137
138 s := cryptobyte.String(*expectAdvertisedALPN)
139
140 var expectedALPNs []string
141
142 for !s.Empty() {
143 var alpn cryptobyte.String
144 if !s.ReadUint8LengthPrefixed(&alpn) {
145 return nil, fmt.Errorf("unexpected error while parsing arguments for -expect-advertised-alpn")
146 }
147 expectedALPNs = append(expectedALPNs, string(alpn))
148 }
149
150 if !slices.Equal(chi.SupportedProtos, expectedALPNs) {
151 return nil, fmt.Errorf("unexpected ALPN: got %q, want %q", chi.SupportedProtos, expectedALPNs)
152 }
153 }
154 return nil, nil
155 },
156 }
157
158 if *noTLS1 {
159 cfg.MinVersion = VersionTLS11
160 if *noTLS11 {
161 cfg.MinVersion = VersionTLS12
162 if *noTLS12 {
163 cfg.MinVersion = VersionTLS13
164 if *noTLS13 {
165 log.Fatalf("no supported versions enabled")
166 }
167 }
168 }
169 } else if *noTLS13 {
170 cfg.MaxVersion = VersionTLS12
171 if *noTLS12 {
172 cfg.MaxVersion = VersionTLS11
173 if *noTLS11 {
174 cfg.MaxVersion = VersionTLS10
175 if *noTLS1 {
176 log.Fatalf("no supported versions enabled")
177 }
178 }
179 }
180 }
181
182 if *advertiseALPN != "" {
183 alpns := *advertiseALPN
184 for len(alpns) > 0 {
185 alpnLen := int(alpns[0])
186 cfg.NextProtos = append(cfg.NextProtos, alpns[1:1+alpnLen])
187 alpns = alpns[alpnLen+1:]
188 }
189 }
190
191 if *rejectALPN {
192 cfg.NextProtos = []string{"unnegotiableprotocol"}
193 }
194
195 if *declineALPN {
196 cfg.NextProtos = []string{}
197 }
198 if *selectALPN != "" {
199 cfg.NextProtos = []string{*selectALPN}
200 }
201
202 if *hostName != "" {
203 cfg.ServerName = *hostName
204 }
205
206 if *keyfile != "" || *certfile != "" {
207 pair, err := LoadX509KeyPair(*certfile, *keyfile)
208 if err != nil {
209 log.Fatalf("load key-file err: %s", err)
210 }
211 cfg.Certificates = []Certificate{pair}
212 }
213 if *trustCert != "" {
214 pool := x509.NewCertPool()
215 certFile, err := os.ReadFile(*trustCert)
216 if err != nil {
217 log.Fatalf("load trust-cert err: %s", err)
218 }
219 block, _ := pem.Decode(certFile)
220 cert, err := x509.ParseCertificate(block.Bytes)
221 if err != nil {
222 log.Fatalf("parse trust-cert err: %s", err)
223 }
224 pool.AddCert(cert)
225 cfg.RootCAs = pool
226 }
227
228 if *requireAnyClientCertificate {
229 cfg.ClientAuth = RequireAnyClientCert
230 }
231 if *verifyPeer {
232 cfg.ClientAuth = VerifyClientCertIfGiven
233 }
234
235 if *echConfigListB64 != "" {
236 echConfigList, err := base64.StdEncoding.DecodeString(*echConfigListB64)
237 if err != nil {
238 log.Fatalf("parse ech-config-list err: %s", err)
239 }
240 cfg.EncryptedClientHelloConfigList = echConfigList
241 cfg.MinVersion = VersionTLS13
242 }
243
244 if len(*curves) != 0 {
245 for _, curveStr := range *curves {
246 id, err := strconv.Atoi(curveStr)
247 if err != nil {
248 log.Fatalf("failed to parse curve id %q: %s", curveStr, err)
249 }
250 cfg.CurvePreferences = append(cfg.CurvePreferences, CurveID(id))
251 }
252 }
253
254 if len(*echServerConfig) != 0 {
255 if len(*echServerConfig) != len(*echServerKey) || len(*echServerConfig) != len(*echServerRetryConfig) {
256 log.Fatal("-ech-server-config, -ech-server-key, and -ech-is-retry-config mismatch")
257 }
258
259 for i, c := range *echServerConfig {
260 configBytes, err := base64.StdEncoding.DecodeString(c)
261 if err != nil {
262 log.Fatalf("parse ech-server-config err: %s", err)
263 }
264 privBytes, err := base64.StdEncoding.DecodeString((*echServerKey)[i])
265 if err != nil {
266 log.Fatalf("parse ech-server-key err: %s", err)
267 }
268
269 cfg.EncryptedClientHelloKeys = append(cfg.EncryptedClientHelloKeys, EncryptedClientHelloKey{
270 Config: configBytes,
271 PrivateKey: privBytes,
272 SendAsRetry: (*echServerRetryConfig)[i] == "1",
273 })
274 }
275 }
276
277 for i := 0; i < *resumeCount+1; i++ {
278 if i > 0 && (*onResumeECHConfigListB64 != "") {
279 echConfigList, err := base64.StdEncoding.DecodeString(*onResumeECHConfigListB64)
280 if err != nil {
281 log.Fatalf("parse ech-config-list err: %s", err)
282 }
283 cfg.EncryptedClientHelloConfigList = echConfigList
284 }
285
286 conn, err := net.Dial("tcp", net.JoinHostPort("localhost", *port))
287 if err != nil {
288 log.Fatalf("dial err: %s", err)
289 }
290 defer conn.Close()
291
292
293 shimIDBytes := make([]byte, 8)
294 byteorder.LEPutUint64(shimIDBytes, *shimID)
295 if _, err := conn.Write(shimIDBytes); err != nil {
296 log.Fatalf("failed to write shim id: %s", err)
297 }
298
299 var tlsConn *Conn
300 if *server {
301 tlsConn = Server(conn, cfg)
302 } else {
303 tlsConn = Client(conn, cfg)
304 }
305
306 if i == 0 && *shimWritesFirst {
307 if _, err := tlsConn.Write([]byte("hello")); err != nil {
308 log.Fatalf("write err: %s", err)
309 }
310 }
311
312 for {
313 buf := make([]byte, 500)
314 var n int
315 n, err = tlsConn.Read(buf)
316 if err != nil {
317 break
318 }
319 buf = buf[:n]
320 for i := range buf {
321 buf[i] ^= 0xff
322 }
323 if _, err = tlsConn.Write(buf); err != nil {
324 break
325 }
326 }
327 if err != nil && err != io.EOF {
328 retryErr, ok := err.(*ECHRejectionError)
329 if !ok {
330 log.Fatalf("unexpected error type returned: %v", err)
331 }
332 if *expectNoECHRetryConfigs && len(retryErr.RetryConfigList) > 0 {
333 log.Fatalf("expected no ECH retry configs, got some")
334 }
335 if *expectedECHRetryConfigs != "" {
336 expectedRetryConfigs, err := base64.StdEncoding.DecodeString(*expectedECHRetryConfigs)
337 if err != nil {
338 log.Fatalf("failed to decode expected retry configs: %s", err)
339 }
340 if !bytes.Equal(retryErr.RetryConfigList, expectedRetryConfigs) {
341 log.Fatalf("unexpected retry list returned: got %x, want %x", retryErr.RetryConfigList, expectedRetryConfigs)
342 }
343 }
344 log.Fatalf("conn error: %s", err)
345 }
346
347 cs := tlsConn.ConnectionState()
348 if cs.HandshakeComplete {
349 if *expectALPN != "" && cs.NegotiatedProtocol != *expectALPN {
350 log.Fatalf("unexpected protocol negotiated: want %q, got %q", *expectALPN, cs.NegotiatedProtocol)
351 }
352
353 if *selectALPN != "" && cs.NegotiatedProtocol != *selectALPN {
354 log.Fatalf("unexpected protocol negotiated: want %q, got %q", *selectALPN, cs.NegotiatedProtocol)
355 }
356
357 if *expectVersion != 0 && cs.Version != uint16(*expectVersion) {
358 log.Fatalf("expected ssl version %q, got %q", uint16(*expectVersion), cs.Version)
359 }
360 if *declineALPN && cs.NegotiatedProtocol != "" {
361 log.Fatal("unexpected ALPN protocol")
362 }
363 if *expectECHAccepted && !cs.ECHAccepted {
364 log.Fatal("expected ECH to be accepted, but connection state shows it was not")
365 } else if i == 0 && *onInitialExpectECHAccepted && !cs.ECHAccepted {
366 log.Fatal("expected ECH to be accepted, but connection state shows it was not")
367 } else if i > 0 && *onResumeExpectECHAccepted && !cs.ECHAccepted {
368 log.Fatal("expected ECH to be accepted on resumption, but connection state shows it was not")
369 } else if i == 0 && !*expectECHAccepted && cs.ECHAccepted {
370 log.Fatal("did not expect ECH, but it was accepted")
371 }
372
373 if *expectHRR && !cs.testingOnlyDidHRR {
374 log.Fatal("expected HRR but did not do it")
375 }
376
377 if *expectNoHRR && cs.testingOnlyDidHRR {
378 log.Fatal("expected no HRR but did do it")
379 }
380
381 if *expectSessionMiss && cs.DidResume {
382 log.Fatal("unexpected session resumption")
383 }
384
385 if *expectedServerName != "" && cs.ServerName != *expectedServerName {
386 log.Fatalf("unexpected server name: got %q, want %q", cs.ServerName, *expectedServerName)
387 }
388 }
389
390 if *expectedCurve != "" {
391 expectedCurveID, err := strconv.Atoi(*expectedCurve)
392 if err != nil {
393 log.Fatalf("failed to parse -expect-curve-id: %s", err)
394 }
395 if tlsConn.curveID != CurveID(expectedCurveID) {
396 log.Fatalf("unexpected curve id: want %d, got %d", expectedCurveID, tlsConn.curveID)
397 }
398 }
399 }
400 }
401
402 func TestBogoSuite(t *testing.T) {
403 if testing.Short() {
404 t.Skip("skipping in short mode")
405 }
406 if testenv.Builder() != "" && runtime.GOOS == "windows" {
407 t.Skip("#66913: windows network connections are flakey on builders")
408 }
409 skipFIPS(t)
410
411
412
413
414
415 if _, err := os.Stat("bogo_config.json"); err != nil {
416 t.Fatal(err)
417 }
418
419 var bogoDir string
420 if *bogoLocalDir != "" {
421 bogoDir = *bogoLocalDir
422 } else {
423 const boringsslModVer = "v0.0.0-20241120195446-5cce3fbd23e1"
424 bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
425 }
426
427 cwd, err := os.Getwd()
428 if err != nil {
429 t.Fatal(err)
430 }
431
432 resultsFile := filepath.Join(t.TempDir(), "results.json")
433
434 args := []string{
435 "test",
436 ".",
437 fmt.Sprintf("-shim-config=%s", filepath.Join(cwd, "bogo_config.json")),
438 fmt.Sprintf("-shim-path=%s", os.Args[0]),
439 "-shim-extra-flags=-bogo-mode",
440 "-allow-unimplemented",
441 "-loose-errors",
442 fmt.Sprintf("-json-output=%s", resultsFile),
443 }
444 if *bogoFilter != "" {
445 args = append(args, fmt.Sprintf("-test=%s", *bogoFilter))
446 }
447
448 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
449 out := &strings.Builder{}
450 cmd.Stderr = out
451 cmd.Dir = filepath.Join(bogoDir, "ssl/test/runner")
452 err = cmd.Run()
453
454
455
456
457
458
459 resultsJSON, jsonErr := os.ReadFile(resultsFile)
460 if jsonErr != nil {
461 if err != nil {
462 t.Fatalf("bogo failed: %s\n%s", err, out)
463 }
464 t.Fatalf("failed to read results JSON file: %s", jsonErr)
465 }
466
467 var results bogoResults
468 if err := json.Unmarshal(resultsJSON, &results); err != nil {
469 t.Fatalf("failed to parse results JSON: %s", err)
470 }
471
472
473
474
475 assertResults := map[string]string{
476 "CurveTest-Client-MLKEM-TLS13": "PASS",
477 "CurveTest-Server-MLKEM-TLS13": "PASS",
478 }
479
480 for name, result := range results.Tests {
481
482 t.Run(name, func(t *testing.T) {
483 if result.Actual == "FAIL" && result.IsUnexpected {
484 t.Fatal(result.Error)
485 }
486 if expectedResult, ok := assertResults[name]; ok && expectedResult != result.Actual {
487 t.Fatalf("unexpected result: got %s, want %s", result.Actual, assertResults[name])
488 }
489 delete(assertResults, name)
490 if result.Actual == "SKIP" {
491 t.Skip()
492 }
493 })
494 }
495 if *bogoFilter == "" {
496
497 for name, expectedResult := range assertResults {
498 t.Run(name, func(t *testing.T) {
499 t.Fatalf("expected test to run with result %s, but it was not present in the test results", expectedResult)
500 })
501 }
502 }
503 }
504
505
506 type bogoResults struct {
507 Version int `json:"version"`
508 Interrupted bool `json:"interrupted"`
509 PathDelimiter string `json:"path_delimiter"`
510 SecondsSinceEpoch float64 `json:"seconds_since_epoch"`
511 NumFailuresByType map[string]int `json:"num_failures_by_type"`
512 Tests map[string]struct {
513 Actual string `json:"actual"`
514 Expected string `json:"expected"`
515 IsUnexpected bool `json:"is_unexpected"`
516 Error string `json:"error,omitempty"`
517 } `json:"tests"`
518 }
519
View as plain text