]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/crypto/tls/tls_test.go
crypto/tls: change default minimum version to 1.2
[gostls13.git] / src / crypto / tls / tls_test.go
index e9abe01280fd21ab0b48d6b2eecfbdc822accf39..16f655dd93dbebcca445931aa1e4b64e5eb39b8b 100644 (file)
@@ -6,17 +6,19 @@ package tls
 
 import (
        "bytes"
+       "context"
+       "crypto"
        "crypto/x509"
        "encoding/json"
        "errors"
        "fmt"
        "internal/testenv"
        "io"
-       "io/ioutil"
        "math"
        "net"
        "os"
        "reflect"
+       "sort"
        "strings"
        "testing"
        "time"
@@ -36,7 +38,7 @@ r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
 -----END CERTIFICATE-----
 `
 
-var rsaKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
+var rsaKeyPEM = testingKey(`-----BEGIN RSA TESTING KEY-----
 MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
 k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
 6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
@@ -44,12 +46,12 @@ MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
 SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
 xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
 D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
------END RSA PRIVATE KEY-----
-`
+-----END RSA TESTING KEY-----
+`)
 
 // keyPEM is the same as rsaKeyPEM, but declares itself as just
 // "PRIVATE KEY", not "RSA PRIVATE KEY".  https://golang.org/issue/4477
-var keyPEM = `-----BEGIN PRIVATE KEY-----
+var keyPEM = testingKey(`-----BEGIN TESTING KEY-----
 MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
 k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
 6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
@@ -57,8 +59,8 @@ MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
 SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
 xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
 D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
------END PRIVATE KEY-----
-`
+-----END TESTING KEY-----
+`)
 
 var ecdsaCertPEM = `-----BEGIN CERTIFICATE-----
 MIIB/jCCAWICCQDscdUxw16XFDAJBgcqhkjOPQQBMEUxCzAJBgNVBAYTAkFVMRMw
@@ -75,17 +77,17 @@ H5jBImIxPL4WxQNiBTexAkF8D1EtpYuWdlVQ80/h/f4pBcGiXPqX5h2PQSQY7hP1
 -----END CERTIFICATE-----
 `
 
-var ecdsaKeyPEM = `-----BEGIN EC PARAMETERS-----
+var ecdsaKeyPEM = testingKey(`-----BEGIN EC PARAMETERS-----
 BgUrgQQAIw==
 -----END EC PARAMETERS-----
------BEGIN EC PRIVATE KEY-----
+-----BEGIN EC TESTING KEY-----
 MIHcAgEBBEIBrsoKp0oqcv6/JovJJDoDVSGWdirrkgCWxrprGlzB9o0X8fV675X0
 NwuBenXFfeZvVcwluO7/Q9wkYoPd/t3jGImgBwYFK4EEACOhgYkDgYYABAFj36bL
 06h5JRGUNB1X/Hwuw64uKW2GGJLVPPhoYMcg/ALWaW+d/t+DmV5xikwKssuFq4Bz
 VQldyCXTXGgu7OC0AQCC/Y/+ODK3NFKlRi+AsG3VQDSV4tgHLqZBBus0S6pPcg1q
 kohxS/xfFg/TEwRSSws+roJr4JFKpO2t3/be5OdqmQ==
------END EC PRIVATE KEY-----
-`
+-----END EC TESTING KEY-----
+`)
 
 var keyPairTests = []struct {
        algo string
@@ -168,35 +170,171 @@ func TestDialTimeout(t *testing.T) {
        if testing.Short() {
                t.Skip("skipping in short mode")
        }
-       listener := newLocalListener(t)
 
-       addr := listener.Addr().String()
-       defer listener.Close()
+       timeout := 100 * time.Microsecond
+       for !t.Failed() {
+               acceptc := make(chan net.Conn)
+               listener := newLocalListener(t)
+               go func() {
+                       for {
+                               conn, err := listener.Accept()
+                               if err != nil {
+                                       close(acceptc)
+                                       return
+                               }
+                               acceptc <- conn
+                       }
+               }()
+
+               addr := listener.Addr().String()
+               dialer := &net.Dialer{
+                       Timeout: timeout,
+               }
+               if conn, err := DialWithDialer(dialer, "tcp", addr, nil); err == nil {
+                       conn.Close()
+                       t.Errorf("DialWithTimeout unexpectedly completed successfully")
+               } else if !isTimeoutError(err) {
+                       t.Errorf("resulting error not a timeout: %v\nType %T: %#v", err, err, err)
+               }
+
+               listener.Close()
+
+               // We're looking for a timeout during the handshake, so check that the
+               // Listener actually accepted the connection to initiate it. (If the server
+               // takes too long to accept the connection, we might cancel before the
+               // underlying net.Conn is ever dialed — without ever attempting a
+               // handshake.)
+               lconn, ok := <-acceptc
+               if ok {
+                       // The Listener accepted a connection, so assume that it was from our
+                       // Dial: we triggered the timeout at the point where we wanted it!
+                       t.Logf("Listener accepted a connection from %s", lconn.RemoteAddr())
+                       lconn.Close()
+               }
+               // Close any spurious extra connecitions from the listener. (This is
+               // possible if there are, for example, stray Dial calls from other tests.)
+               for extraConn := range acceptc {
+                       t.Logf("spurious extra connection from %s", extraConn.RemoteAddr())
+                       extraConn.Close()
+               }
+               if ok {
+                       break
+               }
+
+               t.Logf("with timeout %v, DialWithDialer returned before listener accepted any connections; retrying", timeout)
+               timeout *= 2
+       }
+}
 
-       complete := make(chan bool)
-       defer close(complete)
+func TestDeadlineOnWrite(t *testing.T) {
+       if testing.Short() {
+               t.Skip("skipping in short mode")
+       }
+
+       ln := newLocalListener(t)
+       defer ln.Close()
+
+       srvCh := make(chan *Conn, 1)
 
        go func() {
-               conn, err := listener.Accept()
+               sconn, err := ln.Accept()
                if err != nil {
-                       t.Error(err)
+                       srvCh <- nil
                        return
                }
-               <-complete
-               conn.Close()
+               srv := Server(sconn, testConfig.Clone())
+               if err := srv.Handshake(); err != nil {
+                       srvCh <- nil
+                       return
+               }
+               srvCh <- srv
        }()
 
-       dialer := &net.Dialer{
-               Timeout: 10 * time.Millisecond,
+       clientConfig := testConfig.Clone()
+       clientConfig.MaxVersion = VersionTLS12
+       conn, err := Dial("tcp", ln.Addr().String(), clientConfig)
+       if err != nil {
+               t.Fatal(err)
        }
+       defer conn.Close()
 
-       var err error
-       if _, err = DialWithDialer(dialer, "tcp", addr, nil); err == nil {
-               t.Fatal("DialWithTimeout completed successfully")
+       srv := <-srvCh
+       if srv == nil {
+               t.Error(err)
        }
 
+       // Make sure the client/server is setup correctly and is able to do a typical Write/Read
+       buf := make([]byte, 6)
+       if _, err := srv.Write([]byte("foobar")); err != nil {
+               t.Errorf("Write err: %v", err)
+       }
+       if n, err := conn.Read(buf); n != 6 || err != nil || string(buf) != "foobar" {
+               t.Errorf("Read = %d, %v, data %q; want 6, nil, foobar", n, err, buf)
+       }
+
+       // Set a deadline which should cause Write to timeout
+       if err = srv.SetDeadline(time.Now()); err != nil {
+               t.Fatalf("SetDeadline(time.Now()) err: %v", err)
+       }
+       if _, err = srv.Write([]byte("should fail")); err == nil {
+               t.Fatal("Write should have timed out")
+       }
+
+       // Clear deadline and make sure it still times out
+       if err = srv.SetDeadline(time.Time{}); err != nil {
+               t.Fatalf("SetDeadline(time.Time{}) err: %v", err)
+       }
+       if _, err = srv.Write([]byte("This connection is permanently broken")); err == nil {
+               t.Fatal("Write which previously failed should still time out")
+       }
+
+       // Verify the error
+       if ne := err.(net.Error); ne.Temporary() != false {
+               t.Error("Write timed out but incorrectly classified the error as Temporary")
+       }
        if !isTimeoutError(err) {
-               t.Errorf("resulting error not a timeout: %v\nType %T: %#v", err, err, err)
+               t.Error("Write timed out but did not classify the error as a Timeout")
+       }
+}
+
+type readerFunc func([]byte) (int, error)
+
+func (f readerFunc) Read(b []byte) (int, error) { return f(b) }
+
+// TestDialer tests that tls.Dialer.DialContext can abort in the middle of a handshake.
+// (The other cases are all handled by the existing dial tests in this package, which
+// all also flow through the same code shared code paths)
+func TestDialer(t *testing.T) {
+       ln := newLocalListener(t)
+       defer ln.Close()
+
+       unblockServer := make(chan struct{}) // close-only
+       defer close(unblockServer)
+       go func() {
+               conn, err := ln.Accept()
+               if err != nil {
+                       return
+               }
+               defer conn.Close()
+               <-unblockServer
+       }()
+
+       ctx, cancel := context.WithCancel(context.Background())
+       d := Dialer{Config: &Config{
+               Rand: readerFunc(func(b []byte) (n int, err error) {
+                       // By the time crypto/tls wants randomness, that means it has a TCP
+                       // connection, so we're past the Dialer's dial and now blocked
+                       // in a handshake. Cancel our context and see if we get unstuck.
+                       // (Our TCP listener above never reads or writes, so the Handshake
+                       // would otherwise be stuck forever)
+                       cancel()
+                       return len(b), nil
+               }),
+               ServerName: "foo",
+       }}
+       _, err := d.DialContext(ctx, "tcp", ln.Addr().String())
+       if err != context.Canceled {
+               t.Errorf("err = %v; want context.Canceled", err)
        }
 }
 
@@ -253,6 +391,9 @@ func testConnReadNonzeroAndEOF(t *testing.T, delay time.Duration) error {
        }()
 
        clientConfig := testConfig.Clone()
+       // In TLS 1.3, alerts are encrypted and disguised as application data, so
+       // the opportunistic peek won't work.
+       clientConfig.MaxVersion = VersionTLS12
        conn, err := Dial("tcp", ln.Addr().String(), clientConfig)
        if err != nil {
                t.Fatal(err)
@@ -290,7 +431,11 @@ func TestTLSUniqueMatches(t *testing.T) {
        defer ln.Close()
 
        serverTLSUniques := make(chan []byte)
+       parentDone := make(chan struct{})
+       childDone := make(chan struct{})
+       defer close(parentDone)
        go func() {
+               defer close(childDone)
                for i := 0; i < 2; i++ {
                        sconn, err := ln.Accept()
                        if err != nil {
@@ -298,12 +443,17 @@ func TestTLSUniqueMatches(t *testing.T) {
                                return
                        }
                        serverConfig := testConfig.Clone()
+                       serverConfig.MaxVersion = VersionTLS12 // TLSUnique is not defined in TLS 1.3
                        srv := Server(sconn, serverConfig)
                        if err := srv.Handshake(); err != nil {
                                t.Error(err)
                                return
                        }
-                       serverTLSUniques <- srv.ConnectionState().TLSUnique
+                       select {
+                       case <-parentDone:
+                               return
+                       case serverTLSUniques <- srv.ConnectionState().TLSUnique:
+                       }
                }
        }()
 
@@ -313,9 +463,20 @@ func TestTLSUniqueMatches(t *testing.T) {
        if err != nil {
                t.Fatal(err)
        }
-       if !bytes.Equal(conn.ConnectionState().TLSUnique, <-serverTLSUniques) {
+
+       var serverTLSUniquesValue []byte
+       select {
+       case <-childDone:
+               return
+       case serverTLSUniquesValue = <-serverTLSUniques:
+       }
+
+       if !bytes.Equal(conn.ConnectionState().TLSUnique, serverTLSUniquesValue) {
                t.Error("client and server channel bindings differ")
        }
+       if serverTLSUniquesValue == nil || bytes.Equal(serverTLSUniquesValue, make([]byte, 12)) {
+               t.Error("tls-unique is empty or zero")
+       }
        conn.Close()
 
        conn, err = Dial("tcp", ln.Addr().String(), clientConfig)
@@ -326,9 +487,19 @@ func TestTLSUniqueMatches(t *testing.T) {
        if !conn.ConnectionState().DidResume {
                t.Error("second session did not use resumption")
        }
-       if !bytes.Equal(conn.ConnectionState().TLSUnique, <-serverTLSUniques) {
+
+       select {
+       case <-childDone:
+               return
+       case serverTLSUniquesValue = <-serverTLSUniques:
+       }
+
+       if !bytes.Equal(conn.ConnectionState().TLSUnique, serverTLSUniquesValue) {
                t.Error("client and server channel bindings differ when session resumption is used")
        }
+       if serverTLSUniquesValue == nil || bytes.Equal(serverTLSUniquesValue, make([]byte, 12)) {
+               t.Error("resumption tls-unique is empty or zero")
+       }
 }
 
 func TestVerifyHostname(t *testing.T) {
@@ -352,34 +523,6 @@ func TestVerifyHostname(t *testing.T) {
        if err := c.VerifyHostname("www.google.com"); err == nil {
                t.Fatalf("verify www.google.com succeeded with InsecureSkipVerify=true")
        }
-       if err := c.VerifyHostname("www.yahoo.com"); err == nil {
-               t.Fatalf("verify www.google.com succeeded with InsecureSkipVerify=true")
-       }
-}
-
-func TestVerifyHostnameResumed(t *testing.T) {
-       testenv.MustHaveExternalNetwork(t)
-
-       config := &Config{
-               ClientSessionCache: NewLRUClientSessionCache(32),
-       }
-       for i := 0; i < 2; i++ {
-               c, err := Dial("tcp", "www.google.com:https", config)
-               if err != nil {
-                       t.Fatalf("Dial #%d: %v", i, err)
-               }
-               cs := c.ConnectionState()
-               if i > 0 && !cs.DidResume {
-                       t.Fatalf("Subsequent connection unexpectedly didn't resume")
-               }
-               if cs.VerifiedChains == nil {
-                       t.Fatalf("Dial #%d: cs.VerifiedChains == nil", i)
-               }
-               if err := c.VerifyHostname("www.google.com"); err != nil {
-                       t.Fatalf("verify www.google.com #%d: %v", i, err)
-               }
-               c.Close()
-       }
 }
 
 func TestConnCloseBreakingWrite(t *testing.T) {
@@ -456,8 +599,8 @@ func TestConnCloseBreakingWrite(t *testing.T) {
        }
 
        <-closeReturned
-       if err := tconn.Close(); err != errClosed {
-               t.Errorf("Close error = %v; want errClosed", err)
+       if err := tconn.Close(); err != net.ErrClosed {
+               t.Errorf("Close error = %v; want net.ErrClosed", err)
        }
 }
 
@@ -481,7 +624,7 @@ func TestConnCloseWrite(t *testing.T) {
                }
                defer srv.Close()
 
-               data, err := ioutil.ReadAll(srv)
+               data, err := io.ReadAll(srv)
                if err != nil {
                        return err
                }
@@ -522,7 +665,7 @@ func TestConnCloseWrite(t *testing.T) {
                        return fmt.Errorf("CloseWrite error = %v; want errShutdown", err)
                }
 
-               data, err := ioutil.ReadAll(conn)
+               data, err := io.ReadAll(conn)
                if err != nil {
                        return err
                }
@@ -585,7 +728,7 @@ func TestWarningAlertFlood(t *testing.T) {
                }
                defer srv.Close()
 
-               _, err = ioutil.ReadAll(srv)
+               _, err = io.ReadAll(srv)
                if err == nil {
                        return errors.New("unexpected lack of error from server")
                }
@@ -601,6 +744,7 @@ func TestWarningAlertFlood(t *testing.T) {
        go func() { errChan <- server() }()
 
        clientConfig := testConfig.Clone()
+       clientConfig.MaxVersion = VersionTLS12 // there are no warning alerts in TLS 1.3
        conn, err := Dial("tcp", ln.Addr().String(), clientConfig)
        if err != nil {
                t.Fatal(err)
@@ -620,7 +764,7 @@ func TestWarningAlertFlood(t *testing.T) {
 }
 
 func TestCloneFuncFields(t *testing.T) {
-       const expectedCount = 5
+       const expectedCount = 8
        called := 0
 
        c1 := Config{
@@ -644,6 +788,18 @@ func TestCloneFuncFields(t *testing.T) {
                        called |= 1 << 4
                        return nil
                },
+               VerifyConnection: func(ConnectionState) error {
+                       called |= 1 << 5
+                       return nil
+               },
+               UnwrapSession: func(identity []byte, cs ConnectionState) (*SessionState, error) {
+                       called |= 1 << 6
+                       return nil, nil
+               },
+               WrapSession: func(cs ConnectionState, ss *SessionState) ([]byte, error) {
+                       called |= 1 << 7
+                       return nil, nil
+               },
        }
 
        c2 := c1.Clone()
@@ -653,6 +809,9 @@ func TestCloneFuncFields(t *testing.T) {
        c2.GetClientCertificate(nil)
        c2.GetConfigForClient(nil)
        c2.VerifyPeerCertificate(nil, nil)
+       c2.VerifyConnection(ConnectionState{})
+       c2.UnwrapSession(nil, ConnectionState{})
+       c2.WrapSession(ConnectionState{}, nil)
 
        if called != (1<<expectedCount)-1 {
                t.Fatalf("expected %d calls but saw calls %b", expectedCount, called)
@@ -666,17 +825,12 @@ func TestCloneNonFuncFields(t *testing.T) {
        typ := v.Type()
        for i := 0; i < typ.NumField(); i++ {
                f := v.Field(i)
-               if !f.CanSet() {
-                       // unexported field; not cloned.
-                       continue
-               }
-
                // testing/quick can't handle functions or interfaces and so
                // isn't used here.
                switch fn := typ.Field(i).Name; fn {
                case "Rand":
                        f.Set(reflect.ValueOf(io.Reader(os.Stdin)))
-               case "Time", "GetCertificate", "GetConfigForClient", "VerifyPeerCertificate", "GetClientCertificate":
+               case "Time", "GetCertificate", "GetConfigForClient", "VerifyPeerCertificate", "VerifyConnection", "GetClientCertificate", "WrapSession", "UnwrapSession":
                        // DeepEqual can't compare functions. If you add a
                        // function field to this list, you must also change
                        // TestCloneFuncFields to ensure that the func field is
@@ -711,22 +865,29 @@ func TestCloneNonFuncFields(t *testing.T) {
                        f.Set(reflect.ValueOf([]CurveID{CurveP256}))
                case "Renegotiation":
                        f.Set(reflect.ValueOf(RenegotiateOnceAsClient))
+               case "mutex", "autoSessionTicketKeys", "sessionTicketKeys":
+                       continue // these are unexported fields that are handled separately
                default:
                        t.Errorf("all fields must be accounted for, but saw unknown field %q", fn)
                }
        }
+       // Set the unexported fields related to session ticket keys, which are copied with Clone().
+       c1.autoSessionTicketKeys = []ticketKey{c1.ticketKeyFromBytes(c1.SessionTicketKey)}
+       c1.sessionTicketKeys = []ticketKey{c1.ticketKeyFromBytes(c1.SessionTicketKey)}
 
        c2 := c1.Clone()
-       // DeepEqual also compares unexported fields, thus c2 needs to have run
-       // serverInit in order to be DeepEqual to c1. Cloning it and discarding
-       // the result is sufficient.
-       c2.Clone()
-
        if !reflect.DeepEqual(&c1, c2) {
                t.Errorf("clone failed to copy a field")
        }
 }
 
+func TestCloneNilConfig(t *testing.T) {
+       var config *Config
+       if cc := config.Clone(); cc != nil {
+               t.Fatalf("Clone with nil should return nil, got: %+v", cc)
+       }
+}
+
 // changeImplConn is a net.Conn which can change its Write and Close
 // methods.
 type changeImplConn struct {
@@ -749,7 +910,7 @@ func (w *changeImplConn) Close() error {
        return w.Conn.Close()
 }
 
-func throughput(b *testing.B, totalBytes int64, dynamicRecordSizingDisabled bool) {
+func throughput(b *testing.B, version uint16, totalBytes int64, dynamicRecordSizingDisabled bool) {
        ln := newLocalListener(b)
        defer ln.Close()
 
@@ -785,6 +946,7 @@ func throughput(b *testing.B, totalBytes int64, dynamicRecordSizingDisabled bool
        clientConfig := testConfig.Clone()
        clientConfig.CipherSuites = nil // the defaults may prefer faster ciphers
        clientConfig.DynamicRecordSizingDisabled = dynamicRecordSizingDisabled
+       clientConfig.MaxVersion = version
 
        buf := make([]byte, bufsize)
        chunks := int(math.Ceil(float64(totalBytes) / float64(len(buf))))
@@ -812,7 +974,12 @@ func BenchmarkThroughput(b *testing.B) {
                for size := 1; size <= 64; size <<= 1 {
                        name := fmt.Sprintf("%sPacket/%dMB", mode, size)
                        b.Run(name, func(b *testing.B) {
-                               throughput(b, int64(size<<20), mode == "Max")
+                               b.Run("TLSv12", func(b *testing.B) {
+                                       throughput(b, VersionTLS12, int64(size<<20), mode == "Max")
+                               })
+                               b.Run("TLSv13", func(b *testing.B) {
+                                       throughput(b, VersionTLS13, int64(size<<20), mode == "Max")
+                               })
                        })
                }
        }
@@ -846,7 +1013,7 @@ func (c *slowConn) Write(p []byte) (int, error) {
        return len(p), nil
 }
 
-func latency(b *testing.B, bps int, dynamicRecordSizingDisabled bool) {
+func latency(b *testing.B, version uint16, bps int, dynamicRecordSizingDisabled bool) {
        ln := newLocalListener(b)
        defer ln.Close()
 
@@ -872,6 +1039,7 @@ func latency(b *testing.B, bps int, dynamicRecordSizingDisabled bool) {
 
        clientConfig := testConfig.Clone()
        clientConfig.DynamicRecordSizingDisabled = dynamicRecordSizingDisabled
+       clientConfig.MaxVersion = version
 
        buf := make([]byte, 16384)
        peek := make([]byte, 1)
@@ -903,7 +1071,12 @@ func BenchmarkLatency(b *testing.B) {
                for _, kbps := range []int{200, 500, 1000, 2000, 5000} {
                        name := fmt.Sprintf("%sPacket/%dkbps", mode, kbps)
                        b.Run(name, func(b *testing.B) {
-                               latency(b, kbps*1000, mode == "Max")
+                               b.Run("TLSv12", func(b *testing.B) {
+                                       latency(b, VersionTLS12, kbps*1000, mode == "Max")
+                               })
+                               b.Run("TLSv13", func(b *testing.B) {
+                                       latency(b, VersionTLS13, kbps*1000, mode == "Max")
+                               })
                        })
                }
        }
@@ -916,3 +1089,718 @@ func TestConnectionStateMarshal(t *testing.T) {
                t.Errorf("json.Marshal failed on ConnectionState: %v", err)
        }
 }
+
+func TestConnectionState(t *testing.T) {
+       issuer, err := x509.ParseCertificate(testRSACertificateIssuer)
+       if err != nil {
+               panic(err)
+       }
+       rootCAs := x509.NewCertPool()
+       rootCAs.AddCert(issuer)
+
+       now := func() time.Time { return time.Unix(1476984729, 0) }
+
+       const alpnProtocol = "golang"
+       const serverName = "example.golang"
+       var scts = [][]byte{[]byte("dummy sct 1"), []byte("dummy sct 2")}
+       var ocsp = []byte("dummy ocsp")
+
+       for _, v := range []uint16{VersionTLS12, VersionTLS13} {
+               var name string
+               switch v {
+               case VersionTLS12:
+                       name = "TLSv12"
+               case VersionTLS13:
+                       name = "TLSv13"
+               }
+               t.Run(name, func(t *testing.T) {
+                       config := &Config{
+                               Time:         now,
+                               Rand:         zeroSource{},
+                               Certificates: make([]Certificate, 1),
+                               MaxVersion:   v,
+                               RootCAs:      rootCAs,
+                               ClientCAs:    rootCAs,
+                               ClientAuth:   RequireAndVerifyClientCert,
+                               NextProtos:   []string{alpnProtocol},
+                               ServerName:   serverName,
+                       }
+                       config.Certificates[0].Certificate = [][]byte{testRSACertificate}
+                       config.Certificates[0].PrivateKey = testRSAPrivateKey
+                       config.Certificates[0].SignedCertificateTimestamps = scts
+                       config.Certificates[0].OCSPStaple = ocsp
+
+                       ss, cs, err := testHandshake(t, config, config)
+                       if err != nil {
+                               t.Fatalf("Handshake failed: %v", err)
+                       }
+
+                       if ss.Version != v || cs.Version != v {
+                               t.Errorf("Got versions %x (server) and %x (client), expected %x", ss.Version, cs.Version, v)
+                       }
+
+                       if !ss.HandshakeComplete || !cs.HandshakeComplete {
+                               t.Errorf("Got HandshakeComplete %v (server) and %v (client), expected true", ss.HandshakeComplete, cs.HandshakeComplete)
+                       }
+
+                       if ss.DidResume || cs.DidResume {
+                               t.Errorf("Got DidResume %v (server) and %v (client), expected false", ss.DidResume, cs.DidResume)
+                       }
+
+                       if ss.CipherSuite == 0 || cs.CipherSuite == 0 {
+                               t.Errorf("Got invalid cipher suite: %v (server) and %v (client)", ss.CipherSuite, cs.CipherSuite)
+                       }
+
+                       if ss.NegotiatedProtocol != alpnProtocol || cs.NegotiatedProtocol != alpnProtocol {
+                               t.Errorf("Got negotiated protocol %q (server) and %q (client), expected %q", ss.NegotiatedProtocol, cs.NegotiatedProtocol, alpnProtocol)
+                       }
+
+                       if !cs.NegotiatedProtocolIsMutual {
+                               t.Errorf("Got false NegotiatedProtocolIsMutual on the client side")
+                       }
+                       // NegotiatedProtocolIsMutual on the server side is unspecified.
+
+                       if ss.ServerName != serverName {
+                               t.Errorf("Got server name %q, expected %q", ss.ServerName, serverName)
+                       }
+                       if cs.ServerName != serverName {
+                               t.Errorf("Got server name on client connection %q, expected %q", cs.ServerName, serverName)
+                       }
+
+                       if len(ss.PeerCertificates) != 1 || len(cs.PeerCertificates) != 1 {
+                               t.Errorf("Got %d (server) and %d (client) peer certificates, expected %d", len(ss.PeerCertificates), len(cs.PeerCertificates), 1)
+                       }
+
+                       if len(ss.VerifiedChains) != 1 || len(cs.VerifiedChains) != 1 {
+                               t.Errorf("Got %d (server) and %d (client) verified chains, expected %d", len(ss.VerifiedChains), len(cs.VerifiedChains), 1)
+                       } else if len(ss.VerifiedChains[0]) != 2 || len(cs.VerifiedChains[0]) != 2 {
+                               t.Errorf("Got %d (server) and %d (client) long verified chain, expected %d", len(ss.VerifiedChains[0]), len(cs.VerifiedChains[0]), 2)
+                       }
+
+                       if len(cs.SignedCertificateTimestamps) != 2 {
+                               t.Errorf("Got %d SCTs, expected %d", len(cs.SignedCertificateTimestamps), 2)
+                       }
+                       if !bytes.Equal(cs.OCSPResponse, ocsp) {
+                               t.Errorf("Got OCSPs %x, expected %x", cs.OCSPResponse, ocsp)
+                       }
+                       // Only TLS 1.3 supports OCSP and SCTs on client certs.
+                       if v == VersionTLS13 {
+                               if len(ss.SignedCertificateTimestamps) != 2 {
+                                       t.Errorf("Got %d client SCTs, expected %d", len(ss.SignedCertificateTimestamps), 2)
+                               }
+                               if !bytes.Equal(ss.OCSPResponse, ocsp) {
+                                       t.Errorf("Got client OCSPs %x, expected %x", ss.OCSPResponse, ocsp)
+                               }
+                       }
+
+                       if v == VersionTLS13 {
+                               if ss.TLSUnique != nil || cs.TLSUnique != nil {
+                                       t.Errorf("Got TLSUnique %x (server) and %x (client), expected nil in TLS 1.3", ss.TLSUnique, cs.TLSUnique)
+                               }
+                       } else {
+                               if ss.TLSUnique == nil || cs.TLSUnique == nil {
+                                       t.Errorf("Got TLSUnique %x (server) and %x (client), expected non-nil", ss.TLSUnique, cs.TLSUnique)
+                               }
+                       }
+               })
+       }
+}
+
+// Issue 28744: Ensure that we don't modify memory
+// that Config doesn't own such as Certificates.
+func TestBuildNameToCertificate_doesntModifyCertificates(t *testing.T) {
+       c0 := Certificate{
+               Certificate: [][]byte{testRSACertificate},
+               PrivateKey:  testRSAPrivateKey,
+       }
+       c1 := Certificate{
+               Certificate: [][]byte{testSNICertificate},
+               PrivateKey:  testRSAPrivateKey,
+       }
+       config := testConfig.Clone()
+       config.Certificates = []Certificate{c0, c1}
+
+       config.BuildNameToCertificate()
+       got := config.Certificates
+       want := []Certificate{c0, c1}
+       if !reflect.DeepEqual(got, want) {
+               t.Fatalf("Certificates were mutated by BuildNameToCertificate\nGot: %#v\nWant: %#v\n", got, want)
+       }
+}
+
+func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
+
+func TestClientHelloInfo_SupportsCertificate(t *testing.T) {
+       rsaCert := &Certificate{
+               Certificate: [][]byte{testRSACertificate},
+               PrivateKey:  testRSAPrivateKey,
+       }
+       pkcs1Cert := &Certificate{
+               Certificate:                  [][]byte{testRSACertificate},
+               PrivateKey:                   testRSAPrivateKey,
+               SupportedSignatureAlgorithms: []SignatureScheme{PKCS1WithSHA1, PKCS1WithSHA256},
+       }
+       ecdsaCert := &Certificate{
+               // ECDSA P-256 certificate
+               Certificate: [][]byte{testP256Certificate},
+               PrivateKey:  testP256PrivateKey,
+       }
+       ed25519Cert := &Certificate{
+               Certificate: [][]byte{testEd25519Certificate},
+               PrivateKey:  testEd25519PrivateKey,
+       }
+
+       tests := []struct {
+               c       *Certificate
+               chi     *ClientHelloInfo
+               wantErr string
+       }{
+               {rsaCert, &ClientHelloInfo{
+                       ServerName:        "example.golang",
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256},
+                       SupportedVersions: []uint16{VersionTLS13},
+               }, ""},
+               {ecdsaCert, &ClientHelloInfo{
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256, ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS13, VersionTLS12},
+               }, ""},
+               {rsaCert, &ClientHelloInfo{
+                       ServerName:        "example.com",
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256},
+                       SupportedVersions: []uint16{VersionTLS13},
+               }, "not valid for requested server name"},
+               {ecdsaCert, &ClientHelloInfo{
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP384AndSHA384},
+                       SupportedVersions: []uint16{VersionTLS13},
+               }, "signature algorithms"},
+               {pkcs1Cert, &ClientHelloInfo{
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256, ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS13},
+               }, "signature algorithms"},
+
+               {rsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       SignatureSchemes:  []SignatureScheme{PKCS1WithSHA1},
+                       SupportedVersions: []uint16{VersionTLS13, VersionTLS12},
+               }, "signature algorithms"},
+               {rsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       SignatureSchemes:  []SignatureScheme{PKCS1WithSHA1},
+                       SupportedVersions: []uint16{VersionTLS13, VersionTLS12},
+                       config: &Config{
+                               MaxVersion: VersionTLS12,
+                       },
+               }, ""}, // Check that mutual version selection works.
+
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP384AndSHA384},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""}, // TLS 1.2 does not restrict curves based on the SignatureScheme.
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  nil,
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""}, // TLS 1.2 comes with default signature schemes.
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "cipher suite"},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+                       config: &Config{
+                               CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       },
+               }, "cipher suite"},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP384},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "certificate curve"},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{1},
+                       SignatureSchemes:  []SignatureScheme{ECDSAWithP256AndSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "doesn't support ECDHE"},
+               {ecdsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{PSSWithSHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "signature algorithms"},
+
+               {ed25519Cert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256}, // only relevant for ECDHE support
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{Ed25519},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""},
+               {ed25519Cert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{CurveP256}, // only relevant for ECDHE support
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{Ed25519},
+                       SupportedVersions: []uint16{VersionTLS10},
+                       config:            &Config{MinVersion: VersionTLS10},
+               }, "doesn't support Ed25519"},
+               {ed25519Cert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+                       SupportedCurves:   []CurveID{},
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SignatureSchemes:  []SignatureScheme{Ed25519},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, "doesn't support ECDHE"},
+
+               {rsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+                       SupportedCurves:   []CurveID{CurveP256}, // only relevant for ECDHE support
+                       SupportedPoints:   []uint8{pointFormatUncompressed},
+                       SupportedVersions: []uint16{VersionTLS10},
+                       config:            &Config{MinVersion: VersionTLS10},
+               }, ""},
+               {rsaCert, &ClientHelloInfo{
+                       CipherSuites:      []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
+                       SupportedVersions: []uint16{VersionTLS12},
+               }, ""}, // static RSA fallback
+       }
+       for i, tt := range tests {
+               err := tt.chi.SupportsCertificate(tt.c)
+               switch {
+               case tt.wantErr == "" && err != nil:
+                       t.Errorf("%d: unexpected error: %v", i, err)
+               case tt.wantErr != "" && err == nil:
+                       t.Errorf("%d: unexpected success", i)
+               case tt.wantErr != "" && !strings.Contains(err.Error(), tt.wantErr):
+                       t.Errorf("%d: got error %q, expected %q", i, err, tt.wantErr)
+               }
+       }
+}
+
+func TestCipherSuites(t *testing.T) {
+       var lastID uint16
+       for _, c := range CipherSuites() {
+               if lastID > c.ID {
+                       t.Errorf("CipherSuites are not ordered by ID: got %#04x after %#04x", c.ID, lastID)
+               } else {
+                       lastID = c.ID
+               }
+
+               if c.Insecure {
+                       t.Errorf("%#04x: Insecure CipherSuite returned by CipherSuites()", c.ID)
+               }
+       }
+       lastID = 0
+       for _, c := range InsecureCipherSuites() {
+               if lastID > c.ID {
+                       t.Errorf("InsecureCipherSuites are not ordered by ID: got %#04x after %#04x", c.ID, lastID)
+               } else {
+                       lastID = c.ID
+               }
+
+               if !c.Insecure {
+                       t.Errorf("%#04x: not Insecure CipherSuite returned by InsecureCipherSuites()", c.ID)
+               }
+       }
+
+       CipherSuiteByID := func(id uint16) *CipherSuite {
+               for _, c := range CipherSuites() {
+                       if c.ID == id {
+                               return c
+                       }
+               }
+               for _, c := range InsecureCipherSuites() {
+                       if c.ID == id {
+                               return c
+                       }
+               }
+               return nil
+       }
+
+       for _, c := range cipherSuites {
+               cc := CipherSuiteByID(c.id)
+               if cc == nil {
+                       t.Errorf("%#04x: no CipherSuite entry", c.id)
+                       continue
+               }
+
+               if tls12Only := c.flags&suiteTLS12 != 0; tls12Only && len(cc.SupportedVersions) != 1 {
+                       t.Errorf("%#04x: suite is TLS 1.2 only, but SupportedVersions is %v", c.id, cc.SupportedVersions)
+               } else if !tls12Only && len(cc.SupportedVersions) != 3 {
+                       t.Errorf("%#04x: suite TLS 1.0-1.2, but SupportedVersions is %v", c.id, cc.SupportedVersions)
+               }
+
+               if got := CipherSuiteName(c.id); got != cc.Name {
+                       t.Errorf("%#04x: unexpected CipherSuiteName: got %q, expected %q", c.id, got, cc.Name)
+               }
+       }
+       for _, c := range cipherSuitesTLS13 {
+               cc := CipherSuiteByID(c.id)
+               if cc == nil {
+                       t.Errorf("%#04x: no CipherSuite entry", c.id)
+                       continue
+               }
+
+               if cc.Insecure {
+                       t.Errorf("%#04x: Insecure %v, expected false", c.id, cc.Insecure)
+               }
+               if len(cc.SupportedVersions) != 1 || cc.SupportedVersions[0] != VersionTLS13 {
+                       t.Errorf("%#04x: suite is TLS 1.3 only, but SupportedVersions is %v", c.id, cc.SupportedVersions)
+               }
+
+               if got := CipherSuiteName(c.id); got != cc.Name {
+                       t.Errorf("%#04x: unexpected CipherSuiteName: got %q, expected %q", c.id, got, cc.Name)
+               }
+       }
+
+       if got := CipherSuiteName(0xabc); got != "0x0ABC" {
+               t.Errorf("unexpected fallback CipherSuiteName: got %q, expected 0x0ABC", got)
+       }
+
+       if len(cipherSuitesPreferenceOrder) != len(cipherSuites) {
+               t.Errorf("cipherSuitesPreferenceOrder is not the same size as cipherSuites")
+       }
+       if len(cipherSuitesPreferenceOrderNoAES) != len(cipherSuitesPreferenceOrder) {
+               t.Errorf("cipherSuitesPreferenceOrderNoAES is not the same size as cipherSuitesPreferenceOrder")
+       }
+
+       // Check that disabled suites are at the end of the preference lists, and
+       // that they are marked insecure.
+       for i, id := range disabledCipherSuites {
+               offset := len(cipherSuitesPreferenceOrder) - len(disabledCipherSuites)
+               if cipherSuitesPreferenceOrder[offset+i] != id {
+                       t.Errorf("disabledCipherSuites[%d]: not at the end of cipherSuitesPreferenceOrder", i)
+               }
+               if cipherSuitesPreferenceOrderNoAES[offset+i] != id {
+                       t.Errorf("disabledCipherSuites[%d]: not at the end of cipherSuitesPreferenceOrderNoAES", i)
+               }
+               c := CipherSuiteByID(id)
+               if c == nil {
+                       t.Errorf("%#04x: no CipherSuite entry", id)
+                       continue
+               }
+               if !c.Insecure {
+                       t.Errorf("%#04x: disabled by default but not marked insecure", id)
+               }
+       }
+
+       for i, prefOrder := range [][]uint16{cipherSuitesPreferenceOrder, cipherSuitesPreferenceOrderNoAES} {
+               // Check that insecure and HTTP/2 bad cipher suites are at the end of
+               // the preference lists.
+               var sawInsecure, sawBad bool
+               for _, id := range prefOrder {
+                       c := CipherSuiteByID(id)
+                       if c == nil {
+                               t.Errorf("%#04x: no CipherSuite entry", id)
+                               continue
+                       }
+
+                       if c.Insecure {
+                               sawInsecure = true
+                       } else if sawInsecure {
+                               t.Errorf("%#04x: secure suite after insecure one(s)", id)
+                       }
+
+                       if http2isBadCipher(id) {
+                               sawBad = true
+                       } else if sawBad {
+                               t.Errorf("%#04x: non-bad suite after bad HTTP/2 one(s)", id)
+                       }
+               }
+
+               // Check that the list is sorted according to the documented criteria.
+               isBetter := func(a, b int) bool {
+                       aSuite, bSuite := cipherSuiteByID(prefOrder[a]), cipherSuiteByID(prefOrder[b])
+                       aName, bName := CipherSuiteName(prefOrder[a]), CipherSuiteName(prefOrder[b])
+                       // * < RC4
+                       if !strings.Contains(aName, "RC4") && strings.Contains(bName, "RC4") {
+                               return true
+                       } else if strings.Contains(aName, "RC4") && !strings.Contains(bName, "RC4") {
+                               return false
+                       }
+                       // * < CBC_SHA256
+                       if !strings.Contains(aName, "CBC_SHA256") && strings.Contains(bName, "CBC_SHA256") {
+                               return true
+                       } else if strings.Contains(aName, "CBC_SHA256") && !strings.Contains(bName, "CBC_SHA256") {
+                               return false
+                       }
+                       // * < 3DES
+                       if !strings.Contains(aName, "3DES") && strings.Contains(bName, "3DES") {
+                               return true
+                       } else if strings.Contains(aName, "3DES") && !strings.Contains(bName, "3DES") {
+                               return false
+                       }
+                       // ECDHE < *
+                       if aSuite.flags&suiteECDHE != 0 && bSuite.flags&suiteECDHE == 0 {
+                               return true
+                       } else if aSuite.flags&suiteECDHE == 0 && bSuite.flags&suiteECDHE != 0 {
+                               return false
+                       }
+                       // AEAD < CBC
+                       if aSuite.aead != nil && bSuite.aead == nil {
+                               return true
+                       } else if aSuite.aead == nil && bSuite.aead != nil {
+                               return false
+                       }
+                       // AES < ChaCha20
+                       if strings.Contains(aName, "AES") && strings.Contains(bName, "CHACHA20") {
+                               return i == 0 // true for cipherSuitesPreferenceOrder
+                       } else if strings.Contains(aName, "CHACHA20") && strings.Contains(bName, "AES") {
+                               return i != 0 // true for cipherSuitesPreferenceOrderNoAES
+                       }
+                       // AES-128 < AES-256
+                       if strings.Contains(aName, "AES_128") && strings.Contains(bName, "AES_256") {
+                               return true
+                       } else if strings.Contains(aName, "AES_256") && strings.Contains(bName, "AES_128") {
+                               return false
+                       }
+                       // ECDSA < RSA
+                       if aSuite.flags&suiteECSign != 0 && bSuite.flags&suiteECSign == 0 {
+                               return true
+                       } else if aSuite.flags&suiteECSign == 0 && bSuite.flags&suiteECSign != 0 {
+                               return false
+                       }
+                       t.Fatalf("two ciphersuites are equal by all criteria: %v and %v", aName, bName)
+                       panic("unreachable")
+               }
+               if !sort.SliceIsSorted(prefOrder, isBetter) {
+                       t.Error("preference order is not sorted according to the rules")
+               }
+       }
+}
+
+func TestVersionName(t *testing.T) {
+       if got, exp := VersionName(VersionTLS13), "TLS 1.3"; got != exp {
+               t.Errorf("unexpected VersionName: got %q, expected %q", got, exp)
+       }
+       if got, exp := VersionName(0x12a), "0x012A"; got != exp {
+               t.Errorf("unexpected fallback VersionName: got %q, expected %q", got, exp)
+       }
+}
+
+// http2isBadCipher is copied from net/http.
+// TODO: if it ends up exposed somewhere, use that instead.
+func http2isBadCipher(cipher uint16) bool {
+       switch cipher {
+       case TLS_RSA_WITH_RC4_128_SHA,
+               TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+               TLS_RSA_WITH_AES_128_CBC_SHA,
+               TLS_RSA_WITH_AES_256_CBC_SHA,
+               TLS_RSA_WITH_AES_128_CBC_SHA256,
+               TLS_RSA_WITH_AES_128_GCM_SHA256,
+               TLS_RSA_WITH_AES_256_GCM_SHA384,
+               TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+               TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+               TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+               TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+               TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+               TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+               TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+               TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+               TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+               return true
+       default:
+               return false
+       }
+}
+
+type brokenSigner struct{ crypto.Signer }
+
+func (s brokenSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
+       // Replace opts with opts.HashFunc(), so rsa.PSSOptions are discarded.
+       return s.Signer.Sign(rand, digest, opts.HashFunc())
+}
+
+// TestPKCS1OnlyCert uses a client certificate with a broken crypto.Signer that
+// always makes PKCS #1 v1.5 signatures, so can't be used with RSA-PSS.
+func TestPKCS1OnlyCert(t *testing.T) {
+       clientConfig := testConfig.Clone()
+       clientConfig.Certificates = []Certificate{{
+               Certificate: [][]byte{testRSACertificate},
+               PrivateKey:  brokenSigner{testRSAPrivateKey},
+       }}
+       serverConfig := testConfig.Clone()
+       serverConfig.MaxVersion = VersionTLS12 // TLS 1.3 doesn't support PKCS #1 v1.5
+       serverConfig.ClientAuth = RequireAnyClientCert
+
+       // If RSA-PSS is selected, the handshake should fail.
+       if _, _, err := testHandshake(t, clientConfig, serverConfig); err == nil {
+               t.Fatal("expected broken certificate to cause connection to fail")
+       }
+
+       clientConfig.Certificates[0].SupportedSignatureAlgorithms =
+               []SignatureScheme{PKCS1WithSHA1, PKCS1WithSHA256}
+
+       // But if the certificate restricts supported algorithms, RSA-PSS should not
+       // be selected, and the handshake should succeed.
+       if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil {
+               t.Error(err)
+       }
+}
+
+func TestVerifyCertificates(t *testing.T) {
+       // See https://go.dev/issue/31641.
+       t.Run("TLSv12", func(t *testing.T) { testVerifyCertificates(t, VersionTLS12) })
+       t.Run("TLSv13", func(t *testing.T) { testVerifyCertificates(t, VersionTLS13) })
+}
+
+func testVerifyCertificates(t *testing.T, version uint16) {
+       tests := []struct {
+               name string
+
+               InsecureSkipVerify bool
+               ClientAuth         ClientAuthType
+               ClientCertificates bool
+       }{
+               {
+                       name: "defaults",
+               },
+               {
+                       name:               "InsecureSkipVerify",
+                       InsecureSkipVerify: true,
+               },
+               {
+                       name:       "RequestClientCert with no certs",
+                       ClientAuth: RequestClientCert,
+               },
+               {
+                       name:               "RequestClientCert with certs",
+                       ClientAuth:         RequestClientCert,
+                       ClientCertificates: true,
+               },
+               {
+                       name:               "RequireAnyClientCert",
+                       ClientAuth:         RequireAnyClientCert,
+                       ClientCertificates: true,
+               },
+               {
+                       name:       "VerifyClientCertIfGiven with no certs",
+                       ClientAuth: VerifyClientCertIfGiven,
+               },
+               {
+                       name:               "VerifyClientCertIfGiven with certs",
+                       ClientAuth:         VerifyClientCertIfGiven,
+                       ClientCertificates: true,
+               },
+               {
+                       name:               "RequireAndVerifyClientCert",
+                       ClientAuth:         RequireAndVerifyClientCert,
+                       ClientCertificates: true,
+               },
+       }
+
+       issuer, err := x509.ParseCertificate(testRSACertificateIssuer)
+       if err != nil {
+               t.Fatal(err)
+       }
+       rootCAs := x509.NewCertPool()
+       rootCAs.AddCert(issuer)
+
+       for _, test := range tests {
+               test := test
+               t.Run(test.name, func(t *testing.T) {
+                       t.Parallel()
+
+                       var serverVerifyConnection, clientVerifyConnection bool
+                       var serverVerifyPeerCertificates, clientVerifyPeerCertificates bool
+
+                       clientConfig := testConfig.Clone()
+                       clientConfig.Time = func() time.Time { return time.Unix(1476984729, 0) }
+                       clientConfig.MaxVersion = version
+                       clientConfig.MinVersion = version
+                       clientConfig.RootCAs = rootCAs
+                       clientConfig.ServerName = "example.golang"
+                       clientConfig.ClientSessionCache = NewLRUClientSessionCache(1)
+                       serverConfig := clientConfig.Clone()
+                       serverConfig.ClientCAs = rootCAs
+
+                       clientConfig.VerifyConnection = func(cs ConnectionState) error {
+                               clientVerifyConnection = true
+                               return nil
+                       }
+                       clientConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
+                               clientVerifyPeerCertificates = true
+                               return nil
+                       }
+                       serverConfig.VerifyConnection = func(cs ConnectionState) error {
+                               serverVerifyConnection = true
+                               return nil
+                       }
+                       serverConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
+                               serverVerifyPeerCertificates = true
+                               return nil
+                       }
+
+                       clientConfig.InsecureSkipVerify = test.InsecureSkipVerify
+                       serverConfig.ClientAuth = test.ClientAuth
+                       if !test.ClientCertificates {
+                               clientConfig.Certificates = nil
+                       }
+
+                       if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil {
+                               t.Fatal(err)
+                       }
+
+                       want := serverConfig.ClientAuth != NoClientCert
+                       if serverVerifyPeerCertificates != want {
+                               t.Errorf("VerifyPeerCertificates on the server: got %v, want %v",
+                                       serverVerifyPeerCertificates, want)
+                       }
+                       if !clientVerifyPeerCertificates {
+                               t.Errorf("VerifyPeerCertificates not called on the client")
+                       }
+                       if !serverVerifyConnection {
+                               t.Error("VerifyConnection did not get called on the server")
+                       }
+                       if !clientVerifyConnection {
+                               t.Error("VerifyConnection did not get called on the client")
+                       }
+
+                       serverVerifyPeerCertificates, clientVerifyPeerCertificates = false, false
+                       serverVerifyConnection, clientVerifyConnection = false, false
+                       cs, _, err := testHandshake(t, clientConfig, serverConfig)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       if !cs.DidResume {
+                               t.Error("expected resumption")
+                       }
+
+                       if serverVerifyPeerCertificates {
+                               t.Error("VerifyPeerCertificates got called on the server on resumption")
+                       }
+                       if clientVerifyPeerCertificates {
+                               t.Error("VerifyPeerCertificates got called on the client on resumption")
+                       }
+                       if !serverVerifyConnection {
+                               t.Error("VerifyConnection did not get called on the server on resumption")
+                       }
+                       if !clientVerifyConnection {
+                               t.Error("VerifyConnection did not get called on the client on resumption")
+                       }
+               })
+       }
+}