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-20260209204302-2a7ca5404e13"
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 _ = 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 conn, err := net.Dial("tcp", net.JoinHostPort("localhost", *port))
388 if err != nil {
389 log.Fatalf("dial err: %s", err)
390 }
391 defer conn.Close()
392
393
394 shimIDBytes := make([]byte, 8)
395 byteorder.LEPutUint64(shimIDBytes, *shimID)
396 if _, err := conn.Write(shimIDBytes); err != nil {
397 log.Fatalf("failed to write shim id: %s", err)
398 }
399
400 var tlsConn *Conn
401 if *server {
402 tlsConn = Server(conn, cfg)
403 } else {
404 tlsConn = Client(conn, cfg)
405 }
406
407 if i == 0 && *shimWritesFirst {
408 if _, err := tlsConn.Write([]byte("hello")); err != nil {
409 log.Fatalf("write err: %s", err)
410 }
411 }
412
413
414
415 if *waitForDebugger {
416 pauseProcess()
417 }
418
419 for {
420 buf := make([]byte, 500)
421 var n int
422 n, err = tlsConn.Read(buf)
423 if err != nil {
424 break
425 }
426 buf = buf[:n]
427 for i := range buf {
428 buf[i] ^= 0xff
429 }
430 if _, err = tlsConn.Write(buf); err != nil {
431 break
432 }
433 }
434 if err != io.EOF {
435
436
437
438
439 orderlyShutdown(tlsConn)
440
441 retryErr, ok := err.(*ECHRejectionError)
442 if !ok {
443 log.Fatal(err)
444 }
445 if *expectNoECHRetryConfigs && len(retryErr.RetryConfigList) > 0 {
446 log.Fatalf("expected no ECH retry configs, got some")
447 }
448 if *expectedECHRetryConfigs != "" {
449 expectedRetryConfigs, err := base64.StdEncoding.DecodeString(*expectedECHRetryConfigs)
450 if err != nil {
451 log.Fatalf("failed to decode expected retry configs: %s", err)
452 }
453 if !bytes.Equal(retryErr.RetryConfigList, expectedRetryConfigs) {
454 log.Fatalf("unexpected retry list returned: got %x, want %x", retryErr.RetryConfigList, expectedRetryConfigs)
455 }
456 }
457 log.Fatalf("conn error: %s", err)
458 }
459
460 cs := tlsConn.ConnectionState()
461 if cs.HandshakeComplete {
462 if *expectALPN != "" && cs.NegotiatedProtocol != *expectALPN {
463 log.Fatalf("unexpected protocol negotiated: want %q, got %q", *expectALPN, cs.NegotiatedProtocol)
464 }
465
466 if *selectALPN != "" && cs.NegotiatedProtocol != *selectALPN {
467 log.Fatalf("unexpected protocol negotiated: want %q, got %q", *selectALPN, cs.NegotiatedProtocol)
468 }
469
470 if *expectVersion != 0 && cs.Version != uint16(*expectVersion) {
471 log.Fatalf("expected ssl version %d, got %d", *expectVersion, cs.Version)
472 }
473 if *declineALPN && cs.NegotiatedProtocol != "" {
474 log.Fatal("unexpected ALPN protocol")
475 }
476 if *expectECHAccepted && !cs.ECHAccepted {
477 log.Fatal("expected ECH to be accepted, but connection state shows it was not")
478 } else if i == 0 && *onInitialExpectECHAccepted && !cs.ECHAccepted {
479 log.Fatal("expected ECH to be accepted, but connection state shows it was not")
480 } else if i > 0 && *onResumeExpectECHAccepted && !cs.ECHAccepted {
481 log.Fatal("expected ECH to be accepted on resumption, but connection state shows it was not")
482 } else if i == 0 && !*expectECHAccepted && cs.ECHAccepted {
483 log.Fatal("did not expect ECH, but it was accepted")
484 }
485
486 if *expectHRR && !cs.HelloRetryRequest {
487 log.Fatal("expected HRR but did not do it")
488 }
489
490 if *expectNoHRR && cs.HelloRetryRequest {
491 log.Fatal("expected no HRR but did do it")
492 }
493
494 if *expectSessionMiss && cs.DidResume {
495 log.Fatal("unexpected session resumption")
496 }
497
498
499
500 if *expectEMS && !tlsConn.extMasterSecret && cs.Version < VersionTLS13 {
501 log.Fatal("expected extended master secret to be negotiated, but it was not")
502 }
503
504 if *expectedServerName != "" && cs.ServerName != *expectedServerName {
505 log.Fatalf("unexpected server name: got %q, want %q", cs.ServerName, *expectedServerName)
506 }
507 }
508
509 if *expectedCurve != "" {
510 expectedCurveID, err := strconv.Atoi(*expectedCurve)
511 if err != nil {
512 log.Fatalf("failed to parse -expect-curve-id: %s", err)
513 }
514 if cs.CurveID != CurveID(expectedCurveID) {
515 log.Fatalf("unexpected curve id: want %d, got %d", expectedCurveID, tlsConn.curveID)
516 }
517 }
518
519
520 if *expectedSigAlg != "" && !cs.DidResume {
521 expectedSigAlgID, err := strconv.Atoi(*expectedSigAlg)
522 if err != nil {
523 log.Fatalf("failed to parse -expect-peer-signature-algorithm: %s", err)
524 }
525 if cs.testingOnlyPeerSignatureAlgorithm != SignatureScheme(expectedSigAlgID) {
526 log.Fatalf("unexpected peer signature algorithm: want %s, got %s", SignatureScheme(expectedSigAlgID), cs.testingOnlyPeerSignatureAlgorithm)
527 }
528 }
529 }
530 }
531
532
533
534
535
536
537 func orderlyShutdown(tlsConn *Conn) {
538
539 tlsConn.flush()
540
541 netConn := tlsConn.NetConn()
542 tcpConn := netConn.(*net.TCPConn)
543 tcpConn.CloseWrite()
544
545
546 buf := make([]byte, maxPlaintext)
547 for {
548 n, err := tcpConn.Read(buf)
549 if n == 0 || err != nil {
550 break
551 }
552 }
553
554 tcpConn.CloseRead()
555 }
556
557 func TestBogoSuite(t *testing.T) {
558 skipFIPS(t)
559
560 results := runBogoSuite(t, *bogoFilter, nil)
561
562 if *bogoReport != "" {
563 if err := generateReport(results, *bogoReport); err != nil {
564 t.Fatalf("failed to generate report: %v", err)
565 }
566 }
567
568
569
570
571 assertResults := map[string]string{
572 "CurveTest-Client-X25519MLKEM768-TLS13": "PASS",
573 "CurveTest-Server-X25519MLKEM768-TLS13": "PASS",
574 "CurveTest-Client-MLKEM1024-TLS13": "PASS",
575 "CurveTest-Server-MLKEM1024-TLS13": "PASS",
576
577
578
579 "ClientAuth-Enforced": "PASS",
580 "ServerAuth-Enforced": "PASS",
581 "ClientAuth-Enforced-TLS13": "PASS",
582 "ServerAuth-Enforced-TLS13": "PASS",
583 "VerifyPreferences-Advertised": "PASS",
584 "VerifyPreferences-Enforced": "PASS",
585 "Client-TLS12-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
586 "Server-TLS12-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
587 "Client-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
588 "Server-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
589
590
591
592 "ExtendedMasterSecret-TLS12-Client": "PASS",
593 "ExtendedMasterSecret-TLS12-Server": "PASS",
594 "NoExtendedMasterSecret-TLS13-Client": "PASS",
595 "NoExtendedMasterSecret-TLS13-Server": "PASS",
596 "ExtendedMasterSecret-YesToYes-Client": "PASS",
597 "ExtendedMasterSecret-YesToYes-Server": "PASS",
598 }
599
600 for name, result := range results.Tests {
601
602 t.Run(name, func(t *testing.T) {
603 if result.Actual == "FAIL" && result.IsUnexpected {
604 t.Fail()
605 }
606 if result.Error != "" {
607 t.Log(result.Error)
608 }
609 if exp, ok := assertResults[name]; ok && exp != result.Actual {
610 t.Errorf("unexpected result: got %s, want %s", result.Actual, exp)
611 }
612 delete(assertResults, name)
613 if result.Actual == "SKIP" {
614 t.SkipNow()
615 }
616 })
617 }
618 if *bogoFilter == "" {
619
620 for name, expectedResult := range assertResults {
621 t.Run(name, func(t *testing.T) {
622 t.Fatalf("expected test to run with result %s, but it was not present in the test results", expectedResult)
623 })
624 }
625 }
626 }
627
628
629
630
631
632
633
634
635
636
637 func TestBogoSuiteFIPSEMS(t *testing.T) {
638 for _, tc := range []struct {
639 name string
640 godebug string
641
642
643 expected map[string]string
644 }{
645 {
646 name: "enforced",
647 godebug: "GODEBUG=fips140=on",
648 expected: map[string]string{
649
650
651
652 "NoExtendedMasterSecret-TLS12-Client": "FAIL",
653 "NoExtendedMasterSecret-TLS12-Server": "FAIL",
654 "ExtendedMasterSecret-NoToNo-Client": "FAIL",
655 "ExtendedMasterSecret-NoToNo-Server": "FAIL",
656
657
658 "ExtendedMasterSecret-TLS12-Client": "PASS",
659 "ExtendedMasterSecret-TLS12-Server": "PASS",
660 "ExtendedMasterSecret-YesToYes-Client": "PASS",
661 "ExtendedMasterSecret-YesToYes-Server": "PASS",
662 },
663 },
664 {
665 name: "fips140ems=0",
666 godebug: "GODEBUG=fips140=on,fips140ems=0",
667 expected: map[string]string{
668
669
670
671 "NoExtendedMasterSecret-TLS12-Client": "PASS",
672 "NoExtendedMasterSecret-TLS12-Server": "PASS",
673 "ExtendedMasterSecret-NoToNo-Client": "PASS",
674 "ExtendedMasterSecret-NoToNo-Server": "PASS",
675
676
677 "ExtendedMasterSecret-NoToYes-Client": "PASS",
678 "ExtendedMasterSecret-YesToNo-Server": "PASS",
679
680
681 "ExtendedMasterSecret-TLS12-Client": "PASS",
682 "ExtendedMasterSecret-TLS12-Server": "PASS",
683 "ExtendedMasterSecret-YesToYes-Client": "PASS",
684 "ExtendedMasterSecret-YesToYes-Server": "PASS",
685 },
686 },
687 } {
688 t.Run(tc.name, func(t *testing.T) {
689 filter := strings.Join(slices.Sorted(maps.Keys(tc.expected)), ";")
690 results := runBogoSuite(t, filter, []string{tc.godebug})
691 for name, want := range tc.expected {
692 result, ok := results.Tests[name]
693 if !ok {
694 t.Errorf("%s: expected test to run, but it was not present in the test results", name)
695 continue
696 }
697 if result.Actual != want {
698 t.Errorf("%s: got %s, want %s: %s", name, result.Actual, want, result.Error)
699 }
700 }
701 })
702 }
703 }
704
705
706
707
708
709 func runBogoSuite(t *testing.T, filter string, extraEnv []string) bogoResults {
710 if testing.Short() {
711 t.Skip("skipping in short mode")
712 }
713 if testenv.Builder() != "" && runtime.GOOS == "windows" {
714 t.Skip("#66913: windows network connections are flakey on builders")
715 }
716
717
718
719
720
721 if _, err := os.Stat("bogo_config.json"); err != nil {
722 t.Fatal(err)
723 }
724
725 var bogoDir string
726 if *bogoLocalDir != "" {
727 ensureLocalBogo(t, *bogoLocalDir)
728 bogoDir = *bogoLocalDir
729 } else {
730 bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
731 }
732
733 cwd, err := os.Getwd()
734 if err != nil {
735 t.Fatal(err)
736 }
737
738 resultsFile := filepath.Join(t.TempDir(), "results.json")
739
740 args := []string{
741 "test",
742 ".",
743 fmt.Sprintf("-shim-config=%s", filepath.Join(cwd, "bogo_config.json")),
744 fmt.Sprintf("-shim-path=%s", testenv.Executable(t)),
745 "-shim-extra-flags=-bogo-mode",
746 "-allow-unimplemented",
747 "-loose-errors",
748 fmt.Sprintf("-json-output=%s", resultsFile),
749 }
750 if filter != "" {
751 args = append(args, fmt.Sprintf("-test=%s", filter))
752 }
753
754 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
755 cmd.Dir = filepath.Join(bogoDir, "ssl/test/runner")
756 if extraEnv != nil {
757 cmd.Env = append(os.Environ(), extraEnv...)
758 }
759 out, err := cmd.CombinedOutput()
760
761
762
763
764
765
766 resultsJSON, jsonErr := os.ReadFile(resultsFile)
767 if jsonErr != nil {
768 if err != nil {
769 t.Fatalf("bogo failed: %s\n%s", err, out)
770 }
771 t.Fatalf("failed to read results JSON file: %s", jsonErr)
772 }
773
774 var results bogoResults
775 if err := json.Unmarshal(resultsJSON, &results); err != nil {
776 t.Fatalf("failed to parse results JSON: %s", err)
777 }
778 return results
779 }
780
781
782
783
784
785
786 func ensureLocalBogo(t *testing.T, localBogoDir string) {
787 t.Helper()
788
789 if stat, err := os.Stat(localBogoDir); err == nil {
790 if !stat.IsDir() {
791 t.Fatalf("local bogo dir (%q) exists but is not a directory", localBogoDir)
792 }
793
794 t.Logf("using local bogo checkout from %q", localBogoDir)
795 return
796 } else if !errors.Is(err, os.ErrNotExist) {
797 t.Fatalf("failed to stat local bogo dir (%q): %v", localBogoDir, err)
798 }
799
800 testenv.MustHaveExecPath(t, "git")
801
802 idx := strings.LastIndex(boringsslModVer, "-")
803 if idx == -1 || idx == len(boringsslModVer)-1 {
804 t.Fatalf("invalid boringsslModVer format: %q", boringsslModVer)
805 }
806 commitSHA := boringsslModVer[idx+1:]
807
808 t.Logf("cloning boringssl@%s to %q", commitSHA, localBogoDir)
809 cloneCmd := testenv.Command(t, "git", "clone", "--no-checkout", "https://boringssl.googlesource.com/boringssl", localBogoDir)
810 if err := cloneCmd.Run(); err != nil {
811 t.Fatalf("git clone failed: %v", err)
812 }
813
814 checkoutCmd := testenv.Command(t, "git", "checkout", commitSHA)
815 checkoutCmd.Dir = localBogoDir
816 if err := checkoutCmd.Run(); err != nil {
817 t.Fatalf("git checkout failed: %v", err)
818 }
819
820 t.Logf("using fresh local bogo checkout from %q", localBogoDir)
821 }
822
823 func generateReport(results bogoResults, outPath string) error {
824 data := reportData{
825 Results: results,
826 Timestamp: time.Unix(int64(results.SecondsSinceEpoch), 0).Format("2006-01-02 15:04:05"),
827 Revision: boringsslModVer,
828 }
829
830 tmpl := template.Must(template.New("report").Parse(reportTemplate))
831 file, err := os.Create(outPath)
832 if err != nil {
833 return err
834 }
835 defer file.Close()
836
837 return tmpl.Execute(file, data)
838 }
839
840
841 type bogoResults struct {
842 Version int `json:"version"`
843 Interrupted bool `json:"interrupted"`
844 PathDelimiter string `json:"path_delimiter"`
845 SecondsSinceEpoch float64 `json:"seconds_since_epoch"`
846 NumFailuresByType map[string]int `json:"num_failures_by_type"`
847 Tests map[string]struct {
848 Actual string `json:"actual"`
849 Expected string `json:"expected"`
850 IsUnexpected bool `json:"is_unexpected"`
851 Error string `json:"error,omitempty"`
852 } `json:"tests"`
853 }
854
855 type reportData struct {
856 Results bogoResults
857 SkipReasons map[string]string
858 Timestamp string
859 Revision string
860 }
861
862 const reportTemplate = `
863 <!DOCTYPE html>
864 <html>
865 <head>
866 <title>BoGo Results Report</title>
867 <style>
868 body { font-family: monospace; margin: 20px; }
869 .summary { background: #f5f5f5; padding: 10px; margin-bottom: 20px; }
870 .controls { margin-bottom: 10px; }
871 .controls input, select { margin-right: 10px; }
872 table { width: 100%; border-collapse: collapse; table-layout: fixed; }
873 th, td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; }
874 th { background-color: #f2f2f2; cursor: pointer; }
875 .name-col { width: 30%; }
876 .status-col { width: 8%; }
877 .actual-col { width: 8%; }
878 .expected-col { width: 8%; }
879 .error-col { width: 26%; }
880 .PASS { background-color: #d4edda; }
881 .FAIL { background-color: #f8d7da; }
882 .SKIP { background-color: #fff3cd; }
883 .error {
884 font-family: monospace;
885 font-size: 0.9em;
886 color: #721c24;
887 white-space: pre-wrap;
888 word-break: break-word;
889 }
890 </style>
891 </head>
892 <body>
893 <h1>BoGo Results Report</h1>
894
895 <div class="summary">
896 <strong>Generated:</strong> {{.Timestamp}} | <strong>BoGo Revision:</strong> {{.Revision}}<br>
897 {{range $status, $count := .Results.NumFailuresByType}}
898 <strong>{{$status}}:</strong> {{$count}} |
899 {{end}}
900 </div>
901
902 <div class="controls">
903 <input type="text" id="search" placeholder="Search tests..." onkeyup="filterTests()">
904 <select id="statusFilter" onchange="filterTests()">
905 <option value="">All</option>
906 <option value="FAIL">Failed</option>
907 <option value="PASS">Passed</option>
908 <option value="SKIP">Skipped</option>
909 </select>
910 </div>
911
912 <table id="resultsTable">
913 <thead>
914 <tr>
915 <th class="name-col" onclick="sortBy('name')">Test Name</th>
916 <th class="status-col" onclick="sortBy('status')">Status</th>
917 <th class="actual-col" onclick="sortBy('actual')">Actual</th>
918 <th class="expected-col" onclick="sortBy('expected')">Expected</th>
919 <th class="error-col">Error</th>
920 </tr>
921 </thead>
922 <tbody>
923 {{range $name, $test := .Results.Tests}}
924 <tr class="{{$test.Actual}}" data-name="{{$name}}" data-status="{{$test.Actual}}">
925 <td>{{$name}}</td>
926 <td>{{$test.Actual}}</td>
927 <td>{{$test.Actual}}</td>
928 <td>{{$test.Expected}}</td>
929 <td class="error">{{$test.Error}}</td>
930 </tr>
931 {{end}}
932 </tbody>
933 </table>
934
935 <script>
936 function filterTests() {
937 const search = document.getElementById('search').value.toLowerCase();
938 const status = document.getElementById('statusFilter').value;
939 const rows = document.querySelectorAll('#resultsTable tbody tr');
940
941 rows.forEach(row => {
942 const name = row.dataset.name.toLowerCase();
943 const rowStatus = row.dataset.status;
944 const matchesSearch = name.includes(search);
945 const matchesStatus = !status || rowStatus === status;
946
947 row.style.display = matchesSearch && matchesStatus ? '' : 'none';
948 });
949 }
950
951 function sortBy(column) {
952 const tbody = document.querySelector('#resultsTable tbody');
953 const rows = Array.from(tbody.querySelectorAll('tr'));
954
955 rows.sort((a, b) => {
956 if (column === 'status') {
957 const statusOrder = {'FAIL': 0, 'PASS': 1, 'SKIP': 2};
958 const aStatus = a.dataset.status;
959 const bStatus = b.dataset.status;
960 if (aStatus !== bStatus) {
961 return statusOrder[aStatus] - statusOrder[bStatus];
962 }
963 return a.dataset.name.localeCompare(b.dataset.name);
964 } else {
965 return a.dataset.name.localeCompare(b.dataset.name);
966 }
967 });
968
969 rows.forEach(row => tbody.appendChild(row));
970 filterTests();
971 }
972
973 sortBy("status");
974 </script>
975 </body>
976 </html>
977 `
978
View as plain text