Commit a62ae9f6 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

crypto/x509: add SystemCertPool, refactor system cert pool loading

This exports the system cert pool.

The system cert loading was refactored to let it be run multiple times
(so callers get a copy, and can't mutate global state), and also to
not discard errors.

SystemCertPool returns an error on Windows. Maybe it's fixable later,
but so far we haven't used it, since the system verifies TLS.

Fixes #13335

Change-Id: I3dfb4656a373f241bae8529076d24c5f532f113c
Reviewed-on: https://go-review.googlesource.com/21293
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAndrew Gerrand <adg@golang.org>
parent 71ab3c1c
...@@ -6,6 +6,8 @@ package x509 ...@@ -6,6 +6,8 @@ package x509
import ( import (
"encoding/pem" "encoding/pem"
"errors"
"runtime"
) )
// CertPool is a set of certificates. // CertPool is a set of certificates.
...@@ -18,12 +20,22 @@ type CertPool struct { ...@@ -18,12 +20,22 @@ type CertPool struct {
// NewCertPool returns a new, empty CertPool. // NewCertPool returns a new, empty CertPool.
func NewCertPool() *CertPool { func NewCertPool() *CertPool {
return &CertPool{ return &CertPool{
make(map[string][]int), bySubjectKeyId: make(map[string][]int),
make(map[string][]int), byName: make(map[string][]int),
nil,
} }
} }
// SystemCertPool returns a copy of the system cert pool.
//
// Any mutations to the returned pool are not written to disk and do
// not affect any other pool.
func SystemCertPool() (*CertPool, error) {
if runtime.GOOS == "windows" {
return nil, errors.New("crypto/x509: system root pool is not available on Windows")
}
return loadSystemRoots()
}
// findVerifiedParents attempts to find certificates in s which have signed the // findVerifiedParents attempts to find certificates in s which have signed the
// given certificate. If any candidates were rejected then errCert will be set // given certificate. If any candidates were rejected then errCert will be set
// to one of them, arbitrarily, and err will contain the reason that it was // to one of them, arbitrarily, and err will contain the reason that it was
...@@ -107,10 +119,10 @@ func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) { ...@@ -107,10 +119,10 @@ func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
// Subjects returns a list of the DER-encoded subjects of // Subjects returns a list of the DER-encoded subjects of
// all of the certificates in the pool. // all of the certificates in the pool.
func (s *CertPool) Subjects() (res [][]byte) { func (s *CertPool) Subjects() [][]byte {
res = make([][]byte, len(s.certs)) res := make([][]byte, len(s.certs))
for i, c := range s.certs { for i, c := range s.certs {
res[i] = c.RawSubject res[i] = c.RawSubject
} }
return return res
} }
...@@ -7,11 +7,16 @@ package x509 ...@@ -7,11 +7,16 @@ package x509
import "sync" import "sync"
var ( var (
once sync.Once once sync.Once
systemRoots *CertPool systemRoots *CertPool
systemRootsErr error
) )
func systemRootsPool() *CertPool { func systemRootsPool() *CertPool {
once.Do(initSystemRoots) once.Do(initSystemRoots)
return systemRoots return systemRoots
} }
func initSystemRoots() {
systemRoots, systemRootsErr = loadSystemRoots()
}
...@@ -61,19 +61,23 @@ int FetchPEMRoots(CFDataRef *pemRoots) { ...@@ -61,19 +61,23 @@ int FetchPEMRoots(CFDataRef *pemRoots) {
} }
*/ */
import "C" import "C"
import "unsafe" import (
"errors"
"unsafe"
)
func initSystemRoots() { func loadSystemRoots() (*CertPool, error) {
roots := NewCertPool() roots := NewCertPool()
var data C.CFDataRef = nil var data C.CFDataRef = nil
err := C.FetchPEMRoots(&data) err := C.FetchPEMRoots(&data)
if err == -1 { if err == -1 {
return // TODO: better error message
return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo")
} }
defer C.CFRelease(C.CFTypeRef(data)) defer C.CFRelease(C.CFTypeRef(data))
buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data))) buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
roots.AppendCertsFromPEM(buf) roots.AppendCertsFromPEM(buf)
systemRoots = roots return roots, nil
} }
...@@ -184,8 +184,9 @@ const header = ` ...@@ -184,8 +184,9 @@ const header = `
package x509 package x509
func initSystemRoots() { func loadSystemRoots() (*CertPool, error) {
systemRoots = NewCertPool() p := NewCertPool()
systemRoots.AppendCertsFromPEM([]byte(systemRootsPEM)) p.AppendCertsFromPEM([]byte(systemRootsPEM))
return p
} }
` `
...@@ -10,9 +10,10 @@ ...@@ -10,9 +10,10 @@
package x509 package x509
func initSystemRoots() { func loadSystemRoots() (*CertPool, error) {
systemRoots = NewCertPool() p := NewCertPool()
systemRoots.AppendCertsFromPEM([]byte(systemRootsPEM)) p.AppendCertsFromPEM([]byte(systemRootsPEM))
return p
} }
const systemRootsPEM = ` const systemRootsPEM = `
......
...@@ -6,6 +6,6 @@ ...@@ -6,6 +6,6 @@
package x509 package x509
func initSystemRoots() { func loadSystemRoots() (*CertPool, error) {
systemRoots, _ = execSecurityRoots() return execSecurityRoots()
} }
...@@ -6,7 +6,10 @@ ...@@ -6,7 +6,10 @@
package x509 package x509
import "io/ioutil" import (
"io/ioutil"
"os"
)
// Possible certificate files; stop after finding one. // Possible certificate files; stop after finding one.
var certFiles = []string{ var certFiles = []string{
...@@ -17,17 +20,18 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate ...@@ -17,17 +20,18 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate
return nil, nil return nil, nil
} }
func initSystemRoots() { func loadSystemRoots() (*CertPool, error) {
roots := NewCertPool() roots := NewCertPool()
var bestErr error
for _, file := range certFiles { for _, file := range certFiles {
data, err := ioutil.ReadFile(file) data, err := ioutil.ReadFile(file)
if err == nil { if err == nil {
roots.AppendCertsFromPEM(data) roots.AppendCertsFromPEM(data)
systemRoots = roots return roots, nil
return }
if bestErr == nil || (os.IsNotExist(bestErr) && !os.IsNotExist(err)) {
bestErr = err
} }
} }
return nil, bestErr
// All of the files failed to load. systemRoots will be nil which will
// trigger a specific error at verification time.
} }
...@@ -6,7 +6,10 @@ ...@@ -6,7 +6,10 @@
package x509 package x509
import "io/ioutil" import (
"io/ioutil"
"os"
)
// Possible directories with certificate files; stop after successfully // Possible directories with certificate files; stop after successfully
// reading at least one file from a directory. // reading at least one file from a directory.
...@@ -19,20 +22,26 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate ...@@ -19,20 +22,26 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate
return nil, nil return nil, nil
} }
func initSystemRoots() { func loadSystemRoots() (*CertPool, error) {
roots := NewCertPool() roots := NewCertPool()
var firstErr error
for _, file := range certFiles { for _, file := range certFiles {
data, err := ioutil.ReadFile(file) data, err := ioutil.ReadFile(file)
if err == nil { if err == nil {
roots.AppendCertsFromPEM(data) roots.AppendCertsFromPEM(data)
systemRoots = roots return roots, nil
return }
if firstErr == nil && !os.IsNotExist(err) {
firstErr = err
} }
} }
for _, directory := range certDirectories { for _, directory := range certDirectories {
fis, err := ioutil.ReadDir(directory) fis, err := ioutil.ReadDir(directory)
if err != nil { if err != nil {
if firstErr == nil && !os.IsNotExist(err) {
firstErr = err
}
continue continue
} }
rootsAdded := false rootsAdded := false
...@@ -43,11 +52,9 @@ func initSystemRoots() { ...@@ -43,11 +52,9 @@ func initSystemRoots() {
} }
} }
if rootsAdded { if rootsAdded {
systemRoots = roots return roots, nil
return
} }
} }
// All of the files failed to load. systemRoots will be nil which will return nil, firstErr
// trigger a specific error at verification time.
} }
...@@ -225,5 +225,4 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate ...@@ -225,5 +225,4 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate
return chains, nil return chains, nil
} }
func initSystemRoots() { func loadSystemRoots() (*CertPool, error) { return nil, nil }
}
...@@ -117,10 +117,16 @@ func (e UnknownAuthorityError) Error() string { ...@@ -117,10 +117,16 @@ func (e UnknownAuthorityError) Error() string {
} }
// SystemRootsError results when we fail to load the system root certificates. // SystemRootsError results when we fail to load the system root certificates.
type SystemRootsError struct{} type SystemRootsError struct {
Err error
}
func (SystemRootsError) Error() string { func (se SystemRootsError) Error() string {
return "x509: failed to load system roots and no roots provided" msg := "x509: failed to load system roots and no roots provided"
if se.Err != nil {
return msg + "; " + se.Err.Error()
}
return msg
} }
// errNotParsed is returned when a certificate without ASN.1 contents is // errNotParsed is returned when a certificate without ASN.1 contents is
...@@ -240,7 +246,7 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e ...@@ -240,7 +246,7 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
if opts.Roots == nil { if opts.Roots == nil {
opts.Roots = systemRootsPool() opts.Roots = systemRootsPool()
if opts.Roots == nil { if opts.Roots == nil {
return nil, SystemRootsError{} return nil, SystemRootsError{systemRootsErr}
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment