From 933c251eb524f541ed1ce9f738753fe1e08f6b3a Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 10 May 2026 21:58:35 -0400 Subject: [PATCH] crypto/tls: check verifiedChains roots when resuming sessions When resuming TLS sessions, on the server and client verify that the chains stored in the session state (verifiedChains) are still acceptable with regards to the Config by checking for the inclusion of the root in either ClientCAs (server) or RootCAs (client). This prevents resuming a session with a certificate chain that would be rejected during a full handshake due to an untrusted root. Updates #77113 Updates #77217 Updates CVE-2025-68121 Change-Id: I11fe00909ef1961c24ecf80bf5b97f7b1121d359 Reviewed-on: https://go-review.googlesource.com/c/go/+/737700 Auto-Submit: Roland Shoemaker Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Reviewed-by: Coia Prant Reviewed-by: Filippo Valsorda --- common.go | 25 ++++++++++++++++++++----- handshake_client.go | 7 ++++++- handshake_server.go | 7 ++++++- handshake_server_tls13.go | 7 ++++++- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/common.go b/common.go index 8f357da..01a5129 100644 --- a/common.go +++ b/common.go @@ -1881,13 +1881,28 @@ func fipsAllowChain(chain []*x509.Certificate) bool { return true } -// anyUnexpiredChain reports if at least one of verifiedChains is still -// unexpired. If verifiedChains is empty, it returns false. -func anyUnexpiredChain(verifiedChains [][]*x509.Certificate, now time.Time) bool { +// anyValidVerifiedChain reports if at least one of the chains in verifiedChains +// is valid, as indicated by none of the certificates being expired and the root +// being in opts.Roots (or in the system root pool if opts.Roots is nil). If +// verifiedChains is empty, it returns false. +func anyValidVerifiedChain(verifiedChains [][]*x509.Certificate, opts x509.VerifyOptions) bool { for _, chain := range verifiedChains { - if len(chain) != 0 && !slices.ContainsFunc(chain, func(cert *x509.Certificate) bool { - return now.Before(cert.NotBefore) || now.After(cert.NotAfter) // cert is expired + if len(chain) == 0 { + continue + } + if slices.ContainsFunc(chain, func(cert *x509.Certificate) bool { + return opts.CurrentTime.Before(cert.NotBefore) || opts.CurrentTime.After(cert.NotAfter) }) { + continue + } + // Since we already validated the chain, we only care that it is + // rooted in a CA in CAs, or in the system pool. On platforms where + // we control chain validation (e.g. not Windows or macOS) this is a + // simple lookup in the CertPool internal hash map. On other + // platforms, this may be more expensive, depending on how they + // implement verification of just root certificates. + root := chain[len(chain)-1] + if _, err := root.Verify(opts); err == nil { return true } } diff --git a/handshake_client.go b/handshake_client.go index bb2f8eb..c05b646 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -414,7 +414,12 @@ func (c *Conn) loadSession(hello *clientHelloMsg) ( // application from a faulty ClientSessionCache implementation. return nil, nil, nil, nil } - if !anyUnexpiredChain(session.verifiedChains, c.config.time()) { + opts := x509.VerifyOptions{ + CurrentTime: c.config.time(), + Roots: c.config.RootCAs, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + } + if !anyValidVerifiedChain(session.verifiedChains, opts) { // No valid chains, delete the entry. c.config.ClientSessionCache.Put(cacheKey, nil) return nil, nil, nil, nil diff --git a/handshake_server.go b/handshake_server.go index 3c8a571..fdb1135 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -510,8 +510,13 @@ func (hs *serverHandshakeState) checkForResumption() error { if sessionHasClientCerts && c.config.time().After(sessionState.peerCertificates[0].NotAfter) { return nil } + opts := x509.VerifyOptions{ + CurrentTime: c.config.time(), + Roots: c.config.ClientCAs, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } if sessionHasClientCerts && c.config.ClientAuth >= VerifyClientCertIfGiven && - !anyUnexpiredChain(sessionState.verifiedChains, c.config.time()) { + !anyValidVerifiedChain(sessionState.verifiedChains, opts) { return nil } diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index d4f9ef8..4d049aa 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -464,8 +464,13 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error { if sessionHasClientCerts && c.config.time().After(sessionState.peerCertificates[0].NotAfter) { continue } + opts := x509.VerifyOptions{ + CurrentTime: c.config.time(), + Roots: c.config.ClientCAs, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } if sessionHasClientCerts && c.config.ClientAuth >= VerifyClientCertIfGiven && - !anyUnexpiredChain(sessionState.verifiedChains, c.config.time()) { + !anyValidVerifiedChain(sessionState.verifiedChains, opts) { continue }