1
0
mirror of https://github.com/golang/go synced 2024-11-17 01:54:53 -07:00

crypto/x509: use SAN when comparing certs during path building

Per RFC 4158 Section 2.4.2, when we are discarding candidate
certificates during path building, use the SANs as well as subject and
public key when checking whether a certificate is already present in
the built path. This supports the case where a certificate in the chain
(typically a leaf) has the exact same subject and public key as another
certificate in the chain (typically its parent) but has SANs which don't
match.

Change-Id: I212c234e94a1f6afbe9691e4a3ba257461db3a7e
Reviewed-on: https://go-review.googlesource.com/c/go/+/401115
Reviewed-by: Roland Shoemaker <roland@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Roland Shoemaker 2022-04-19 12:05:10 -07:00
parent 1715a86721
commit b941a10e38
2 changed files with 70 additions and 11 deletions

View File

@ -7,6 +7,7 @@ package x509
import (
"bytes"
"crypto"
"crypto/x509/pkix"
"errors"
"fmt"
"net"
@ -825,6 +826,50 @@ func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate
return n
}
// alreadyInChain checks whether a candidate certificate is present in a chain.
// Rather than doing a direct byte for byte equivalency check, we check if the
// subject, public key, and SAN, if present, are equal. This prevents loops that
// are created by mutual cross-signatures, or other cross-signature bridge
// oddities.
func alreadyInChain(candidate *Certificate, chain []*Certificate) bool {
type pubKeyEqual interface {
Equal(crypto.PublicKey) bool
}
var candidateSAN *pkix.Extension
for _, ext := range candidate.Extensions {
if ext.Id.Equal(oidExtensionSubjectAltName) {
candidateSAN = &ext
break
}
}
for _, cert := range chain {
if !bytes.Equal(candidate.RawSubject, cert.RawSubject) {
continue
}
if !candidate.PublicKey.(pubKeyEqual).Equal(cert.PublicKey) {
continue
}
var certSAN *pkix.Extension
for _, ext := range cert.Extensions {
if ext.Id.Equal(oidExtensionSubjectAltName) {
certSAN = &ext
break
}
}
if candidateSAN == nil && certSAN == nil {
return true
} else if candidateSAN == nil || certSAN == nil {
return false
}
if bytes.Equal(candidateSAN.Value, certSAN.Value) {
return true
}
}
return false
}
// maxChainSignatureChecks is the maximum number of CheckSignatureFrom calls
// that an invocation of buildChains will (transitively) make. Most chains are
// less than 15 certificates long, so this leaves space for multiple chains and
@ -837,18 +882,9 @@ func (c *Certificate) buildChains(currentChain []*Certificate, sigChecks *int, o
hintCert *Certificate
)
type pubKeyEqual interface {
Equal(crypto.PublicKey) bool
}
considerCandidate := func(certType int, candidate *Certificate) {
for _, cert := range currentChain {
// If a certificate already appeared in the chain we've built, don't
// reconsider it. This prevents loops, for isntance those created by
// mutual cross-signatures, or other cross-signature bridges oddities.
if bytes.Equal(cert.RawSubject, candidate.RawSubject) && cert.PublicKey.(pubKeyEqual).Equal(candidate.PublicKey) {
return
}
if alreadyInChain(candidate, currentChain) {
return
}
if sigChecks == nil {

View File

@ -2340,6 +2340,29 @@ func TestPathBuilding(t *testing.T) {
"CN=leaf -> CN=inter b -> CN=inter c -> CN=root",
},
},
{
// Build a simple two node graph, where the leaf is directly issued from
// the root and both certificates have matching subject and public key, but
// the leaf has SANs.
name: "leaf with same subject, key, as parent but with SAN",
graph: trustGraphDescription{
Roots: []string{"root"},
Leaf: "root",
Graph: []trustGraphEdge{
{
Issuer: "root",
Subject: "root",
Type: leafCertificate,
MutateTemplate: func(c *Certificate) {
c.DNSNames = []string{"localhost"}
},
},
},
},
expectedChains: []string{
"CN=root -> CN=root",
},
},
}
for _, tc := range tests {