// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package tls import ( "bufio" "bytes" "context" "crypto/internal/boring" "encoding/hex" "errors" "flag" "fmt" "internal/testenv" "io" "net" "os" "os/exec" "runtime" "strconv" "strings" "sync" "testing" "testing/cryptotest" "time" ) // TLS reference tests run a connection against a reference implementation // (OpenSSL) of TLS and record the bytes of the resulting connection. The Go // code, during a test, is configured with deterministic randomness and so the // reference test can be reproduced exactly in the future. // // In order to save everyone who wishes to run the tests from needing the // reference implementation installed, the reference connections are saved in // files in the testdata directory. Thus running the tests involves nothing // external, but creating and updating them requires the reference // implementation. // // Tests can be updated by running them with the -update flag. This will cause // the test files for failing tests to be regenerated. Since the reference // implementation will always generate fresh random numbers, large parts of the // reference connection will always change. var ( update = flag.Bool("update", false, "update golden files on failure") keyFile = flag.String("keylog", "", "destination file for KeyLogWriter") bogoMode = flag.Bool("bogo-mode", false, "Enabled bogo shim mode, ignore everything else") bogoFilter = flag.String("bogo-filter", "", "BoGo test filter") bogoLocalDir = flag.String("bogo-local-dir", "", "If not-present, checkout BoGo into this dir, or otherwise use it as a pre-existing checkout") bogoReport = flag.String("bogo-html-report", "", "File path to render an HTML report with BoGo results") ) func runTestAndUpdateIfNeeded(t *testing.T, name string, run func(t *testing.T, update bool)) { skipFIPS(t) // FIPS 140-3 mode changes the advertised parameters. // Go+BoringCrypto's boring.RandReader ignores the testing override set by // cryptotest.SetGlobalRandom, so e.g. ECDH key generation would be // non-deterministic. Setting cryptocustomrand=1 makes rand.CustomReader // pass the caller's reader (the testing source) through instead. if boring.Enabled { testenv.SetGODEBUG(t, "cryptocustomrand=1") } success := t.Run(name, func(t *testing.T) { cryptotest.SetGlobalRandom(t, 0) run(t, false) }) if !success && *update { t.Run(name+"#update", func(t *testing.T) { cryptotest.SetGlobalRandom(t, 0) run(t, true) }) } } // checkOpenSSLVersion ensures that the version of OpenSSL looks reasonable // before updating the test data. func checkOpenSSLVersion() error { if !*update { return nil } openssl := exec.Command("openssl", "version") output, err := openssl.CombinedOutput() if err != nil { return err } version := string(output) if strings.HasPrefix(version, "OpenSSL 1.1.1") { return nil } println("***********************************************") println("") println("You need to build OpenSSL 1.1.1 from source in order") println("to update the test data.") println("") println("Configure it with:") println("./Configure enable-weak-ssl-ciphers no-shared") println("and then add the apps/ directory at the front of your PATH.") println("***********************************************") return errors.New("version of OpenSSL does not appear to be suitable for updating test data") } // recordingConn is a net.Conn that records the traffic that passes through it. // WriteTo can be used to produce output that can be later be loaded with // ParseTestData. type recordingConn struct { net.Conn sync.Mutex flows [][]byte reading bool } func (r *recordingConn) Read(b []byte) (n int, err error) { if n, err = r.Conn.Read(b); n == 0 { return } b = b[:n] r.Lock() defer r.Unlock() if l := len(r.flows); l == 0 || !r.reading { buf := make([]byte, len(b)) copy(buf, b) r.flows = append(r.flows, buf) } else { r.flows[l-1] = append(r.flows[l-1], b[:n]...) } r.reading = true return } func (r *recordingConn) Write(b []byte) (n int, err error) { if n, err = r.Conn.Write(b); n == 0 { return } b = b[:n] r.Lock() defer r.Unlock() if l := len(r.flows); l == 0 || r.reading { buf := make([]byte, len(b)) copy(buf, b) r.flows = append(r.flows, buf) } else { r.flows[l-1] = append(r.flows[l-1], b[:n]...) } r.reading = false return } // WriteTo writes Go source code to w that contains the recorded traffic. func (r *recordingConn) WriteTo(w io.Writer) (int64, error) { // TLS always starts with a client to server flow. clientToServer := true var written int64 for i, flow := range r.flows { source, dest := "client", "server" if !clientToServer { source, dest = dest, source } n, err := fmt.Fprintf(w, ">>> Flow %d (%s to %s)\n", i+1, source, dest) written += int64(n) if err != nil { return written, err } dumper := hex.Dumper(w) n, err = dumper.Write(flow) written += int64(n) if err != nil { return written, err } err = dumper.Close() if err != nil { return written, err } clientToServer = !clientToServer } return written, nil } func parseTestData(r io.Reader) (flows [][]byte, err error) { var currentFlow []byte scanner := bufio.NewScanner(r) for scanner.Scan() { line := scanner.Text() // If the line starts with ">>> " then it marks the beginning // of a new flow. if strings.HasPrefix(line, ">>> ") { if len(currentFlow) > 0 || len(flows) > 0 { flows = append(flows, currentFlow) currentFlow = nil } continue } // Otherwise the line is a line of hex dump that looks like: // 00000170 fc f5 06 bf (...) |.....X{&?......!| // (Some bytes have been omitted from the middle section.) _, after, ok := strings.Cut(line, " ") if !ok { return nil, errors.New("invalid test data") } line = after before, _, ok := strings.Cut(line, "|") if !ok { return nil, errors.New("invalid test data") } line = before hexBytes := strings.Fields(line) for _, hexByte := range hexBytes { val, err := strconv.ParseUint(hexByte, 16, 8) if err != nil { return nil, errors.New("invalid hex byte in test data: " + err.Error()) } currentFlow = append(currentFlow, byte(val)) } } if len(currentFlow) > 0 { flows = append(flows, currentFlow) } return flows, nil } // replayingConn is a net.Conn that replays flows recorded by recordingConn. type replayingConn struct { t testing.TB sync.Mutex flows [][]byte reading bool } var _ net.Conn = (*replayingConn)(nil) func (r *replayingConn) Read(b []byte) (n int, err error) { r.Lock() defer r.Unlock() if !r.reading { r.t.Errorf("expected write, got read") return 0, fmt.Errorf("recording expected write, got read") } n = copy(b, r.flows[0]) r.flows[0] = r.flows[0][n:] if len(r.flows[0]) == 0 { r.flows = r.flows[1:] if len(r.flows) == 0 { return n, io.EOF } else { r.reading = false } } return n, nil } func (r *replayingConn) Write(b []byte) (n int, err error) { r.Lock() defer r.Unlock() if r.reading { r.t.Errorf("expected read, got write") return 0, fmt.Errorf("recording expected read, got write") } if !bytes.HasPrefix(r.flows[0], b) { r.t.Errorf("write mismatch: expected %x, got %x", r.flows[0], b) return 0, fmt.Errorf("write mismatch") } r.flows[0] = r.flows[0][len(b):] if len(r.flows[0]) == 0 { r.flows = r.flows[1:] r.reading = true } return len(b), nil } func (r *replayingConn) Close() error { r.Lock() defer r.Unlock() if len(r.flows) > 0 { r.t.Errorf("closed with unfinished flows") return fmt.Errorf("unexpected close") } return nil } func (r *replayingConn) LocalAddr() net.Addr { return nil } func (r *replayingConn) RemoteAddr() net.Addr { return nil } func (r *replayingConn) SetDeadline(t time.Time) error { return nil } func (r *replayingConn) SetReadDeadline(t time.Time) error { return nil } func (r *replayingConn) SetWriteDeadline(t time.Time) error { return nil } // tempFile creates a temp file containing contents and returns its path. func tempFile(contents string) string { file, err := os.CreateTemp("", "go-tls-test") if err != nil { panic("failed to create temp file: " + err.Error()) } path := file.Name() file.WriteString(contents) file.Close() return path } // localListener is set up by TestMain and used by localPipe to create Conn // pairs like net.Pipe, but connected by an actual buffered TCP connection. var localListener struct { mu sync.Mutex addr net.Addr ch chan net.Conn } const localFlakes = 0 // change to 1 or 2 to exercise localServer/localPipe handling of mismatches func localServer(l net.Listener) { for n := 0; ; n++ { c, err := l.Accept() if err != nil { return } if localFlakes == 1 && n%2 == 0 { c.Close() continue } localListener.ch <- c } } var isConnRefused = func(err error) bool { return false } func localPipe(t testing.TB) (net.Conn, net.Conn) { localListener.mu.Lock() defer localListener.mu.Unlock() addr := localListener.addr var err error Dialing: // We expect a rare mismatch, but probably not 5 in a row. for i := 0; i < 5; i++ { tooSlow := time.NewTimer(1 * time.Second) defer tooSlow.Stop() var c1 net.Conn c1, err = net.Dial(addr.Network(), addr.String()) if err != nil { if runtime.GOOS == "dragonfly" && (isConnRefused(err) || os.IsTimeout(err)) { // golang.org/issue/29583: Dragonfly sometimes returns a spurious // ECONNREFUSED or ETIMEDOUT. <-tooSlow.C continue } t.Fatalf("localPipe: %v", err) } if localFlakes == 2 && i == 0 { c1.Close() continue } for { select { case <-tooSlow.C: t.Logf("localPipe: timeout waiting for %v", c1.LocalAddr()) c1.Close() continue Dialing case c2 := <-localListener.ch: if c2.RemoteAddr().String() == c1.LocalAddr().String() { t.Cleanup(func() { c1.Close() }) t.Cleanup(func() { c2.Close() }) return c1, c2 } t.Logf("localPipe: unexpected connection: %v != %v", c2.RemoteAddr(), c1.LocalAddr()) c2.Close() } } } t.Fatalf("localPipe: failed to connect: %v", err) panic("unreachable") } func TestMain(m *testing.M) { flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args) flag.PrintDefaults() if *bogoMode { os.Exit(89) } } flag.Parse() if *bogoMode { bogoShim() os.Exit(0) } os.Exit(runMain(m)) } func runMain(m *testing.M) int { // Cipher suites preferences change based on the architecture. Force them to // the version without AES acceleration for test consistency. hasAESGCMHardwareSupport = false // Set up localPipe. l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { l, err = net.Listen("tcp6", "[::1]:0") } if err != nil { fmt.Fprintf(os.Stderr, "Failed to open local listener: %v", err) os.Exit(1) } localListener.ch = make(chan net.Conn) localListener.addr = l.Addr() defer l.Close() go localServer(l) if err := checkOpenSSLVersion(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v", err) os.Exit(1) } rootCAPath := tempFile(testRootCertPEM) defer os.Remove(rootCAPath) defaultClientCommand = []string{"openssl", "s_client", "-no_ticket", "-verify", "1", "-verify_return_error", "-CAfile", rootCAPath, "-servername", "test.golang.example", "-attime", fmt.Sprint(testTime().Unix())} clientRootCAPath := tempFile(testClientRootCertPEM) defer os.Remove(clientRootCAPath) serverCommand = []string{"openssl", "s_server", "-no_ticket", "-num_tickets", "0", "-naccept", "1", "-verify_return_error", "-verifyCAfile", clientRootCAPath, "-attime", fmt.Sprint(testTime().Unix())} if *keyFile != "" { f, err := os.OpenFile(*keyFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { panic("failed to open -keylog file: " + err.Error()) } testConfigClient.KeyLogWriter = f testConfigServer.KeyLogWriter = f testConfigFIPS140.KeyLogWriter = f defer f.Close() } return m.Run() } func testHandshake(t *testing.T, clientConfig, serverConfig *Config) (serverState, clientState ConnectionState, err error) { const sentinel = "SENTINEL\n" c, s := localPipe(t) errChan := make(chan error, 1) go func() { cli := Client(c, clientConfig) err := cli.Handshake() if err != nil { errChan <- fmt.Errorf("client: %v", err) c.Close() return } clientState = cli.ConnectionState() buf, err := io.ReadAll(cli) if err != nil { if serverConfig.ClientAuth != NoClientCert && clientState.Version == VersionTLS13 { // In TLS 1.3, client certificates are sent after the server's // handshake has completed, and the client only learns about it // reading the alert after the handshake. errChan <- fmt.Errorf("client (from Read): %v", err) c.Close() return } else { t.Errorf("failed to call cli.Read: %v", err) } } defer func() { errChan <- nil }() if got := string(buf); got != sentinel { t.Errorf("read %q from TLS connection, but expected %q", got, sentinel) } // We discard the error because after ReadAll returns the server must // have already closed the connection. Sending data (the closeNotify // alert) can cause a reset, that will make Close return an error. cli.Close() }() server := Server(s, serverConfig) err = server.Handshake() if err == nil { serverState = server.ConnectionState() if _, err := io.WriteString(server, sentinel); err != nil { t.Errorf("failed to call server.Write: %v", err) } if err := server.Close(); err != nil { t.Errorf("failed to call server.Close: %v", err) } } else { err = fmt.Errorf("server: %v", err) s.Close() } err = errors.Join(err, <-errChan) return } func fromHex(s string) []byte { b, _ := hex.DecodeString(s) return b } func TestServerHelloTrailingMessage(t *testing.T) { // In TLS 1.3 the change cipher spec message is optional. If a CCS message // is not sent, after reading the ServerHello, the read traffic secret is // set, and all following messages must be encrypted. If the server sends // additional unencrypted messages in a record with the ServerHello, the // client must either fail or ignore the additional messages. c, s := localPipe(t) go func() { ctx := context.Background() srv := Server(s, testConfigServer.Clone()) clientHello, _, err := srv.readClientHello(ctx) if err != nil { testFatal(t, err) } hs := serverHandshakeStateTLS13{ c: srv, ctx: ctx, clientHello: clientHello, } if err := hs.processClientHello(); err != nil { testFatal(t, err) } if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil { testFatal(t, err) } record, err := concatHandshakeMessages(hs.hello, &encryptedExtensionsMsg{alpnProtocol: "h2"}) if err != nil { testFatal(t, err) } if _, err := s.Write(record); err != nil { testFatal(t, err) } srv.Close() }() cli := Client(c, testConfigClient.Clone()) expectedErr := "tls: handshake buffer not empty before setting read traffic secret" if err := cli.Handshake(); err == nil { t.Fatal("expected error from incomplete handshake, got nil") } else if err.Error() != expectedErr { t.Fatalf("expected error %q, got %q", expectedErr, err.Error()) } } func TestClientHelloTrailingMessage(t *testing.T) { // Same as TestServerHelloTrailingMessage but for the client side. c, s := localPipe(t) go func() { cli := Client(c, testConfigClient.Clone()) hello, _, _, err := cli.makeClientHello() if err != nil { testFatal(t, err) } record, err := concatHandshakeMessages(hello, &certificateMsgTLS13{}) if err != nil { testFatal(t, err) } if _, err := c.Write(record); err != nil { testFatal(t, err) } cli.Close() }() srv := Server(s, testConfigServer.Clone()) expectedErr := "tls: handshake buffer not empty before setting read traffic secret" if err := srv.Handshake(); err == nil { t.Fatal("expected error from incomplete handshake, got nil") } else if err.Error() != expectedErr { t.Fatalf("expected error %q, got %q", expectedErr, err.Error()) } } func TestDoubleClientHelloHRR(t *testing.T) { // If a client sends two ClientHello messages in a single record, and the // server sends a HRR after reading the first ClientHello, the server must // either fail or ignore the trailing ClientHello. c, s := localPipe(t) go func() { cli := Client(c, testConfigClient.Clone()) hello, _, _, err := cli.makeClientHello() if err != nil { testFatal(t, err) } hello.keyShares = nil record, err := concatHandshakeMessages(hello, hello) if err != nil { testFatal(t, err) } if _, err := c.Write(record); err != nil { testFatal(t, err) } cli.Close() }() srv := Server(s, testConfigServer.Clone()) expectedErr := "tls: handshake buffer not empty before HelloRetryRequest" if err := srv.Handshake(); err == nil { t.Fatal("expected error from incomplete handshake, got nil") } else if err.Error() != expectedErr { t.Fatalf("expected error %q, got %q", expectedErr, err.Error()) } } // concatHandshakeMessages marshals and concatenates the given handshake // messages into a single record. func concatHandshakeMessages(msgs ...handshakeMessage) ([]byte, error) { var marshalled []byte for _, msg := range msgs { data, err := msg.marshal() if err != nil { return nil, err } marshalled = append(marshalled, data...) } m := len(marshalled) outBuf := make([]byte, recordHeaderLen) outBuf[0] = byte(recordTypeHandshake) vers := VersionTLS12 outBuf[1] = byte(vers >> 8) outBuf[2] = byte(vers) outBuf[3] = byte(m >> 8) outBuf[4] = byte(m) outBuf = append(outBuf, marshalled...) return outBuf, nil } func TestMultipleKeyUpdate(t *testing.T) { for _, requestUpdate := range []bool{true, false} { t.Run(fmt.Sprintf("requestUpdate=%t", requestUpdate), func(t *testing.T) { c, s := localPipe(t) clientConfig := testConfigClient.Clone() clientConfig.MinVersion = VersionTLS13 clientConfig.MaxVersion = VersionTLS13 serverConfig := testConfigServer.Clone() serverConfig.MinVersion = VersionTLS13 serverConfig.MaxVersion = VersionTLS13 client := Client(c, clientConfig) server := Server(s, serverConfig) clientHandshakeDone := make(chan struct{}) go func() { if err := client.Handshake(); err != nil { } close(clientHandshakeDone) io.Copy(io.Discard, server) }() if err := server.Handshake(); err != nil { t.Fatalf("server handshake failed: %v\n", err) } <-clientHandshakeDone c.SetReadDeadline(time.Now().Add(1 * time.Second)) s.SetReadDeadline(time.Now().Add(1 * time.Second)) kuMsg, err := (&keyUpdateMsg{updateRequested: requestUpdate}).marshal() if err != nil { t.Fatalf("failed to marshal key update message: %v", err) } client.out.Lock() if _, err := client.writeRecordLocked(recordTypeHandshake, append(kuMsg, kuMsg...)); err != nil { t.Fatalf("failed to write key update messages: %v", err) } client.out.Unlock() _, err = io.Copy(io.Discard, client) if err == nil { t.Fatal("expected multiple key update messages to cause an error, got nil") } else if !strings.HasSuffix(err.Error(), "tls: unexpected message") { t.Fatalf("unexpected error: %v", err) } }) } }