]> Cypherpunks.ru repositories - gostls13.git/commitdiff
crypto/x509: verification with system and custom roots
authorRoland Shoemaker <roland@golang.org>
Fri, 1 Oct 2021 17:14:32 +0000 (10:14 -0700)
committerFilippo Valsorda <filippo@golang.org>
Sat, 6 Nov 2021 16:43:43 +0000 (16:43 +0000)
Make system cert pools special, such that when one has extra roots
added to it we run verifications twice, once using the platform
verifier, if available, and once using the Go verifier, merging the
results.

This change re-enables SystemCertPool on Windows, but explicitly does
not return anything from CertPool.Subjects (which matches the behavior
of macOS). CertPool.Subjects is also marked deprecated.

Fixes #46287
Fixes #16736

Change-Id: Idc1843f715ae2b2d0108e55ab942c287181a340a
Reviewed-on: https://go-review.googlesource.com/c/go/+/353589
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Trust: Roland Shoemaker <roland@golang.org>

src/crypto/x509/cert_pool.go
src/crypto/x509/hybrid_pool_test.go [new file with mode: 0644]
src/crypto/x509/root_darwin.go
src/crypto/x509/root_windows.go
src/crypto/x509/root_windows_test.go [new file with mode: 0644]
src/crypto/x509/verify.go

index d760dc11c60a8e31719ca176388fc35957912655..873ffeee1dae21703a5c0cdf3a92da42780c4d48 100644 (file)
@@ -8,8 +8,6 @@ import (
        "bytes"
        "crypto/sha256"
        "encoding/pem"
-       "errors"
-       "runtime"
        "sync"
 )
 
@@ -29,6 +27,12 @@ type CertPool struct {
        // call getCert and otherwise negate savings from lazy getCert
        // funcs).
        haveSum map[sum224]bool
+
+       // systemPool indicates whether this is a special pool derived from the
+       // system roots. If it includes additional roots, it requires doing two
+       // verifications, one using the roots provided by the caller, and one using
+       // the system platform verifier.
+       systemPool bool
 }
 
 // lazyCert is minimal metadata about a Cert and a func to retrieve it
@@ -75,9 +79,10 @@ func (s *CertPool) cert(n int) (*Certificate, error) {
 
 func (s *CertPool) copy() *CertPool {
        p := &CertPool{
-               byName:    make(map[string][]int, len(s.byName)),
-               lazyCerts: make([]lazyCert, len(s.lazyCerts)),
-               haveSum:   make(map[sum224]bool, len(s.haveSum)),
+               byName:     make(map[string][]int, len(s.byName)),
+               lazyCerts:  make([]lazyCert, len(s.lazyCerts)),
+               haveSum:    make(map[sum224]bool, len(s.haveSum)),
+               systemPool: s.systemPool,
        }
        for k, v := range s.byName {
                indexes := make([]int, len(v))
@@ -103,15 +108,6 @@ func (s *CertPool) copy() *CertPool {
 //
 // New changes in the system cert pool might not be reflected in subsequent calls.
 func SystemCertPool() (*CertPool, error) {
-       if runtime.GOOS == "windows" {
-               // Issue 16736, 18609:
-               return nil, errors.New("crypto/x509: system root pool is not available on Windows")
-       } else if runtime.GOOS == "darwin" {
-               return nil, errors.New("crypto/x509: system root pool is not available on macOS")
-       } else if runtime.GOOS == "ios" {
-               return nil, errors.New("crypto/x509: system root pool is not available on iOS")
-       }
-
        if sysRoots := systemRootsPool(); sysRoots != nil {
                return sysRoots.copy(), nil
        }
@@ -243,6 +239,9 @@ func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
 
 // Subjects returns a list of the DER-encoded subjects of
 // all of the certificates in the pool.
+//
+// Deprecated: if s was returned by SystemCertPool, Subjects
+// will not include the system roots.
 func (s *CertPool) Subjects() [][]byte {
        res := make([][]byte, s.len())
        for i, lc := range s.lazyCerts {
diff --git a/src/crypto/x509/hybrid_pool_test.go b/src/crypto/x509/hybrid_pool_test.go
new file mode 100644 (file)
index 0000000..d4dd9d5
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x509_test
+
+import (
+       "crypto/ecdsa"
+       "crypto/elliptic"
+       "crypto/rand"
+       "crypto/tls"
+       "crypto/x509"
+       "crypto/x509/pkix"
+       "internal/testenv"
+       "math/big"
+       "runtime"
+       "testing"
+       "time"
+)
+
+func TestHybridPool(t *testing.T) {
+       if !(runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios") {
+               t.Skipf("platform verifier not available on %s", runtime.GOOS)
+       }
+       if !testenv.HasExternalNetwork() {
+               t.Skip()
+       }
+
+       // Get the google.com chain, which should be valid on all platforms we
+       // are testing
+       c, err := tls.Dial("tcp", "google.com:443", &tls.Config{InsecureSkipVerify: true})
+       if err != nil {
+               t.Fatalf("tls connection failed: %s", err)
+       }
+       googChain := c.ConnectionState().PeerCertificates
+
+       rootTmpl := &x509.Certificate{
+               SerialNumber:          big.NewInt(1),
+               Subject:               pkix.Name{CommonName: "Go test root"},
+               IsCA:                  true,
+               BasicConstraintsValid: true,
+               NotBefore:             time.Now().Add(-time.Hour),
+               NotAfter:              time.Now().Add(time.Hour * 10),
+       }
+       k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+       if err != nil {
+               t.Fatalf("failed to generate test key: %s", err)
+       }
+       rootDER, err := x509.CreateCertificate(rand.Reader, rootTmpl, rootTmpl, k.Public(), k)
+       if err != nil {
+               t.Fatalf("failed to create test cert: %s", err)
+       }
+       root, err := x509.ParseCertificate(rootDER)
+       if err != nil {
+               t.Fatalf("failed to parse test cert: %s", err)
+       }
+
+       pool, err := x509.SystemCertPool()
+       if err != nil {
+               t.Fatalf("SystemCertPool failed: %s", err)
+       }
+       opts := x509.VerifyOptions{Roots: pool}
+
+       _, err = googChain[0].Verify(opts)
+       if err != nil {
+               t.Fatalf("verification failed for google.com chain (empty pool): %s", err)
+       }
+
+       pool.AddCert(root)
+
+       _, err = googChain[0].Verify(opts)
+       if err != nil {
+               t.Fatalf("verification failed for google.com chain (hybrid pool): %s", err)
+       }
+
+       certTmpl := &x509.Certificate{
+               SerialNumber: big.NewInt(1),
+               NotBefore:    time.Now().Add(-time.Hour),
+               NotAfter:     time.Now().Add(time.Hour * 10),
+               DNSNames:     []string{"example.com"},
+       }
+       certDER, err := x509.CreateCertificate(rand.Reader, certTmpl, rootTmpl, k.Public(), k)
+       if err != nil {
+               t.Fatalf("failed to create test cert: %s", err)
+       }
+       cert, err := x509.ParseCertificate(certDER)
+       if err != nil {
+               t.Fatalf("failed to parse test cert: %s", err)
+       }
+
+       _, err = cert.Verify(opts)
+       if err != nil {
+               t.Fatalf("verification failed for custom chain (hybrid pool): %s", err)
+       }
+}
index 7bc6ce09fa0e265a018de2b6fe4ccb3bf2c43897..a7ff1e78bbff13f850433e5fe229c40fb101262a 100644 (file)
@@ -107,5 +107,5 @@ func exportCertificate(cert macOS.CFRef) (*Certificate, error) {
 }
 
 func loadSystemRoots() (*CertPool, error) {
-       return nil, nil
+       return &CertPool{systemPool: true}, nil
 }
index f77ea3a69840f61a3979a6ae5cbdf163625605b9..d65d8768d9052885cfed1972f74b43d4649502e4 100644 (file)
@@ -10,6 +10,10 @@ import (
        "unsafe"
 )
 
+func loadSystemRoots() (*CertPool, error) {
+       return &CertPool{systemPool: true}, nil
+}
+
 // Creates a new *syscall.CertContext representing the leaf certificate in an in-memory
 // certificate store containing itself and all of the intermediate certificates specified
 // in the opts.Intermediates CertPool.
@@ -271,47 +275,3 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate
 
        return chains, nil
 }
-
-func loadSystemRoots() (*CertPool, error) {
-       // TODO: restore this functionality on Windows. We tried to do
-       // it in Go 1.8 but had to revert it. See Issue 18609.
-       // Returning (nil, nil) was the old behavior, prior to CL 30578.
-       // The if statement here avoids vet complaining about
-       // unreachable code below.
-       if true {
-               return nil, nil
-       }
-
-       const CRYPT_E_NOT_FOUND = 0x80092004
-
-       store, err := syscall.CertOpenSystemStore(0, syscall.StringToUTF16Ptr("ROOT"))
-       if err != nil {
-               return nil, err
-       }
-       defer syscall.CertCloseStore(store, 0)
-
-       roots := NewCertPool()
-       var cert *syscall.CertContext
-       for {
-               cert, err = syscall.CertEnumCertificatesInStore(store, cert)
-               if err != nil {
-                       if errno, ok := err.(syscall.Errno); ok {
-                               if errno == CRYPT_E_NOT_FOUND {
-                                       break
-                               }
-                       }
-                       return nil, err
-               }
-               if cert == nil {
-                       break
-               }
-               // Copy the buf, since ParseCertificate does not create its own copy.
-               buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:cert.Length:cert.Length]
-               buf2 := make([]byte, cert.Length)
-               copy(buf2, buf)
-               if c, err := ParseCertificate(buf2); err == nil {
-                       roots.AddCert(c)
-               }
-       }
-       return roots, nil
-}
diff --git a/src/crypto/x509/root_windows_test.go b/src/crypto/x509/root_windows_test.go
new file mode 100644 (file)
index 0000000..ce6d927
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x509_test
+
+import (
+       "crypto/tls"
+       "crypto/x509"
+       "internal/testenv"
+       "testing"
+       "time"
+)
+
+func TestPlatformVerifier(t *testing.T) {
+       if !testenv.HasExternalNetwork() {
+               t.Skip()
+       }
+
+       getChain := func(host string) []*x509.Certificate {
+               t.Helper()
+               c, err := tls.Dial("tcp", host+":443", &tls.Config{InsecureSkipVerify: true})
+               if err != nil {
+                       t.Fatalf("tls connection failed: %s", err)
+               }
+               return c.ConnectionState().PeerCertificates
+       }
+
+       tests := []struct {
+               name        string
+               host        string
+               verifyName  string
+               verifyTime  time.Time
+               expectedErr string
+       }{
+               {
+                       // whatever google.com serves should, hopefully, be trusted
+                       name: "valid chain",
+                       host: "google.com",
+               },
+               {
+                       name:        "expired leaf",
+                       host:        "expired.badssl.com",
+                       expectedErr: "x509: certificate has expired or is not yet valid: ",
+               },
+               {
+                       name:        "wrong host for leaf",
+                       host:        "wrong.host.badssl.com",
+                       verifyName:  "wrong.host.badssl.com",
+                       expectedErr: "x509: certificate is valid for *.badssl.com, badssl.com, not wrong.host.badssl.com",
+               },
+               {
+                       name:        "self-signed leaf",
+                       host:        "self-signed.badssl.com",
+                       expectedErr: "x509: certificate signed by unknown authority",
+               },
+               {
+                       name:        "untrusted root",
+                       host:        "untrusted-root.badssl.com",
+                       expectedErr: "x509: certificate signed by unknown authority",
+               },
+               {
+                       name:        "expired leaf (custom time)",
+                       host:        "google.com",
+                       verifyTime:  time.Time{}.Add(time.Hour),
+                       expectedErr: "x509: certificate has expired or is not yet valid: ",
+               },
+               {
+                       name:       "valid chain (custom time)",
+                       host:       "google.com",
+                       verifyTime: time.Now(),
+               },
+       }
+
+       for _, tc := range tests {
+               t.Run(tc.name, func(t *testing.T) {
+                       chain := getChain(tc.host)
+                       var opts x509.VerifyOptions
+                       if len(chain) > 1 {
+                               opts.Intermediates = x509.NewCertPool()
+                               for _, c := range chain[1:] {
+                                       opts.Intermediates.AddCert(c)
+                               }
+                       }
+                       if tc.verifyName != "" {
+                               opts.DNSName = tc.verifyName
+                       }
+                       if !tc.verifyTime.IsZero() {
+                               opts.CurrentTime = tc.verifyTime
+                       }
+
+                       _, err := chain[0].Verify(opts)
+                       if err != nil && tc.expectedErr == "" {
+                               t.Errorf("unexpected verification error: %s", err)
+                       } else if err != nil && err.Error() != tc.expectedErr {
+                               t.Errorf("unexpected verification error: got %q, want %q", err.Error(), tc.expectedErr)
+                       } else if err == nil && tc.expectedErr != "" {
+                               t.Errorf("unexpected verification success: want %q", tc.expectedErr)
+                       }
+               })
+       }
+}
index 59852d9d68a13040d64422f80345c4ec4aff97dd..1562ee57af1a9f50167b18d2e6220d39792f4d3f 100644 (file)
@@ -741,9 +741,20 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
                }
        }
 
-       // Use platform verifiers, where available
-       if opts.Roots == nil && (runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios") {
-               return c.systemVerify(&opts)
+       // Use platform verifiers, where available, if Roots is from SystemCertPool.
+       if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
+               if opts.Roots == nil {
+                       return c.systemVerify(&opts)
+               }
+               if opts.Roots != nil && opts.Roots.systemPool {
+                       platformChains, err := c.systemVerify(&opts)
+                       // If the platform verifier succeeded, or there are no additional
+                       // roots, return the platform verifier result. Otherwise, continue
+                       // with the Go verifier.
+                       if err == nil || opts.Roots.len() == 0 {
+                               return platformChains, err
+                       }
+               }
        }
 
        if opts.Roots == nil {