Skip to content

Commit b941a10

Browse files
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 <[email protected]> Run-TryBot: Roland Shoemaker <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Damien Neil <[email protected]>
1 parent 1715a86 commit b941a10

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

src/crypto/x509/verify.go

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package x509
77
import (
88
"bytes"
99
"crypto"
10+
"crypto/x509/pkix"
1011
"errors"
1112
"fmt"
1213
"net"
@@ -825,6 +826,50 @@ func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate
825826
return n
826827
}
827828

829+
// alreadyInChain checks whether a candidate certificate is present in a chain.
830+
// Rather than doing a direct byte for byte equivalency check, we check if the
831+
// subject, public key, and SAN, if present, are equal. This prevents loops that
832+
// are created by mutual cross-signatures, or other cross-signature bridge
833+
// oddities.
834+
func alreadyInChain(candidate *Certificate, chain []*Certificate) bool {
835+
type pubKeyEqual interface {
836+
Equal(crypto.PublicKey) bool
837+
}
838+
839+
var candidateSAN *pkix.Extension
840+
for _, ext := range candidate.Extensions {
841+
if ext.Id.Equal(oidExtensionSubjectAltName) {
842+
candidateSAN = &ext
843+
break
844+
}
845+
}
846+
847+
for _, cert := range chain {
848+
if !bytes.Equal(candidate.RawSubject, cert.RawSubject) {
849+
continue
850+
}
851+
if !candidate.PublicKey.(pubKeyEqual).Equal(cert.PublicKey) {
852+
continue
853+
}
854+
var certSAN *pkix.Extension
855+
for _, ext := range cert.Extensions {
856+
if ext.Id.Equal(oidExtensionSubjectAltName) {
857+
certSAN = &ext
858+
break
859+
}
860+
}
861+
if candidateSAN == nil && certSAN == nil {
862+
return true
863+
} else if candidateSAN == nil || certSAN == nil {
864+
return false
865+
}
866+
if bytes.Equal(candidateSAN.Value, certSAN.Value) {
867+
return true
868+
}
869+
}
870+
return false
871+
}
872+
828873
// maxChainSignatureChecks is the maximum number of CheckSignatureFrom calls
829874
// that an invocation of buildChains will (transitively) make. Most chains are
830875
// 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
837882
hintCert *Certificate
838883
)
839884

840-
type pubKeyEqual interface {
841-
Equal(crypto.PublicKey) bool
842-
}
843-
844885
considerCandidate := func(certType int, candidate *Certificate) {
845-
for _, cert := range currentChain {
846-
// If a certificate already appeared in the chain we've built, don't
847-
// reconsider it. This prevents loops, for isntance those created by
848-
// mutual cross-signatures, or other cross-signature bridges oddities.
849-
if bytes.Equal(cert.RawSubject, candidate.RawSubject) && cert.PublicKey.(pubKeyEqual).Equal(candidate.PublicKey) {
850-
return
851-
}
886+
if alreadyInChain(candidate, currentChain) {
887+
return
852888
}
853889

854890
if sigChecks == nil {

src/crypto/x509/verify_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2340,6 +2340,29 @@ func TestPathBuilding(t *testing.T) {
23402340
"CN=leaf -> CN=inter b -> CN=inter c -> CN=root",
23412341
},
23422342
},
2343+
{
2344+
// Build a simple two node graph, where the leaf is directly issued from
2345+
// the root and both certificates have matching subject and public key, but
2346+
// the leaf has SANs.
2347+
name: "leaf with same subject, key, as parent but with SAN",
2348+
graph: trustGraphDescription{
2349+
Roots: []string{"root"},
2350+
Leaf: "root",
2351+
Graph: []trustGraphEdge{
2352+
{
2353+
Issuer: "root",
2354+
Subject: "root",
2355+
Type: leafCertificate,
2356+
MutateTemplate: func(c *Certificate) {
2357+
c.DNSNames = []string{"localhost"}
2358+
},
2359+
},
2360+
},
2361+
},
2362+
expectedChains: []string{
2363+
"CN=root -> CN=root",
2364+
},
2365+
},
23432366
}
23442367

23452368
for _, tc := range tests {

0 commit comments

Comments
 (0)