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 "errors"
15 "flag"
16 "fmt"
17 "html/template"
18 "internal/byteorder"
19 "internal/testenv"
20 "io"
21 "log"
22 "maps"
23 "net"
24 "os"
25 "path/filepath"
26 "runtime"
27 "slices"
28 "strconv"
29 "strings"
30 "testing"
31 "time"
32
33 "golang.org/x/crypto/cryptobyte"
34 )
35
36
37
38
39
40 const boringsslModVer = "v0.0.0-20261008002628-fdcd735858aa"
41
42 var (
43 port = flag.String("port", "", "")
44 server = flag.Bool("server", false, "")
45
46 isHandshakerSupported = flag.Bool("is-handshaker-supported", false, "")
47
48 keyfile = flag.String("key-file", "", "")
49 certfile = flag.String("cert-file", "", "")
50 ocspResponse = flagBase64("ocsp-response", "")
51 signingPrefs = flagIntSlice("signing-prefs", "")
52
53 trustCert = flag.String("trust-cert", "", "")
54
55 minVersion = flag.Int("min-version", VersionSSL30, "")
56 maxVersion = flag.Int("max-version", VersionTLS13, "")
57 expectVersion = flag.Int("expect-version", 0, "")
58
59 noTLS1 = flag.Bool("no-tls1", false, "")
60 noTLS11 = flag.Bool("no-tls11", false, "")
61 noTLS12 = flag.Bool("no-tls12", false, "")
62 noTLS13 = flag.Bool("no-tls13", false, "")
63
64 requireAnyClientCertificate = flag.Bool("require-any-client-certificate", false, "")
65
66 shimWritesFirst = flag.Bool("shim-writes-first", false, "")
67
68 resumeCount = flag.Int("resume-count", 0, "")
69
70 curves = flagIntSlice("curves", "")
71 expectedCurve = flag.String("expect-curve-id", "", "")
72
73 verifyPrefs = flagIntSlice("verify-prefs", "")
74 expectedSigAlg = flag.String("expect-peer-signature-algorithm", "", "")
75 expectedPeerSigAlg = flagIntSlice("expect-peer-verify-pref", "")
76
77 shimID = flag.Uint64("shim-id", 0, "")
78 useIPv6 = flag.Bool("ipv6", false, "")
79
80 echConfigList = flagBase64("ech-config-list", "")
81 expectECHAccepted = flag.Bool("expect-ech-accept", false, "")
82 expectHRR = flag.Bool("expect-hrr", false, "")
83 expectNoHRR = flag.Bool("expect-no-hrr", false, "")
84 expectedECHRetryConfigs = flag.String("expect-ech-retry-configs", "", "")
85 expectNoECHRetryConfigs = flag.Bool("expect-no-ech-retry-configs", false, "")
86 onInitialExpectECHAccepted = flag.Bool("on-initial-expect-ech-accept", false, "")
87 _ = flag.Bool("expect-no-ech-name-override", false, "")
88 _ = flag.String("expect-ech-name-override", "", "")
89 _ = flag.Bool("reverify-on-resume", false, "")
90 onResumeECHConfigList = flagBase64("on-resume-ech-config-list", "")
91 _ = flag.Bool("on-resume-expect-reject-early-data", false, "")
92 onResumeExpectECHAccepted = flag.Bool("on-resume-expect-ech-accept", false, "")
93 _ = flag.Bool("on-resume-expect-no-ech-name-override", false, "")
94 expectedServerName = flag.String("expect-server-name", "", "")
95 echServerConfig = flagStringSlice("ech-server-config", "")
96 echServerKey = flagStringSlice("ech-server-key", "")
97 echServerRetryConfig = flagStringSlice("ech-is-retry-config", "")
98
99 expectSessionMiss = flag.Bool("expect-session-miss", false, "")
100
101 expectEMS = flag.Bool("expect-extended-master-secret", false, "")
102
103 _ = flag.Bool("enable-early-data", false, "")
104 _ = flag.Bool("on-resume-expect-accept-early-data", false, "")
105 _ = flag.Bool("expect-ticket-supports-early-data", false, "")
106 _ = flag.Bool("on-resume-shim-writes-first", false, "")
107
108 advertiseALPN = flag.String("advertise-alpn", "", "")
109 expectALPN = flag.String("expect-alpn", "", "")
110 rejectALPN = flag.Bool("reject-alpn", false, "")
111 declineALPN = flag.Bool("decline-alpn", false, "")
112 expectAdvertisedALPN = flag.String("expect-advertised-alpn", "", "")
113 selectALPN = flag.String("select-alpn", "", "")
114
115 hostName = flag.String("host-name", "", "")
116
117 verifyPeer = flag.Bool("verify-peer", false, "")
118 _ = flag.Bool("use-custom-verify-callback", false, "")
119
120 waitForDebugger = flag.Bool("wait-for-debugger", false, "")
121 )
122
123 type stringSlice []string
124
125 func flagStringSlice(name, usage string) *stringSlice {
126 f := new(stringSlice)
127 flag.Var(f, name, usage)
128 return f
129 }
130
131 func (saf *stringSlice) String() string {
132 return strings.Join(*saf, ",")
133 }
134
135 func (saf *stringSlice) Set(s string) error {
136 *saf = append(*saf, s)
137 return nil
138 }
139
140 type intSlice []int64
141
142 func flagIntSlice(name, usage string) *intSlice {
143 f := new(intSlice)
144 flag.Var(f, name, usage)
145 return f
146 }
147
148 func (sf *intSlice) String() string {
149 return strings.Join(strings.Split(fmt.Sprint(*sf), " "), ",")
150 }
151
152 func (sf *intSlice) Set(s string) error {
153 i, err := strconv.ParseInt(s, 10, 64)
154 if err != nil {
155 return err
156 }
157 *sf = append(*sf, i)
158 return nil
159 }
160
161 type base64Flag []byte
162
163 func flagBase64(name, usage string) *base64Flag {
164 f := new(base64Flag)
165 flag.Var(f, name, usage)
166 return f
167 }
168
169 func (f *base64Flag) String() string {
170 return base64.StdEncoding.EncodeToString(*f)
171 }
172
173 func (f *base64Flag) Set(s string) error {
174 if *f != nil {
175 return fmt.Errorf("multiple base64 values not supported")
176 }
177 b, err := base64.StdEncoding.DecodeString(s)
178 if err != nil {
179 return err
180 }
181 *f = b
182 return nil
183 }
184
185 func bogoShim() {
186 if *isHandshakerSupported {
187 fmt.Println("No")
188 return
189 }
190
191 fmt.Printf("BoGo shim flags: %q", os.Args[1:])
192
193
194 var ciphersuites []uint16
195 for _, s := range append(CipherSuites(), InsecureCipherSuites()...) {
196 ciphersuites = append(ciphersuites, s.ID)
197 }
198
199 cfg := &Config{
200 ServerName: "test",
201
202 MinVersion: uint16(*minVersion),
203 MaxVersion: uint16(*maxVersion),
204
205 ClientSessionCache: NewLRUClientSessionCache(0),
206
207 CipherSuites: ciphersuites,
208
209 GetConfigForClient: func(chi *ClientHelloInfo) (*Config, error) {
210
211 if *expectAdvertisedALPN != "" {
212
213 s := cryptobyte.String(*expectAdvertisedALPN)
214
215 var expectedALPNs []string
216
217 for !s.Empty() {
218 var alpn cryptobyte.String
219 if !s.ReadUint8LengthPrefixed(&alpn) {
220 return nil, fmt.Errorf("unexpected error while parsing arguments for -expect-advertised-alpn")
221 }
222 expectedALPNs = append(expectedALPNs, string(alpn))
223 }
224
225 if !slices.Equal(chi.SupportedProtos, expectedALPNs) {
226 return nil, fmt.Errorf("unexpected ALPN: got %q, want %q", chi.SupportedProtos, expectedALPNs)
227 }
228 }
229 return nil, nil
230 },
231 }
232
233 if *noTLS1 {
234 cfg.MinVersion = VersionTLS11
235 if *noTLS11 {
236 cfg.MinVersion = VersionTLS12
237 if *noTLS12 {
238 cfg.MinVersion = VersionTLS13
239 if *noTLS13 {
240 log.Fatalf("no supported versions enabled")
241 }
242 }
243 }
244 } else if *noTLS13 {
245 cfg.MaxVersion = VersionTLS12
246 if *noTLS12 {
247 cfg.MaxVersion = VersionTLS11
248 if *noTLS11 {
249 cfg.MaxVersion = VersionTLS10
250 if *noTLS1 {
251 log.Fatalf("no supported versions enabled")
252 }
253 }
254 }
255 }
256
257 if *advertiseALPN != "" {
258 alpns := *advertiseALPN
259 for len(alpns) > 0 {
260 alpnLen := int(alpns[0])
261 cfg.NextProtos = append(cfg.NextProtos, alpns[1:1+alpnLen])
262 alpns = alpns[alpnLen+1:]
263 }
264 }
265
266 if *rejectALPN {
267 cfg.NextProtos = []string{"unnegotiableprotocol"}
268 }
269
270 if *declineALPN {
271 cfg.NextProtos = []string{}
272 }
273 if *selectALPN != "" {
274 cfg.NextProtos = []string{*selectALPN}
275 }
276
277 if *hostName != "" {
278 cfg.ServerName = *hostName
279 }
280
281 if *keyfile != "" || *certfile != "" {
282 pair, err := LoadX509KeyPair(*certfile, *keyfile)
283 if err != nil {
284 log.Fatalf("load key-file err: %s", err)
285 }
286 for _, id := range *signingPrefs {
287 pair.SupportedSignatureAlgorithms = append(pair.SupportedSignatureAlgorithms, SignatureScheme(id))
288 }
289 pair.OCSPStaple = *ocspResponse
290
291
292
293 cfg.GetCertificate = func(chi *ClientHelloInfo) (*Certificate, error) {
294 if *expectedPeerSigAlg != nil {
295 if len(chi.SignatureSchemes) != len(*expectedPeerSigAlg) {
296 return nil, fmt.Errorf("unexpected signature algorithms: got %s, want %v", chi.SignatureSchemes, *expectedPeerSigAlg)
297 }
298 for i := range *expectedPeerSigAlg {
299 if chi.SignatureSchemes[i] != SignatureScheme((*expectedPeerSigAlg)[i]) {
300 return nil, fmt.Errorf("unexpected signature algorithms: got %s, want %v", chi.SignatureSchemes, *expectedPeerSigAlg)
301 }
302 }
303 }
304 return &pair, nil
305 }
306 cfg.GetClientCertificate = func(cri *CertificateRequestInfo) (*Certificate, error) {
307 if *expectedPeerSigAlg != nil {
308 if len(cri.SignatureSchemes) != len(*expectedPeerSigAlg) {
309 return nil, fmt.Errorf("unexpected signature algorithms: got %s, want %v", cri.SignatureSchemes, *expectedPeerSigAlg)
310 }
311 for i := range *expectedPeerSigAlg {
312 if cri.SignatureSchemes[i] != SignatureScheme((*expectedPeerSigAlg)[i]) {
313 return nil, fmt.Errorf("unexpected signature algorithms: got %s, want %v", cri.SignatureSchemes, *expectedPeerSigAlg)
314 }
315 }
316 }
317 return &pair, nil
318 }
319 }
320 if *trustCert != "" {
321 pool := x509.NewCertPool()
322 certFile, err := os.ReadFile(*trustCert)
323 if err != nil {
324 log.Fatalf("load trust-cert err: %s", err)
325 }
326 block, _ := pem.Decode(certFile)
327 cert, err := x509.ParseCertificate(block.Bytes)
328 if err != nil {
329 log.Fatalf("parse trust-cert err: %s", err)
330 }
331 pool.AddCert(cert)
332 cfg.RootCAs = pool
333 }
334
335 if *requireAnyClientCertificate {
336 cfg.ClientAuth = RequireAnyClientCert
337 }
338 if *verifyPeer {
339 cfg.ClientAuth = VerifyClientCertIfGiven
340 }
341
342 if *echConfigList != nil {
343 cfg.EncryptedClientHelloConfigList = *echConfigList
344 cfg.MinVersion = VersionTLS13
345 }
346
347 if *curves != nil {
348 for _, id := range *curves {
349 cfg.CurvePreferences = append(cfg.CurvePreferences, CurveID(id))
350 }
351 }
352
353 if *verifyPrefs != nil {
354 for _, id := range *verifyPrefs {
355 testingOnlySupportedSignatureAlgorithms = append(testingOnlySupportedSignatureAlgorithms, SignatureScheme(id))
356 }
357 }
358
359 if *echServerConfig != nil {
360 if len(*echServerConfig) != len(*echServerKey) || len(*echServerConfig) != len(*echServerRetryConfig) {
361 log.Fatal("-ech-server-config, -ech-server-key, and -ech-is-retry-config mismatch")
362 }
363
364 for i, c := range *echServerConfig {
365 configBytes, err := base64.StdEncoding.DecodeString(c)
366 if err != nil {
367 log.Fatalf("parse ech-server-config err: %s", err)
368 }
369 privBytes, err := base64.StdEncoding.DecodeString((*echServerKey)[i])
370 if err != nil {
371 log.Fatalf("parse ech-server-key err: %s", err)
372 }
373
374 cfg.EncryptedClientHelloKeys = append(cfg.EncryptedClientHelloKeys, EncryptedClientHelloKey{
375 Config: configBytes,
376 PrivateKey: privBytes,
377 SendAsRetry: (*echServerRetryConfig)[i] == "1",
378 })
379 }
380 }
381
382 for i := 0; i < *resumeCount+1; i++ {
383 if i > 0 && *onResumeECHConfigList != nil {
384 cfg.EncryptedClientHelloConfigList = *onResumeECHConfigList
385 }
386
387 host := "127.0.0.1"
388 if *useIPv6 {
389 host = "::1"
390 }
391 conn, err := net.Dial("tcp", net.JoinHostPort(host, *port))
392 if err != nil {
393 log.Fatalf("dial err: %s", err)
394 }
395 defer conn.Close()
396
397
398 shimIDBytes := make([]byte, 8)
399 byteorder.LEPutUint64(shimIDBytes, *shimID)
400 if _, err := conn.Write(shimIDBytes); err != nil {
401 log.Fatalf("failed to write shim id: %s", err)
402 }
403
404 var tlsConn *Conn
405 if *server {
406 tlsConn = Server(conn, cfg)
407 } else {
408 tlsConn = Client(conn, cfg)
409 }
410
411 if i == 0 && *shimWritesFirst {
412 if _, err := tlsConn.Write([]byte("hello")); err != nil {
413 log.Fatalf("write err: %s", err)
414 }
415 }
416
417
418
419 if *waitForDebugger {
420 pauseProcess()
421 }
422
423 for {
424 buf := make([]byte, 500)
425 var n int
426 n, err = tlsConn.Read(buf)
427 if err != nil {
428 break
429 }
430 buf = buf[:n]
431 for i := range buf {
432 buf[i] ^= 0xff
433 }
434 if _, err = tlsConn.Write(buf); err != nil {
435 break
436 }
437 }
438 if err != io.EOF {
439
440
441
442
443 orderlyShutdown(tlsConn)
444
445 retryErr, ok := err.(*ECHRejectionError)
446 if !ok {
447 log.Fatal(err)
448 }
449 if *expectNoECHRetryConfigs && len(retryErr.RetryConfigList) > 0 {
450 log.Fatalf("expected no ECH retry configs, got some")
451 }
452 if *expectedECHRetryConfigs != "" {
453 expectedRetryConfigs, err := base64.StdEncoding.DecodeString(*expectedECHRetryConfigs)
454 if err != nil {
455 log.Fatalf("failed to decode expected retry configs: %s", err)
456 }
457 if !bytes.Equal(retryErr.RetryConfigList, expectedRetryConfigs) {
458 log.Fatalf("unexpected retry list returned: got %x, want %x", retryErr.RetryConfigList, expectedRetryConfigs)
459 }
460 }
461 log.Fatalf("conn error: %s", err)
462 }
463
464 cs := tlsConn.ConnectionState()
465 if cs.HandshakeComplete {
466 if *expectALPN != "" && cs.NegotiatedProtocol != *expectALPN {
467 log.Fatalf("unexpected protocol negotiated: want %q, got %q", *expectALPN, cs.NegotiatedProtocol)
468 }
469
470 if *selectALPN != "" && cs.NegotiatedProtocol != *selectALPN {
471 log.Fatalf("unexpected protocol negotiated: want %q, got %q", *selectALPN, cs.NegotiatedProtocol)
472 }
473
474 if *expectVersion != 0 && cs.Version != uint16(*expectVersion) {
475 log.Fatalf("expected ssl version %d, got %d", *expectVersion, cs.Version)
476 }
477 if *declineALPN && cs.NegotiatedProtocol != "" {
478 log.Fatal("unexpected ALPN protocol")
479 }
480 if *expectECHAccepted && !cs.ECHAccepted {
481 log.Fatal("expected ECH to be accepted, but connection state shows it was not")
482 } else if i == 0 && *onInitialExpectECHAccepted && !cs.ECHAccepted {
483 log.Fatal("expected ECH to be accepted, but connection state shows it was not")
484 } else if i > 0 && *onResumeExpectECHAccepted && !cs.ECHAccepted {
485 log.Fatal("expected ECH to be accepted on resumption, but connection state shows it was not")
486 } else if i == 0 && !*expectECHAccepted && cs.ECHAccepted {
487 log.Fatal("did not expect ECH, but it was accepted")
488 }
489
490 if *expectHRR && !cs.HelloRetryRequest {
491 log.Fatal("expected HRR but did not do it")
492 }
493
494 if *expectNoHRR && cs.HelloRetryRequest {
495 log.Fatal("expected no HRR but did do it")
496 }
497
498 if *expectSessionMiss && cs.DidResume {
499 log.Fatal("unexpected session resumption")
500 }
501
502
503
504 if *expectEMS && !tlsConn.extMasterSecret && cs.Version < VersionTLS13 {
505 log.Fatal("expected extended master secret to be negotiated, but it was not")
506 }
507
508 if *expectedServerName != "" && cs.ServerName != *expectedServerName {
509 log.Fatalf("unexpected server name: got %q, want %q", cs.ServerName, *expectedServerName)
510 }
511 }
512
513 if *expectedCurve != "" {
514 expectedCurveID, err := strconv.Atoi(*expectedCurve)
515 if err != nil {
516 log.Fatalf("failed to parse -expect-curve-id: %s", err)
517 }
518 if cs.CurveID != CurveID(expectedCurveID) {
519 log.Fatalf("unexpected curve id: want %d, got %d", expectedCurveID, tlsConn.curveID)
520 }
521 }
522
523
524 if *expectedSigAlg != "" && !cs.DidResume {
525 expectedSigAlgID, err := strconv.Atoi(*expectedSigAlg)
526 if err != nil {
527 log.Fatalf("failed to parse -expect-peer-signature-algorithm: %s", err)
528 }
529 if cs.testingOnlyPeerSignatureAlgorithm != SignatureScheme(expectedSigAlgID) {
530 log.Fatalf("unexpected peer signature algorithm: want %s, got %s", SignatureScheme(expectedSigAlgID), cs.testingOnlyPeerSignatureAlgorithm)
531 }
532 }
533 }
534 }
535
536
537
538
539
540
541 func orderlyShutdown(tlsConn *Conn) {
542
543 tlsConn.flush()
544
545 netConn := tlsConn.NetConn()
546 tcpConn := netConn.(*net.TCPConn)
547 tcpConn.CloseWrite()
548
549
550 buf := make([]byte, maxPlaintext)
551 for {
552 n, err := tcpConn.Read(buf)
553 if n == 0 || err != nil {
554 break
555 }
556 }
557
558 tcpConn.CloseRead()
559 }
560
561 func TestBogoSuite(t *testing.T) {
562 skipFIPS(t)
563
564 results := runBogoSuite(t, *bogoFilter, nil)
565
566 if *bogoReport != "" {
567 if err := generateReport(results, *bogoReport); err != nil {
568 t.Fatalf("failed to generate report: %v", err)
569 }
570 }
571
572
573
574
575 assertResults := map[string]string{
576 "CurveTest-Client-X25519MLKEM768-TLS13": "PASS",
577 "CurveTest-Server-X25519MLKEM768-TLS13": "PASS",
578 "CurveTest-Client-MLKEM1024-TLS13": "PASS",
579 "CurveTest-Server-MLKEM1024-TLS13": "PASS",
580
581
582
583 "ClientAuth-Enforced": "PASS",
584 "ServerAuth-Enforced": "PASS",
585 "ClientAuth-Enforced-TLS13": "PASS",
586 "ServerAuth-Enforced-TLS13": "PASS",
587 "VerifyPreferences-Advertised": "PASS",
588 "VerifyPreferences-Enforced": "PASS",
589 "Client-TLS12-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
590 "Server-TLS12-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
591 "Client-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
592 "Server-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
593
594
595
596 "ExtendedMasterSecret-TLS12-Client": "PASS",
597 "ExtendedMasterSecret-TLS12-Server": "PASS",
598 "NoExtendedMasterSecret-TLS13-Client": "PASS",
599 "NoExtendedMasterSecret-TLS13-Server": "PASS",
600 "ExtendedMasterSecret-YesToYes-Client": "PASS",
601 "ExtendedMasterSecret-YesToYes-Server": "PASS",
602 }
603
604 for name, result := range results.Tests {
605
606 t.Run(name, func(t *testing.T) {
607 if result.Actual == "FAIL" && result.IsUnexpected {
608 t.Fail()
609 }
610 if result.Error != "" {
611 t.Log(result.Error)
612 }
613 if exp, ok := assertResults[name]; ok && exp != result.Actual {
614 t.Errorf("unexpected result: got %s, want %s", result.Actual, exp)
615 }
616 delete(assertResults, name)
617 if result.Actual == "SKIP" {
618 t.SkipNow()
619 }
620 })
621 }
622 if *bogoFilter == "" {
623
624 for name, expectedResult := range assertResults {
625 t.Run(name, func(t *testing.T) {
626 t.Fatalf("expected test to run with result %s, but it was not present in the test results", expectedResult)
627 })
628 }
629 }
630 }
631
632
633
634
635
636
637
638
639
640
641 func TestBogoSuiteFIPSEMS(t *testing.T) {
642 for _, tc := range []struct {
643 name string
644 godebug string
645
646
647 expected map[string]string
648 }{
649 {
650 name: "enforced",
651 godebug: "GODEBUG=fips140=on",
652 expected: map[string]string{
653
654
655
656 "NoExtendedMasterSecret-TLS12-Client": "FAIL",
657 "NoExtendedMasterSecret-TLS12-Server": "FAIL",
658 "ExtendedMasterSecret-NoToNo-Client": "FAIL",
659 "ExtendedMasterSecret-NoToNo-Server": "FAIL",
660
661
662 "ExtendedMasterSecret-TLS12-Client": "PASS",
663 "ExtendedMasterSecret-TLS12-Server": "PASS",
664 "ExtendedMasterSecret-YesToYes-Client": "PASS",
665 "ExtendedMasterSecret-YesToYes-Server": "PASS",
666 },
667 },
668 {
669 name: "fips140ems=0",
670 godebug: "GODEBUG=fips140=on,fips140ems=0",
671 expected: map[string]string{
672
673
674
675 "NoExtendedMasterSecret-TLS12-Client": "PASS",
676 "NoExtendedMasterSecret-TLS12-Server": "PASS",
677 "ExtendedMasterSecret-NoToNo-Client": "PASS",
678 "ExtendedMasterSecret-NoToNo-Server": "PASS",
679
680
681 "ExtendedMasterSecret-NoToYes-Client": "PASS",
682 "ExtendedMasterSecret-YesToNo-Server": "PASS",
683
684
685 "ExtendedMasterSecret-TLS12-Client": "PASS",
686 "ExtendedMasterSecret-TLS12-Server": "PASS",
687 "ExtendedMasterSecret-YesToYes-Client": "PASS",
688 "ExtendedMasterSecret-YesToYes-Server": "PASS",
689 },
690 },
691 } {
692 t.Run(tc.name, func(t *testing.T) {
693 filter := strings.Join(slices.Sorted(maps.Keys(tc.expected)), ";")
694 results := runBogoSuite(t, filter, []string{tc.godebug})
695 for name, want := range tc.expected {
696 result, ok := results.Tests[name]
697 if !ok {
698 t.Errorf("%s: expected test to run, but it was not present in the test results", name)
699 continue
700 }
701 if result.Actual != want {
702 t.Errorf("%s: got %s, want %s: %s", name, result.Actual, want, result.Error)
703 }
704 }
705 })
706 }
707 }
708
709
710
711
712
713 func runBogoSuite(t *testing.T, filter string, extraEnv []string) bogoResults {
714 if testing.Short() {
715 t.Skip("skipping in short mode")
716 }
717 if testenv.Builder() != "" && runtime.GOOS == "windows" {
718 t.Skip("#66913: windows network connections are flakey on builders")
719 }
720
721
722
723
724
725 if _, err := os.Stat("bogo_config.json"); err != nil {
726 t.Fatal(err)
727 }
728
729 var bogoDir string
730 if *bogoLocalDir != "" {
731 ensureLocalBogo(t, *bogoLocalDir)
732 bogoDir = *bogoLocalDir
733 } else {
734 bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
735 }
736
737 cwd, err := os.Getwd()
738 if err != nil {
739 t.Fatal(err)
740 }
741
742 resultsFile := filepath.Join(t.TempDir(), "results.json")
743
744 args := []string{
745 "test",
746 ".",
747 fmt.Sprintf("-shim-config=%s", filepath.Join(cwd, "bogo_config.json")),
748 fmt.Sprintf("-shim-path=%s", testenv.Executable(t)),
749 "-shim-extra-flags=-bogo-mode",
750 "-allow-unimplemented",
751 "-loose-errors",
752 fmt.Sprintf("-json-output=%s", resultsFile),
753 }
754 if filter != "" {
755 args = append(args, fmt.Sprintf("-test=%s", filter))
756 }
757
758 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
759 cmd.Dir = filepath.Join(bogoDir, "ssl/test/runner")
760
761
762 cmd.Env = append(cmd.Environ(), "GOFIPS140=off")
763 cmd.Env = append(cmd.Env, extraEnv...)
764 out, err := cmd.CombinedOutput()
765
766
767
768
769
770
771 resultsJSON, jsonErr := os.ReadFile(resultsFile)
772 if jsonErr != nil {
773 if err != nil {
774 t.Fatalf("bogo failed: %s\n%s", err, out)
775 }
776 t.Fatalf("failed to read results JSON file: %s", jsonErr)
777 }
778
779 var results bogoResults
780 if err := json.Unmarshal(resultsJSON, &results); err != nil {
781 t.Fatalf("failed to parse results JSON: %s", err)
782 }
783 return results
784 }
785
786
787
788
789
790
791 func ensureLocalBogo(t *testing.T, localBogoDir string) {
792 t.Helper()
793
794 if stat, err := os.Stat(localBogoDir); err == nil {
795 if !stat.IsDir() {
796 t.Fatalf("local bogo dir (%q) exists but is not a directory", localBogoDir)
797 }
798
799 t.Logf("using local bogo checkout from %q", localBogoDir)
800 return
801 } else if !errors.Is(err, os.ErrNotExist) {
802 t.Fatalf("failed to stat local bogo dir (%q): %v", localBogoDir, err)
803 }
804
805 testenv.MustHaveExecPath(t, "git")
806
807 idx := strings.LastIndex(boringsslModVer, "-")
808 if idx == -1 || idx == len(boringsslModVer)-1 {
809 t.Fatalf("invalid boringsslModVer format: %q", boringsslModVer)
810 }
811 commitSHA := boringsslModVer[idx+1:]
812
813 t.Logf("cloning boringssl@%s to %q", commitSHA, localBogoDir)
814 cloneCmd := testenv.Command(t, "git", "clone", "--no-checkout", "https://boringssl.googlesource.com/boringssl", localBogoDir)
815 if err := cloneCmd.Run(); err != nil {
816 t.Fatalf("git clone failed: %v", err)
817 }
818
819 checkoutCmd := testenv.Command(t, "git", "checkout", commitSHA)
820 checkoutCmd.Dir = localBogoDir
821 if err := checkoutCmd.Run(); err != nil {
822 t.Fatalf("git checkout failed: %v", err)
823 }
824
825 t.Logf("using fresh local bogo checkout from %q", localBogoDir)
826 }
827
828 func generateReport(results bogoResults, outPath string) error {
829 data := reportData{
830 Results: results,
831 Timestamp: time.Unix(int64(results.SecondsSinceEpoch), 0).Format("2006-01-02 15:04:05"),
832 Revision: boringsslModVer,
833 }
834
835 tmpl := template.Must(template.New("report").Parse(reportTemplate))
836 file, err := os.Create(outPath)
837 if err != nil {
838 return err
839 }
840 defer file.Close()
841
842 return tmpl.Execute(file, data)
843 }
844
845
846 type bogoResults struct {
847 Version int `json:"version"`
848 Interrupted bool `json:"interrupted"`
849 PathDelimiter string `json:"path_delimiter"`
850 SecondsSinceEpoch float64 `json:"seconds_since_epoch"`
851 NumFailuresByType map[string]int `json:"num_failures_by_type"`
852 Tests map[string]struct {
853 Actual string `json:"actual"`
854 Expected string `json:"expected"`
855 IsUnexpected bool `json:"is_unexpected"`
856 Error string `json:"error,omitempty"`
857 } `json:"tests"`
858 }
859
860 type reportData struct {
861 Results bogoResults
862 SkipReasons map[string]string
863 Timestamp string
864 Revision string
865 }
866
867 const reportTemplate = `
868 <!DOCTYPE html>
869 <html>
870 <head>
871 <title>BoGo Results Report</title>
872 <style>
873 body { font-family: monospace; margin: 20px; }
874 .summary { background: #f5f5f5; padding: 10px; margin-bottom: 20px; }
875 .controls { margin-bottom: 10px; }
876 .controls input, select { margin-right: 10px; }
877 table { width: 100%; border-collapse: collapse; table-layout: fixed; }
878 th, td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; }
879 th { background-color: #f2f2f2; cursor: pointer; }
880 .name-col { width: 30%; }
881 .status-col { width: 8%; }
882 .actual-col { width: 8%; }
883 .expected-col { width: 8%; }
884 .error-col { width: 26%; }
885 .PASS { background-color: #d4edda; }
886 .FAIL { background-color: #f8d7da; }
887 .SKIP { background-color: #fff3cd; }
888 .error {
889 font-family: monospace;
890 font-size: 0.9em;
891 color: #721c24;
892 white-space: pre-wrap;
893 word-break: break-word;
894 }
895 </style>
896 </head>
897 <body>
898 <h1>BoGo Results Report</h1>
899
900 <div class="summary">
901 <strong>Generated:</strong> {{.Timestamp}} | <strong>BoGo Revision:</strong> {{.Revision}}<br>
902 {{range $status, $count := .Results.NumFailuresByType}}
903 <strong>{{$status}}:</strong> {{$count}} |
904 {{end}}
905 </div>
906
907 <div class="controls">
908 <input type="text" id="search" placeholder="Search tests..." onkeyup="filterTests()">
909 <select id="statusFilter" onchange="filterTests()">
910 <option value="">All</option>
911 <option value="FAIL">Failed</option>
912 <option value="PASS">Passed</option>
913 <option value="SKIP">Skipped</option>
914 </select>
915 </div>
916
917 <table id="resultsTable">
918 <thead>
919 <tr>
920 <th class="name-col" onclick="sortBy('name')">Test Name</th>
921 <th class="status-col" onclick="sortBy('status')">Status</th>
922 <th class="actual-col" onclick="sortBy('actual')">Actual</th>
923 <th class="expected-col" onclick="sortBy('expected')">Expected</th>
924 <th class="error-col">Error</th>
925 </tr>
926 </thead>
927 <tbody>
928 {{range $name, $test := .Results.Tests}}
929 <tr class="{{$test.Actual}}" data-name="{{$name}}" data-status="{{$test.Actual}}">
930 <td>{{$name}}</td>
931 <td>{{$test.Actual}}</td>
932 <td>{{$test.Actual}}</td>
933 <td>{{$test.Expected}}</td>
934 <td class="error">{{$test.Error}}</td>
935 </tr>
936 {{end}}
937 </tbody>
938 </table>
939
940 <script>
941 function filterTests() {
942 const search = document.getElementById('search').value.toLowerCase();
943 const status = document.getElementById('statusFilter').value;
944 const rows = document.querySelectorAll('#resultsTable tbody tr');
945
946 rows.forEach(row => {
947 const name = row.dataset.name.toLowerCase();
948 const rowStatus = row.dataset.status;
949 const matchesSearch = name.includes(search);
950 const matchesStatus = !status || rowStatus === status;
951
952 row.style.display = matchesSearch && matchesStatus ? '' : 'none';
953 });
954 }
955
956 function sortBy(column) {
957 const tbody = document.querySelector('#resultsTable tbody');
958 const rows = Array.from(tbody.querySelectorAll('tr'));
959
960 rows.sort((a, b) => {
961 if (column === 'status') {
962 const statusOrder = {'FAIL': 0, 'PASS': 1, 'SKIP': 2};
963 const aStatus = a.dataset.status;
964 const bStatus = b.dataset.status;
965 if (aStatus !== bStatus) {
966 return statusOrder[aStatus] - statusOrder[bStatus];
967 }
968 return a.dataset.name.localeCompare(b.dataset.name);
969 } else {
970 return a.dataset.name.localeCompare(b.dataset.name);
971 }
972 });
973
974 rows.forEach(row => tbody.appendChild(row));
975 filterTests();
976 }
977
978 sortBy("status");
979 </script>
980 </body>
981 </html>
982 `
983
View as plain text