Source file src/crypto/tls/bogo_shim_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     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  	ocspResponse = flagBase64("ocsp-response", "")
    41  	signingPrefs = flagIntSlice("signing-prefs", "")
    42  
    43  	trustCert = flag.String("trust-cert", "", "")
    44  
    45  	minVersion    = flag.Int("min-version", VersionSSL30, "")
    46  	maxVersion    = flag.Int("max-version", VersionTLS13, "")
    47  	expectVersion = flag.Int("expect-version", 0, "")
    48  
    49  	noTLS1  = flag.Bool("no-tls1", false, "")
    50  	noTLS11 = flag.Bool("no-tls11", false, "")
    51  	noTLS12 = flag.Bool("no-tls12", false, "")
    52  	noTLS13 = flag.Bool("no-tls13", false, "")
    53  
    54  	requireAnyClientCertificate = flag.Bool("require-any-client-certificate", false, "")
    55  
    56  	shimWritesFirst = flag.Bool("shim-writes-first", false, "")
    57  
    58  	resumeCount = flag.Int("resume-count", 0, "")
    59  
    60  	curves        = flagIntSlice("curves", "")
    61  	expectedCurve = flag.String("expect-curve-id", "", "")
    62  
    63  	verifyPrefs        = flagIntSlice("verify-prefs", "")
    64  	expectedSigAlg     = flag.String("expect-peer-signature-algorithm", "", "")
    65  	expectedPeerSigAlg = flagIntSlice("expect-peer-verify-pref", "")
    66  
    67  	shimID = flag.Uint64("shim-id", 0, "")
    68  	_      = flag.Bool("ipv6", false, "")
    69  
    70  	echConfigList              = flagBase64("ech-config-list", "")
    71  	expectECHAccepted          = flag.Bool("expect-ech-accept", false, "")
    72  	expectHRR                  = flag.Bool("expect-hrr", false, "")
    73  	expectNoHRR                = flag.Bool("expect-no-hrr", false, "")
    74  	expectedECHRetryConfigs    = flag.String("expect-ech-retry-configs", "", "")
    75  	expectNoECHRetryConfigs    = flag.Bool("expect-no-ech-retry-configs", false, "")
    76  	onInitialExpectECHAccepted = flag.Bool("on-initial-expect-ech-accept", false, "")
    77  	_                          = flag.Bool("expect-no-ech-name-override", false, "")
    78  	_                          = flag.String("expect-ech-name-override", "", "")
    79  	_                          = flag.Bool("reverify-on-resume", false, "")
    80  	onResumeECHConfigList      = flagBase64("on-resume-ech-config-list", "")
    81  	_                          = flag.Bool("on-resume-expect-reject-early-data", false, "")
    82  	onResumeExpectECHAccepted  = flag.Bool("on-resume-expect-ech-accept", false, "")
    83  	_                          = flag.Bool("on-resume-expect-no-ech-name-override", false, "")
    84  	expectedServerName         = flag.String("expect-server-name", "", "")
    85  	echServerConfig            = flagStringSlice("ech-server-config", "")
    86  	echServerKey               = flagStringSlice("ech-server-key", "")
    87  	echServerRetryConfig       = flagStringSlice("ech-is-retry-config", "")
    88  
    89  	expectSessionMiss = flag.Bool("expect-session-miss", false, "")
    90  
    91  	_ = flag.Bool("enable-early-data", false, "")
    92  	_ = flag.Bool("on-resume-expect-accept-early-data", false, "")
    93  	_ = flag.Bool("expect-ticket-supports-early-data", false, "")
    94  	_ = flag.Bool("on-resume-shim-writes-first", false, "")
    95  
    96  	advertiseALPN        = flag.String("advertise-alpn", "", "")
    97  	expectALPN           = flag.String("expect-alpn", "", "")
    98  	rejectALPN           = flag.Bool("reject-alpn", false, "")
    99  	declineALPN          = flag.Bool("decline-alpn", false, "")
   100  	expectAdvertisedALPN = flag.String("expect-advertised-alpn", "", "")
   101  	selectALPN           = flag.String("select-alpn", "", "")
   102  
   103  	hostName = flag.String("host-name", "", "")
   104  
   105  	verifyPeer = flag.Bool("verify-peer", false, "")
   106  	_          = flag.Bool("use-custom-verify-callback", false, "")
   107  
   108  	waitForDebugger = flag.Bool("wait-for-debugger", false, "")
   109  )
   110  
   111  type stringSlice []string
   112  
   113  func flagStringSlice(name, usage string) *stringSlice {
   114  	f := new(stringSlice)
   115  	flag.Var(f, name, usage)
   116  	return f
   117  }
   118  
   119  func (saf *stringSlice) String() string {
   120  	return strings.Join(*saf, ",")
   121  }
   122  
   123  func (saf *stringSlice) Set(s string) error {
   124  	*saf = append(*saf, s)
   125  	return nil
   126  }
   127  
   128  type intSlice []int64
   129  
   130  func flagIntSlice(name, usage string) *intSlice {
   131  	f := new(intSlice)
   132  	flag.Var(f, name, usage)
   133  	return f
   134  }
   135  
   136  func (sf *intSlice) String() string {
   137  	return strings.Join(strings.Split(fmt.Sprint(*sf), " "), ",")
   138  }
   139  
   140  func (sf *intSlice) Set(s string) error {
   141  	i, err := strconv.ParseInt(s, 10, 64)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	*sf = append(*sf, i)
   146  	return nil
   147  }
   148  
   149  type base64Flag []byte
   150  
   151  func flagBase64(name, usage string) *base64Flag {
   152  	f := new(base64Flag)
   153  	flag.Var(f, name, usage)
   154  	return f
   155  }
   156  
   157  func (f *base64Flag) String() string {
   158  	return base64.StdEncoding.EncodeToString(*f)
   159  }
   160  
   161  func (f *base64Flag) Set(s string) error {
   162  	if *f != nil {
   163  		return fmt.Errorf("multiple base64 values not supported")
   164  	}
   165  	b, err := base64.StdEncoding.DecodeString(s)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	*f = b
   170  	return nil
   171  }
   172  
   173  func bogoShim() {
   174  	if *isHandshakerSupported {
   175  		fmt.Println("No")
   176  		return
   177  	}
   178  
   179  	fmt.Printf("BoGo shim flags: %q", os.Args[1:])
   180  
   181  	// Test with both the default and insecure cipher suites.
   182  	var ciphersuites []uint16
   183  	for _, s := range append(CipherSuites(), InsecureCipherSuites()...) {
   184  		ciphersuites = append(ciphersuites, s.ID)
   185  	}
   186  
   187  	cfg := &Config{
   188  		ServerName: "test",
   189  
   190  		MinVersion: uint16(*minVersion),
   191  		MaxVersion: uint16(*maxVersion),
   192  
   193  		ClientSessionCache: NewLRUClientSessionCache(0),
   194  
   195  		CipherSuites: ciphersuites,
   196  
   197  		GetConfigForClient: func(chi *ClientHelloInfo) (*Config, error) {
   198  
   199  			if *expectAdvertisedALPN != "" {
   200  
   201  				s := cryptobyte.String(*expectAdvertisedALPN)
   202  
   203  				var expectedALPNs []string
   204  
   205  				for !s.Empty() {
   206  					var alpn cryptobyte.String
   207  					if !s.ReadUint8LengthPrefixed(&alpn) {
   208  						return nil, fmt.Errorf("unexpected error while parsing arguments for -expect-advertised-alpn")
   209  					}
   210  					expectedALPNs = append(expectedALPNs, string(alpn))
   211  				}
   212  
   213  				if !slices.Equal(chi.SupportedProtos, expectedALPNs) {
   214  					return nil, fmt.Errorf("unexpected ALPN: got %q, want %q", chi.SupportedProtos, expectedALPNs)
   215  				}
   216  			}
   217  			return nil, nil
   218  		},
   219  	}
   220  
   221  	if *noTLS1 {
   222  		cfg.MinVersion = VersionTLS11
   223  		if *noTLS11 {
   224  			cfg.MinVersion = VersionTLS12
   225  			if *noTLS12 {
   226  				cfg.MinVersion = VersionTLS13
   227  				if *noTLS13 {
   228  					log.Fatalf("no supported versions enabled")
   229  				}
   230  			}
   231  		}
   232  	} else if *noTLS13 {
   233  		cfg.MaxVersion = VersionTLS12
   234  		if *noTLS12 {
   235  			cfg.MaxVersion = VersionTLS11
   236  			if *noTLS11 {
   237  				cfg.MaxVersion = VersionTLS10
   238  				if *noTLS1 {
   239  					log.Fatalf("no supported versions enabled")
   240  				}
   241  			}
   242  		}
   243  	}
   244  
   245  	if *advertiseALPN != "" {
   246  		alpns := *advertiseALPN
   247  		for len(alpns) > 0 {
   248  			alpnLen := int(alpns[0])
   249  			cfg.NextProtos = append(cfg.NextProtos, alpns[1:1+alpnLen])
   250  			alpns = alpns[alpnLen+1:]
   251  		}
   252  	}
   253  
   254  	if *rejectALPN {
   255  		cfg.NextProtos = []string{"unnegotiableprotocol"}
   256  	}
   257  
   258  	if *declineALPN {
   259  		cfg.NextProtos = []string{}
   260  	}
   261  	if *selectALPN != "" {
   262  		cfg.NextProtos = []string{*selectALPN}
   263  	}
   264  
   265  	if *hostName != "" {
   266  		cfg.ServerName = *hostName
   267  	}
   268  
   269  	if *keyfile != "" || *certfile != "" {
   270  		pair, err := LoadX509KeyPair(*certfile, *keyfile)
   271  		if err != nil {
   272  			log.Fatalf("load key-file err: %s", err)
   273  		}
   274  		for _, id := range *signingPrefs {
   275  			pair.SupportedSignatureAlgorithms = append(pair.SupportedSignatureAlgorithms, SignatureScheme(id))
   276  		}
   277  		pair.OCSPStaple = *ocspResponse
   278  		// Use Get[Client]Certificate to force the use of the certificate, which
   279  		// more closely matches the BoGo expectations (e.g. handshake failure if
   280  		// no client certificates are compatible).
   281  		cfg.GetCertificate = func(chi *ClientHelloInfo) (*Certificate, error) {
   282  			if *expectedPeerSigAlg != nil {
   283  				if len(chi.SignatureSchemes) != len(*expectedPeerSigAlg) {
   284  					return nil, fmt.Errorf("unexpected signature algorithms: got %s, want %v", chi.SignatureSchemes, *expectedPeerSigAlg)
   285  				}
   286  				for i := range *expectedPeerSigAlg {
   287  					if chi.SignatureSchemes[i] != SignatureScheme((*expectedPeerSigAlg)[i]) {
   288  						return nil, fmt.Errorf("unexpected signature algorithms: got %s, want %v", chi.SignatureSchemes, *expectedPeerSigAlg)
   289  					}
   290  				}
   291  			}
   292  			return &pair, nil
   293  		}
   294  		cfg.GetClientCertificate = func(cri *CertificateRequestInfo) (*Certificate, error) {
   295  			if *expectedPeerSigAlg != nil {
   296  				if len(cri.SignatureSchemes) != len(*expectedPeerSigAlg) {
   297  					return nil, fmt.Errorf("unexpected signature algorithms: got %s, want %v", cri.SignatureSchemes, *expectedPeerSigAlg)
   298  				}
   299  				for i := range *expectedPeerSigAlg {
   300  					if cri.SignatureSchemes[i] != SignatureScheme((*expectedPeerSigAlg)[i]) {
   301  						return nil, fmt.Errorf("unexpected signature algorithms: got %s, want %v", cri.SignatureSchemes, *expectedPeerSigAlg)
   302  					}
   303  				}
   304  			}
   305  			return &pair, nil
   306  		}
   307  	}
   308  	if *trustCert != "" {
   309  		pool := x509.NewCertPool()
   310  		certFile, err := os.ReadFile(*trustCert)
   311  		if err != nil {
   312  			log.Fatalf("load trust-cert err: %s", err)
   313  		}
   314  		block, _ := pem.Decode(certFile)
   315  		cert, err := x509.ParseCertificate(block.Bytes)
   316  		if err != nil {
   317  			log.Fatalf("parse trust-cert err: %s", err)
   318  		}
   319  		pool.AddCert(cert)
   320  		cfg.RootCAs = pool
   321  	}
   322  
   323  	if *requireAnyClientCertificate {
   324  		cfg.ClientAuth = RequireAnyClientCert
   325  	}
   326  	if *verifyPeer {
   327  		cfg.ClientAuth = VerifyClientCertIfGiven
   328  	}
   329  
   330  	if *echConfigList != nil {
   331  		cfg.EncryptedClientHelloConfigList = *echConfigList
   332  		cfg.MinVersion = VersionTLS13
   333  	}
   334  
   335  	if *curves != nil {
   336  		for _, id := range *curves {
   337  			cfg.CurvePreferences = append(cfg.CurvePreferences, CurveID(id))
   338  		}
   339  	}
   340  
   341  	if *verifyPrefs != nil {
   342  		for _, id := range *verifyPrefs {
   343  			testingOnlySupportedSignatureAlgorithms = append(testingOnlySupportedSignatureAlgorithms, SignatureScheme(id))
   344  		}
   345  	}
   346  
   347  	if *echServerConfig != nil {
   348  		if len(*echServerConfig) != len(*echServerKey) || len(*echServerConfig) != len(*echServerRetryConfig) {
   349  			log.Fatal("-ech-server-config, -ech-server-key, and -ech-is-retry-config mismatch")
   350  		}
   351  
   352  		for i, c := range *echServerConfig {
   353  			configBytes, err := base64.StdEncoding.DecodeString(c)
   354  			if err != nil {
   355  				log.Fatalf("parse ech-server-config err: %s", err)
   356  			}
   357  			privBytes, err := base64.StdEncoding.DecodeString((*echServerKey)[i])
   358  			if err != nil {
   359  				log.Fatalf("parse ech-server-key err: %s", err)
   360  			}
   361  
   362  			cfg.EncryptedClientHelloKeys = append(cfg.EncryptedClientHelloKeys, EncryptedClientHelloKey{
   363  				Config:      configBytes,
   364  				PrivateKey:  privBytes,
   365  				SendAsRetry: (*echServerRetryConfig)[i] == "1",
   366  			})
   367  		}
   368  	}
   369  
   370  	for i := 0; i < *resumeCount+1; i++ {
   371  		if i > 0 && *onResumeECHConfigList != nil {
   372  			cfg.EncryptedClientHelloConfigList = *onResumeECHConfigList
   373  		}
   374  
   375  		conn, err := net.Dial("tcp", net.JoinHostPort("localhost", *port))
   376  		if err != nil {
   377  			log.Fatalf("dial err: %s", err)
   378  		}
   379  		defer conn.Close()
   380  
   381  		// Write the shim ID we were passed as a little endian uint64
   382  		shimIDBytes := make([]byte, 8)
   383  		byteorder.LEPutUint64(shimIDBytes, *shimID)
   384  		if _, err := conn.Write(shimIDBytes); err != nil {
   385  			log.Fatalf("failed to write shim id: %s", err)
   386  		}
   387  
   388  		var tlsConn *Conn
   389  		if *server {
   390  			tlsConn = Server(conn, cfg)
   391  		} else {
   392  			tlsConn = Client(conn, cfg)
   393  		}
   394  
   395  		if i == 0 && *shimWritesFirst {
   396  			if _, err := tlsConn.Write([]byte("hello")); err != nil {
   397  				log.Fatalf("write err: %s", err)
   398  			}
   399  		}
   400  
   401  		// If we were instructed to wait for a debugger, then send SIGSTOP to ourselves.
   402  		// When the debugger attaches it will continue the process.
   403  		if *waitForDebugger {
   404  			pauseProcess()
   405  		}
   406  
   407  		for {
   408  			buf := make([]byte, 500)
   409  			var n int
   410  			n, err = tlsConn.Read(buf)
   411  			if err != nil {
   412  				break
   413  			}
   414  			buf = buf[:n]
   415  			for i := range buf {
   416  				buf[i] ^= 0xff
   417  			}
   418  			if _, err = tlsConn.Write(buf); err != nil {
   419  				break
   420  			}
   421  		}
   422  		if err != io.EOF {
   423  			// Flush the TLS conn and then perform a graceful shutdown of the
   424  			// TCP connection to avoid the runner side hitting an unexpected
   425  			// write error before it has processed the alert we may have
   426  			// generated for the error condition.
   427  			orderlyShutdown(tlsConn)
   428  
   429  			retryErr, ok := err.(*ECHRejectionError)
   430  			if !ok {
   431  				log.Fatal(err)
   432  			}
   433  			if *expectNoECHRetryConfigs && len(retryErr.RetryConfigList) > 0 {
   434  				log.Fatalf("expected no ECH retry configs, got some")
   435  			}
   436  			if *expectedECHRetryConfigs != "" {
   437  				expectedRetryConfigs, err := base64.StdEncoding.DecodeString(*expectedECHRetryConfigs)
   438  				if err != nil {
   439  					log.Fatalf("failed to decode expected retry configs: %s", err)
   440  				}
   441  				if !bytes.Equal(retryErr.RetryConfigList, expectedRetryConfigs) {
   442  					log.Fatalf("unexpected retry list returned: got %x, want %x", retryErr.RetryConfigList, expectedRetryConfigs)
   443  				}
   444  			}
   445  			log.Fatalf("conn error: %s", err)
   446  		}
   447  
   448  		cs := tlsConn.ConnectionState()
   449  		if cs.HandshakeComplete {
   450  			if *expectALPN != "" && cs.NegotiatedProtocol != *expectALPN {
   451  				log.Fatalf("unexpected protocol negotiated: want %q, got %q", *expectALPN, cs.NegotiatedProtocol)
   452  			}
   453  
   454  			if *selectALPN != "" && cs.NegotiatedProtocol != *selectALPN {
   455  				log.Fatalf("unexpected protocol negotiated: want %q, got %q", *selectALPN, cs.NegotiatedProtocol)
   456  			}
   457  
   458  			if *expectVersion != 0 && cs.Version != uint16(*expectVersion) {
   459  				log.Fatalf("expected ssl version %q, got %q", uint16(*expectVersion), cs.Version)
   460  			}
   461  			if *declineALPN && cs.NegotiatedProtocol != "" {
   462  				log.Fatal("unexpected ALPN protocol")
   463  			}
   464  			if *expectECHAccepted && !cs.ECHAccepted {
   465  				log.Fatal("expected ECH to be accepted, but connection state shows it was not")
   466  			} else if i == 0 && *onInitialExpectECHAccepted && !cs.ECHAccepted {
   467  				log.Fatal("expected ECH to be accepted, but connection state shows it was not")
   468  			} else if i > 0 && *onResumeExpectECHAccepted && !cs.ECHAccepted {
   469  				log.Fatal("expected ECH to be accepted on resumption, but connection state shows it was not")
   470  			} else if i == 0 && !*expectECHAccepted && cs.ECHAccepted {
   471  				log.Fatal("did not expect ECH, but it was accepted")
   472  			}
   473  
   474  			if *expectHRR && !cs.testingOnlyDidHRR {
   475  				log.Fatal("expected HRR but did not do it")
   476  			}
   477  
   478  			if *expectNoHRR && cs.testingOnlyDidHRR {
   479  				log.Fatal("expected no HRR but did do it")
   480  			}
   481  
   482  			if *expectSessionMiss && cs.DidResume {
   483  				log.Fatal("unexpected session resumption")
   484  			}
   485  
   486  			if *expectedServerName != "" && cs.ServerName != *expectedServerName {
   487  				log.Fatalf("unexpected server name: got %q, want %q", cs.ServerName, *expectedServerName)
   488  			}
   489  		}
   490  
   491  		if *expectedCurve != "" {
   492  			expectedCurveID, err := strconv.Atoi(*expectedCurve)
   493  			if err != nil {
   494  				log.Fatalf("failed to parse -expect-curve-id: %s", err)
   495  			}
   496  			if cs.CurveID != CurveID(expectedCurveID) {
   497  				log.Fatalf("unexpected curve id: want %d, got %d", expectedCurveID, tlsConn.curveID)
   498  			}
   499  		}
   500  
   501  		// TODO: implement testingOnlyPeerSignatureAlgorithm on resumption.
   502  		if *expectedSigAlg != "" && !cs.DidResume {
   503  			expectedSigAlgID, err := strconv.Atoi(*expectedSigAlg)
   504  			if err != nil {
   505  				log.Fatalf("failed to parse -expect-peer-signature-algorithm: %s", err)
   506  			}
   507  			if cs.testingOnlyPeerSignatureAlgorithm != SignatureScheme(expectedSigAlgID) {
   508  				log.Fatalf("unexpected peer signature algorithm: want %s, got %s", SignatureScheme(expectedSigAlgID), cs.testingOnlyPeerSignatureAlgorithm)
   509  			}
   510  		}
   511  	}
   512  }
   513  
   514  // If the test case produces an error, we don't want to immediately close the
   515  // TCP connection after generating an alert. The runner side may try to write
   516  // additional data to the connection before it reads the alert. If the conn
   517  // has already been torn down, then these writes will produce an unexpected
   518  // broken pipe err and fail the test.
   519  func orderlyShutdown(tlsConn *Conn) {
   520  	// Flush any pending alert data
   521  	tlsConn.flush()
   522  
   523  	netConn := tlsConn.NetConn()
   524  	tcpConn := netConn.(*net.TCPConn)
   525  	tcpConn.CloseWrite()
   526  
   527  	// Read and discard any data that was sent by the peer.
   528  	buf := make([]byte, maxPlaintext)
   529  	for {
   530  		n, err := tcpConn.Read(buf)
   531  		if n == 0 || err != nil {
   532  			break
   533  		}
   534  	}
   535  
   536  	tcpConn.CloseRead()
   537  }
   538  
   539  func TestBogoSuite(t *testing.T) {
   540  	if testing.Short() {
   541  		t.Skip("skipping in short mode")
   542  	}
   543  	if testenv.Builder() != "" && runtime.GOOS == "windows" {
   544  		t.Skip("#66913: windows network connections are flakey on builders")
   545  	}
   546  	skipFIPS(t)
   547  
   548  	// In order to make Go test caching work as expected, we stat the
   549  	// bogo_config.json file, so that the Go testing hooks know that it is
   550  	// important for this test and will invalidate a cached test result if the
   551  	// file changes.
   552  	if _, err := os.Stat("bogo_config.json"); err != nil {
   553  		t.Fatal(err)
   554  	}
   555  
   556  	var bogoDir string
   557  	if *bogoLocalDir != "" {
   558  		bogoDir = *bogoLocalDir
   559  	} else {
   560  		const boringsslModVer = "v0.0.0-20250620172916-f51d8b099832"
   561  		bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
   562  	}
   563  
   564  	cwd, err := os.Getwd()
   565  	if err != nil {
   566  		t.Fatal(err)
   567  	}
   568  
   569  	resultsFile := filepath.Join(t.TempDir(), "results.json")
   570  
   571  	args := []string{
   572  		"test",
   573  		".",
   574  		fmt.Sprintf("-shim-config=%s", filepath.Join(cwd, "bogo_config.json")),
   575  		fmt.Sprintf("-shim-path=%s", os.Args[0]),
   576  		"-shim-extra-flags=-bogo-mode",
   577  		"-allow-unimplemented",
   578  		"-loose-errors", // TODO(roland): this should be removed eventually
   579  		fmt.Sprintf("-json-output=%s", resultsFile),
   580  	}
   581  	if *bogoFilter != "" {
   582  		args = append(args, fmt.Sprintf("-test=%s", *bogoFilter))
   583  	}
   584  
   585  	cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
   586  	out := &strings.Builder{}
   587  	cmd.Stderr = out
   588  	cmd.Dir = filepath.Join(bogoDir, "ssl/test/runner")
   589  	err = cmd.Run()
   590  	// NOTE: we don't immediately check the error, because the failure could be either because
   591  	// the runner failed for some unexpected reason, or because a test case failed, and we
   592  	// cannot easily differentiate these cases. We check if the JSON results file was written,
   593  	// which should only happen if the failure was because of a test failure, and use that
   594  	// to determine the failure mode.
   595  
   596  	resultsJSON, jsonErr := os.ReadFile(resultsFile)
   597  	if jsonErr != nil {
   598  		if err != nil {
   599  			t.Fatalf("bogo failed: %s\n%s", err, out)
   600  		}
   601  		t.Fatalf("failed to read results JSON file: %s", jsonErr)
   602  	}
   603  
   604  	var results bogoResults
   605  	if err := json.Unmarshal(resultsJSON, &results); err != nil {
   606  		t.Fatalf("failed to parse results JSON: %s", err)
   607  	}
   608  
   609  	// assertResults contains test results we want to make sure
   610  	// are present in the output. They are only checked if -bogo-filter
   611  	// was not passed.
   612  	assertResults := map[string]string{
   613  		"CurveTest-Client-MLKEM-TLS13": "PASS",
   614  		"CurveTest-Server-MLKEM-TLS13": "PASS",
   615  
   616  		// Various signature algorithm tests checking that we enforce our
   617  		// preferences on the peer.
   618  		"ClientAuth-Enforced":                    "PASS",
   619  		"ServerAuth-Enforced":                    "PASS",
   620  		"ClientAuth-Enforced-TLS13":              "PASS",
   621  		"ServerAuth-Enforced-TLS13":              "PASS",
   622  		"VerifyPreferences-Advertised":           "PASS",
   623  		"VerifyPreferences-Enforced":             "PASS",
   624  		"Client-TLS12-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
   625  		"Server-TLS12-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
   626  		"Client-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
   627  		"Server-TLS13-NoSign-RSA_PKCS1_MD5_SHA1": "PASS",
   628  	}
   629  
   630  	for name, result := range results.Tests {
   631  		// This is not really the intended way to do this... but... it works?
   632  		t.Run(name, func(t *testing.T) {
   633  			if result.Actual == "FAIL" && result.IsUnexpected {
   634  				t.Fail()
   635  			}
   636  			if result.Error != "" {
   637  				t.Log(result.Error)
   638  			}
   639  			if exp, ok := assertResults[name]; ok && exp != result.Actual {
   640  				t.Errorf("unexpected result: got %s, want %s", result.Actual, exp)
   641  			}
   642  			delete(assertResults, name)
   643  			if result.Actual == "SKIP" {
   644  				t.SkipNow()
   645  			}
   646  		})
   647  	}
   648  	if *bogoFilter == "" {
   649  		// Anything still in assertResults did not show up in the results, so we should fail
   650  		for name, expectedResult := range assertResults {
   651  			t.Run(name, func(t *testing.T) {
   652  				t.Fatalf("expected test to run with result %s, but it was not present in the test results", expectedResult)
   653  			})
   654  		}
   655  	}
   656  }
   657  
   658  // bogoResults is a copy of boringssl.googlesource.com/boringssl/testresults.Results
   659  type bogoResults struct {
   660  	Version           int            `json:"version"`
   661  	Interrupted       bool           `json:"interrupted"`
   662  	PathDelimiter     string         `json:"path_delimiter"`
   663  	SecondsSinceEpoch float64        `json:"seconds_since_epoch"`
   664  	NumFailuresByType map[string]int `json:"num_failures_by_type"`
   665  	Tests             map[string]struct {
   666  		Actual       string `json:"actual"`
   667  		Expected     string `json:"expected"`
   668  		IsUnexpected bool   `json:"is_unexpected"`
   669  		Error        string `json:"error,omitempty"`
   670  	} `json:"tests"`
   671  }
   672  

View as plain text