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 }