crypto/tls: add LocalCertificate to ConnectionState Adds a new field to ConnectionState, LocalCertificate, which contains the certificate chain which was presented to the connection peer, if one was. Fixes #24673 Change-Id: Iae758fef4a2808e3295324890f4b2f55c71fcc4c Reviewed-on: https://go-review.googlesource.com/c/go/+/788866 Auto-Submit: Roland Shoemaker <roland@golang.org> Reviewed-by: Cherry Mui <cherryyz@google.com> TryBot-Bypass: Roland Shoemaker <roland@golang.org> Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
diff --git a/api/go1.27.txt b/api/go1.27.txt index 93a08c2..2520fba 100644 --- a/api/go1.27.txt +++ b/api/go1.27.txt
@@ -49,6 +49,7 @@ pkg crypto/tls, const MLKEM1024 = 514 #78543 pkg crypto/tls, const MLKEM1024 CurveID #78543 pkg crypto/tls, type Config struct, Rand //deprecated #79367 +pkg crypto/tls, type ConnectionState struct, LocalCertificate [][]uint8 #24673 pkg crypto/tls, type QUICConfig struct, ClientHelloInfoConn net.Conn #77363 pkg crypto/x509, const MLDSA = 5 #78888 pkg crypto/x509, const MLDSA PublicKeyAlgorithm #78888
diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 067b45f..dd4aaf5 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go
@@ -325,6 +325,11 @@ // are a server, or if we received a HelloRetryRequest if we are a client. HelloRetryRequest bool + // LocalCertificate is the certificate chain presented to the peer, if any, + // during the handshake. This field is only populated for connections which + // are not resumed (DidResume is false). + LocalCertificate [][]byte + // ekm is a closure exposed via ExportKeyingMaterial. ekm func(label string, context []byte, length int) ([]byte, error)
diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index b4508d6..fb1e9f0 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go
@@ -54,6 +54,7 @@ ocspResponse []byte // stapled OCSP response scts [][]byte // signed certificate timestamps from server peerCertificates []*x509.Certificate + localCertificate [][]byte // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate @@ -1622,6 +1623,7 @@ state.ServerName = c.serverName state.CipherSuite = c.cipherSuite state.PeerCertificates = c.peerCertificates + state.LocalCertificate = c.localCertificate state.VerifiedChains = c.verifiedChains state.SignedCertificateTimestamps = c.scts state.OCSPResponse = c.ocspResponse
diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index 52bf608..54227aa 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go
@@ -728,6 +728,10 @@ } } + if chainToSend != nil { + hs.c.localCertificate = chainToSend.Certificate + } + shd, ok := msg.(*serverHelloDoneMsg) if !ok { c.sendAlert(alertUnexpectedMessage)
diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go index d367020..f551506 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go
@@ -756,6 +756,10 @@ return err } + if cert != nil { + hs.c.localCertificate = cert.Certificate + } + certMsg := new(certificateMsgTLS13) certMsg.certificate = *cert
diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index cde7046..a4544a1 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go
@@ -279,6 +279,11 @@ } return err } + + if hs.cert != nil { + hs.c.localCertificate = hs.cert.Certificate + } + if hs.clientHello.scts { hs.hello.scts = hs.cert.SignedCertificateTimestamps }
diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index 3175d74..2c96e24 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go
@@ -493,6 +493,9 @@ } return err } + if certificate != nil { + hs.c.localCertificate = certificate.Certificate + } hs.sigAlg, err = selectSignatureScheme(c.vers, certificate, hs.clientHello.supportedSignatureAlgorithms) if err != nil { // getCertificate returned a certificate that is unsupported or
diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index c17d4a8..2bfb645 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go
@@ -2947,3 +2947,119 @@ t.Errorf("got %q, want %q", got, want) } } + +func testLocalCertificate(t *testing.T, version uint16, callback bool) { + clientConfig, serverConfig := testConfigClient.Clone(), testConfigServer.Clone() + + clientConfig.MinVersion, serverConfig.MinVersion = version, version + serverConfig.ClientAuth = RequestClientCert + + serverCert, clientCert := testConfigServer.Certificates[0], testConfigClient.Certificates[0] + + if callback { + clientConfig.GetClientCertificate = func(_ *CertificateRequestInfo) (*Certificate, error) { + return &Certificate{ + Certificate: clientCert.Certificate, + PrivateKey: clientCert.PrivateKey, + }, nil + } + clientConfig.Certificates = nil + serverConfig.GetCertificate = func(_ *ClientHelloInfo) (*Certificate, error) { + return &Certificate{ + Certificate: serverCert.Certificate, + PrivateKey: serverCert.PrivateKey, + }, nil + } + serverConfig.Certificates = nil + } + + ss, cs, err := testHandshake(t, clientConfig, serverConfig) + if err != nil { + t.Fatalf("unexpected failure: %s", err) + } + + if !slices.EqualFunc(ss.LocalCertificate, serverCert.Certificate, bytes.Equal) { + t.Errorf("unexpected server local certificate: %x, want %x", ss.LocalCertificate, serverCert.Certificate) + } + + if !slices.EqualFunc(cs.LocalCertificate, clientCert.Certificate, bytes.Equal) { + t.Errorf("unexpected client local certificate: %x, want %x", cs.LocalCertificate, clientCert.Certificate) + } +} + +func TestLocalCertificate(t *testing.T) { + for _, useCallback := range []bool{true, false} { + name := "UseCertificates" + if useCallback { + name = "UseCallbacks" + } + t.Run(name, func(t *testing.T) { + for _, v := range []uint16{VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13} { + t.Run(VersionName(v), func(t *testing.T) { + testLocalCertificate(t, v, useCallback) + }) + } + }) + } +} + +func testLocalCertificateResumption(t *testing.T, version uint16, callback bool) { + clientConfig, serverConfig := testConfigClient.Clone(), testConfigServer.Clone() + + clientConfig.MinVersion, serverConfig.MinVersion = version, version + clientConfig.ClientSessionCache = NewLRUClientSessionCache(1) + serverConfig.ClientAuth = RequestClientCert + + serverCert, clientCert := testConfigServer.Certificates[0], testConfigClient.Certificates[0] + + if callback { + clientConfig.GetClientCertificate = func(_ *CertificateRequestInfo) (*Certificate, error) { + return &Certificate{ + Certificate: clientCert.Certificate, + PrivateKey: clientCert.PrivateKey, + }, nil + } + clientConfig.Certificates = nil + serverConfig.GetCertificate = func(_ *ClientHelloInfo) (*Certificate, error) { + return &Certificate{ + Certificate: serverCert.Certificate, + PrivateKey: serverCert.PrivateKey, + }, nil + } + serverConfig.Certificates = nil + } + + if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil { + t.Fatalf("first handshake failed: %s", err) + } + + ss, cs, err := testHandshake(t, clientConfig, serverConfig) + if err != nil { + t.Fatalf("second handshake failed: %s", err) + } + if !ss.DidResume || !cs.DidResume { + t.Fatalf("second handshake did not resume (server=%v client=%v)", ss.DidResume, cs.DidResume) + } + if ss.LocalCertificate != nil { + t.Errorf("server LocalCertificate on resumed connection: got %x, want nil", ss.LocalCertificate) + } + if cs.LocalCertificate != nil { + t.Errorf("client LocalCertificate on resumed connection: got %x, want nil", cs.LocalCertificate) + } +} + +func TestLocalCertificateResumption(t *testing.T) { + for _, useCallback := range []bool{true, false} { + name := "UseCertificates" + if useCallback { + name = "UseCallbacks" + } + t.Run(name, func(t *testing.T) { + for _, v := range []uint16{VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13} { + t.Run(VersionName(v), func(t *testing.T) { + testLocalCertificateResumption(t, v, useCallback) + }) + } + }) + } +}