]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/x509: use SAN when comparing certs during path building
authorRoland Shoemaker <roland@golang.org>
Tue, 19 Apr 2022 19:05:10 +0000 (12:05 -0700)
committerRoland Shoemaker <roland@golang.org>
Thu, 21 Apr 2022 16:18:44 +0000 (16:18 +0000)
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>
src/crypto/x509/verify.go
src/crypto/x509/verify_test.go

index 77ad6868fa65fe7fb90cb81fe2acebff7e565b0e..a739956cfe070f94db01652295b56380b48ad7c3 100644 (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 {
index 1b2cbe34dd2f8cc2817dfcc0456adfc9f86360a7..8a7b08ab58f873773f5defbbc47ee29bab6216d3 100644 (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 {