diff options
Diffstat (limited to 'libgo/go/crypto/x509')
-rw-r--r-- | libgo/go/crypto/x509/pkcs1.go | 2 | ||||
-rw-r--r-- | libgo/go/crypto/x509/root.go | 17 | ||||
-rw-r--r-- | libgo/go/crypto/x509/root_darwin.go | 80 | ||||
-rw-r--r-- | libgo/go/crypto/x509/root_stub.go | 15 | ||||
-rw-r--r-- | libgo/go/crypto/x509/root_unix.go | 35 | ||||
-rw-r--r-- | libgo/go/crypto/x509/root_windows.go | 226 | ||||
-rw-r--r-- | libgo/go/crypto/x509/verify.go | 36 | ||||
-rw-r--r-- | libgo/go/crypto/x509/verify_test.go | 53 | ||||
-rw-r--r-- | libgo/go/crypto/x509/x509.go | 2 |
9 files changed, 439 insertions, 27 deletions
diff --git a/libgo/go/crypto/x509/pkcs1.go b/libgo/go/crypto/x509/pkcs1.go index 3aaa8c5832a..873d3966eb5 100644 --- a/libgo/go/crypto/x509/pkcs1.go +++ b/libgo/go/crypto/x509/pkcs1.go @@ -24,7 +24,7 @@ type pkcs1PrivateKey struct { Dq *big.Int `asn1:"optional"` Qinv *big.Int `asn1:"optional"` - AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional"` + AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"` } type pkcs1AdditionalRSAPrime struct { diff --git a/libgo/go/crypto/x509/root.go b/libgo/go/crypto/x509/root.go new file mode 100644 index 00000000000..8aae14e09ee --- /dev/null +++ b/libgo/go/crypto/x509/root.go @@ -0,0 +1,17 @@ +// Copyright 2012 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 + +import "sync" + +var ( + once sync.Once + systemRoots *CertPool +) + +func systemRootsPool() *CertPool { + once.Do(initSystemRoots) + return systemRoots +} diff --git a/libgo/go/crypto/x509/root_darwin.go b/libgo/go/crypto/x509/root_darwin.go new file mode 100644 index 00000000000..0f99581e8a7 --- /dev/null +++ b/libgo/go/crypto/x509/root_darwin.go @@ -0,0 +1,80 @@ +// 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 + +/* +#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1060 +#cgo LDFLAGS: -framework CoreFoundation -framework Security + +#include <CoreFoundation/CoreFoundation.h> +#include <Security/Security.h> + +// FetchPEMRoots fetches the system's list of trusted X.509 root certificates. +// +// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root +// certificates of the system. On failure, the function returns -1. +// +// Note: The CFDataRef returned in pemRoots must be released (using CFRelease) after +// we've consumed its content. +int FetchPEMRoots(CFDataRef *pemRoots) { + if (pemRoots == NULL) { + return -1; + } + + CFArrayRef certs = NULL; + OSStatus err = SecTrustCopyAnchorCertificates(&certs); + if (err != noErr) { + return -1; + } + + CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0); + int i, ncerts = CFArrayGetCount(certs); + for (i = 0; i < ncerts; i++) { + CFDataRef data = NULL; + SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i); + if (cert == NULL) { + continue; + } + + // Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport. + // Once we support weak imports via cgo we should prefer that, and fall back to this + // for older systems. + err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); + if (err != noErr) { + continue; + } + + if (data != NULL) { + CFDataAppendBytes(combinedData, CFDataGetBytePtr(data), CFDataGetLength(data)); + CFRelease(data); + } + } + + CFRelease(certs); + + *pemRoots = combinedData; + return 0; +} +*/ +import "C" +import "unsafe" + +func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { + return nil, nil +} + +func initSystemRoots() { + roots := NewCertPool() + + var data C.CFDataRef = nil + err := C.FetchPEMRoots(&data) + if err != -1 { + defer C.CFRelease(C.CFTypeRef(data)) + buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data))) + roots.AppendCertsFromPEM(buf) + } + + systemRoots = roots +} diff --git a/libgo/go/crypto/x509/root_stub.go b/libgo/go/crypto/x509/root_stub.go new file mode 100644 index 00000000000..568004108b5 --- /dev/null +++ b/libgo/go/crypto/x509/root_stub.go @@ -0,0 +1,15 @@ +// 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. + +// +build plan9 darwin,!cgo + +package x509 + +func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { + return nil, nil +} + +func initSystemRoots() { + systemRoots = NewCertPool() +} diff --git a/libgo/go/crypto/x509/root_unix.go b/libgo/go/crypto/x509/root_unix.go new file mode 100644 index 00000000000..76e79f494f7 --- /dev/null +++ b/libgo/go/crypto/x509/root_unix.go @@ -0,0 +1,35 @@ +// 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. + +// +build freebsd linux openbsd netbsd + +package x509 + +import "io/ioutil" + +// Possible certificate files; stop after finding one. +var certFiles = []string{ + "/etc/ssl/certs/ca-certificates.crt", // Linux etc + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/ssl/cert.pem", // OpenBSD + "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD +} + +func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { + return nil, nil +} + +func initSystemRoots() { + roots := NewCertPool() + for _, file := range certFiles { + data, err := ioutil.ReadFile(file) + if err == nil { + roots.AppendCertsFromPEM(data) + break + } + } + + systemRoots = roots +} diff --git a/libgo/go/crypto/x509/root_windows.go b/libgo/go/crypto/x509/root_windows.go new file mode 100644 index 00000000000..7e8f2af4b0e --- /dev/null +++ b/libgo/go/crypto/x509/root_windows.go @@ -0,0 +1,226 @@ +// Copyright 2012 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 + +import ( + "errors" + "syscall" + "unsafe" +) + +// 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. +// +// A pointer to the in-memory store is available in the returned CertContext's Store field. +// The store is automatically freed when the CertContext is freed using +// syscall.CertFreeCertificateContext. +func createStoreContext(leaf *Certificate, opts *VerifyOptions) (*syscall.CertContext, error) { + var storeCtx *syscall.CertContext + + leafCtx, err := syscall.CertCreateCertificateContext(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING, &leaf.Raw[0], uint32(len(leaf.Raw))) + if err != nil { + return nil, err + } + defer syscall.CertFreeCertificateContext(leafCtx) + + handle, err := syscall.CertOpenStore(syscall.CERT_STORE_PROV_MEMORY, 0, 0, syscall.CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, 0) + if err != nil { + return nil, err + } + defer syscall.CertCloseStore(handle, 0) + + err = syscall.CertAddCertificateContextToStore(handle, leafCtx, syscall.CERT_STORE_ADD_ALWAYS, &storeCtx) + if err != nil { + return nil, err + } + + if opts.Intermediates != nil { + for _, intermediate := range opts.Intermediates.certs { + ctx, err := syscall.CertCreateCertificateContext(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING, &intermediate.Raw[0], uint32(len(intermediate.Raw))) + if err != nil { + return nil, err + } + + err = syscall.CertAddCertificateContextToStore(handle, ctx, syscall.CERT_STORE_ADD_ALWAYS, nil) + syscall.CertFreeCertificateContext(ctx) + if err != nil { + return nil, err + } + } + } + + return storeCtx, nil +} + +// extractSimpleChain extracts the final certificate chain from a CertSimpleChain. +func extractSimpleChain(simpleChain **syscall.CertSimpleChain, count int) (chain []*Certificate, err error) { + if simpleChain == nil || count == 0 { + return nil, errors.New("x509: invalid simple chain") + } + + simpleChains := (*[1 << 20]*syscall.CertSimpleChain)(unsafe.Pointer(simpleChain))[:] + lastChain := simpleChains[count-1] + elements := (*[1 << 20]*syscall.CertChainElement)(unsafe.Pointer(lastChain.Elements))[:] + for i := 0; i < int(lastChain.NumElements); i++ { + // Copy the buf, since ParseCertificate does not create its own copy. + cert := elements[i].CertContext + encodedCert := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:] + buf := make([]byte, cert.Length) + copy(buf, encodedCert[:]) + parsedCert, err := ParseCertificate(buf) + if err != nil { + return nil, err + } + chain = append(chain, parsedCert) + } + + return chain, nil +} + +// checkChainTrustStatus checks the trust status of the certificate chain, translating +// any errors it finds into Go errors in the process. +func checkChainTrustStatus(c *Certificate, chainCtx *syscall.CertChainContext) error { + if chainCtx.TrustStatus.ErrorStatus != syscall.CERT_TRUST_NO_ERROR { + status := chainCtx.TrustStatus.ErrorStatus + switch status { + case syscall.CERT_TRUST_IS_NOT_TIME_VALID: + return CertificateInvalidError{c, Expired} + default: + return UnknownAuthorityError{c} + } + } + return nil +} + +// checkChainSSLServerPolicy checks that the certificate chain in chainCtx is valid for +// use as a certificate chain for a SSL/TLS server. +func checkChainSSLServerPolicy(c *Certificate, chainCtx *syscall.CertChainContext, opts *VerifyOptions) error { + sslPara := &syscall.SSLExtraCertChainPolicyPara{ + AuthType: syscall.AUTHTYPE_SERVER, + ServerName: syscall.StringToUTF16Ptr(opts.DNSName), + } + sslPara.Size = uint32(unsafe.Sizeof(*sslPara)) + + para := &syscall.CertChainPolicyPara{ + ExtraPolicyPara: uintptr(unsafe.Pointer(sslPara)), + } + para.Size = uint32(unsafe.Sizeof(*para)) + + status := syscall.CertChainPolicyStatus{} + err := syscall.CertVerifyCertificateChainPolicy(syscall.CERT_CHAIN_POLICY_SSL, chainCtx, para, &status) + if err != nil { + return err + } + + // TODO(mkrautz): use the lChainIndex and lElementIndex fields + // of the CertChainPolicyStatus to provide proper context, instead + // using c. + if status.Error != 0 { + switch status.Error { + case syscall.CERT_E_EXPIRED: + return CertificateInvalidError{c, Expired} + case syscall.CERT_E_CN_NO_MATCH: + return HostnameError{c, opts.DNSName} + case syscall.CERT_E_UNTRUSTEDROOT: + return UnknownAuthorityError{c} + default: + return UnknownAuthorityError{c} + } + } + + return nil +} + +// systemVerify is like Verify, except that it uses CryptoAPI calls +// to build certificate chains and verify them. +func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { + hasDNSName := opts != nil && len(opts.DNSName) > 0 + + storeCtx, err := createStoreContext(c, opts) + if err != nil { + return nil, err + } + defer syscall.CertFreeCertificateContext(storeCtx) + + para := new(syscall.CertChainPara) + para.Size = uint32(unsafe.Sizeof(*para)) + + // If there's a DNSName set in opts, assume we're verifying + // a certificate from a TLS server. + if hasDNSName { + oids := []*byte{ + &syscall.OID_PKIX_KP_SERVER_AUTH[0], + // Both IE and Chrome allow certificates with + // Server Gated Crypto as well. Some certificates + // in the wild require them. + &syscall.OID_SERVER_GATED_CRYPTO[0], + &syscall.OID_SGC_NETSCAPE[0], + } + para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_OR + para.RequestedUsage.Usage.Length = uint32(len(oids)) + para.RequestedUsage.Usage.UsageIdentifiers = &oids[0] + } else { + para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_AND + para.RequestedUsage.Usage.Length = 0 + para.RequestedUsage.Usage.UsageIdentifiers = nil + } + + var verifyTime *syscall.Filetime + if opts != nil && !opts.CurrentTime.IsZero() { + ft := syscall.NsecToFiletime(opts.CurrentTime.UnixNano()) + verifyTime = &ft + } + + // CertGetCertificateChain will traverse Windows's root stores + // in an attempt to build a verified certificate chain. Once + // it has found a verified chain, it stops. MSDN docs on + // CERT_CHAIN_CONTEXT: + // + // When a CERT_CHAIN_CONTEXT is built, the first simple chain + // begins with an end certificate and ends with a self-signed + // certificate. If that self-signed certificate is not a root + // or otherwise trusted certificate, an attempt is made to + // build a new chain. CTLs are used to create the new chain + // beginning with the self-signed certificate from the original + // chain as the end certificate of the new chain. This process + // continues building additional simple chains until the first + // self-signed certificate is a trusted certificate or until + // an additional simple chain cannot be built. + // + // The result is that we'll only get a single trusted chain to + // return to our caller. + var chainCtx *syscall.CertChainContext + err = syscall.CertGetCertificateChain(syscall.Handle(0), storeCtx, verifyTime, storeCtx.Store, para, 0, 0, &chainCtx) + if err != nil { + return nil, err + } + defer syscall.CertFreeCertificateChain(chainCtx) + + err = checkChainTrustStatus(c, chainCtx) + if err != nil { + return nil, err + } + + if hasDNSName { + err = checkChainSSLServerPolicy(c, chainCtx, opts) + if err != nil { + return nil, err + } + } + + chain, err := extractSimpleChain(chainCtx.Chains, int(chainCtx.ChainCount)) + if err != nil { + return nil, err + } + + chains = append(chains, chain) + + return chains, nil +} + +func initSystemRoots() { + systemRoots = NewCertPool() +} diff --git a/libgo/go/crypto/x509/verify.go b/libgo/go/crypto/x509/verify.go index 3859dd8d488..307c5ef0339 100644 --- a/libgo/go/crypto/x509/verify.go +++ b/libgo/go/crypto/x509/verify.go @@ -5,6 +5,7 @@ package x509 import ( + "runtime" "strings" "time" "unicode/utf8" @@ -23,6 +24,9 @@ const ( // certificate has a name constraint which doesn't include the name // being checked. CANotAuthorizedForThisName + // TooManyIntermediates results when a path length constraint is + // violated. + TooManyIntermediates ) // CertificateInvalidError results when an odd error occurs. Users of this @@ -40,6 +44,8 @@ func (e CertificateInvalidError) Error() string { return "x509: certificate has expired or is not yet valid" case CANotAuthorizedForThisName: return "x509: a root or intermediate certificate is not authorized to sign in this domain" + case TooManyIntermediates: + return "x509: too many intermediates for path length constraint" } return "x509: unknown error" } @@ -76,7 +82,7 @@ func (e UnknownAuthorityError) Error() string { type VerifyOptions struct { DNSName string Intermediates *CertPool - Roots *CertPool + Roots *CertPool // if nil, the system roots are used CurrentTime time.Time // if zero, the current time is used } @@ -87,7 +93,7 @@ const ( ) // isValid performs validity checks on the c. -func (c *Certificate) isValid(certType int, opts *VerifyOptions) error { +func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error { now := opts.CurrentTime if now.IsZero() { now = time.Now() @@ -130,26 +136,44 @@ func (c *Certificate) isValid(certType int, opts *VerifyOptions) error { return CertificateInvalidError{c, NotAuthorizedToSign} } + if c.BasicConstraintsValid && c.MaxPathLen >= 0 { + numIntermediates := len(currentChain) - 1 + if numIntermediates > c.MaxPathLen { + return CertificateInvalidError{c, TooManyIntermediates} + } + } + return nil } // Verify attempts to verify c by building one or more chains from c to a -// certificate in opts.roots, using certificates in opts.Intermediates if +// certificate in opts.Roots, using certificates in opts.Intermediates if // needed. If successful, it returns one or more chains where the first // element of the chain is c and the last element is from opts.Roots. // // WARNING: this doesn't do any revocation checking. func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) { - err = c.isValid(leafCertificate, &opts) + // Use Windows's own verification and chain building. + if opts.Roots == nil && runtime.GOOS == "windows" { + return c.systemVerify(&opts) + } + + if opts.Roots == nil { + opts.Roots = systemRootsPool() + } + + err = c.isValid(leafCertificate, nil, &opts) if err != nil { return } + if len(opts.DNSName) > 0 { err = c.VerifyHostname(opts.DNSName) if err != nil { return } } + return c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts) } @@ -163,7 +187,7 @@ func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate func (c *Certificate) buildChains(cache map[int][][]*Certificate, currentChain []*Certificate, opts *VerifyOptions) (chains [][]*Certificate, err error) { for _, rootNum := range opts.Roots.findVerifiedParents(c) { root := opts.Roots.certs[rootNum] - err = root.isValid(rootCertificate, opts) + err = root.isValid(rootCertificate, currentChain, opts) if err != nil { continue } @@ -178,7 +202,7 @@ nextIntermediate: continue nextIntermediate } } - err = intermediate.isValid(intermediateCertificate, opts) + err = intermediate.isValid(intermediateCertificate, currentChain, opts) if err != nil { continue } diff --git a/libgo/go/crypto/x509/verify_test.go b/libgo/go/crypto/x509/verify_test.go index 2cdd66a5589..7b171b291a4 100644 --- a/libgo/go/crypto/x509/verify_test.go +++ b/libgo/go/crypto/x509/verify_test.go @@ -8,6 +8,7 @@ import ( "crypto/x509/pkix" "encoding/pem" "errors" + "runtime" "strings" "testing" "time" @@ -19,7 +20,7 @@ type verifyTest struct { roots []string currentTime int64 dnsName string - nilRoots bool + systemSkip bool errorCallback func(*testing.T, int, error) bool expectedChains [][]string @@ -60,14 +61,6 @@ var verifyTests = []verifyTest{ { leaf: googleLeaf, intermediates: []string{thawteIntermediate}, - nilRoots: true, // verifies that we don't crash - currentTime: 1302726541, - dnsName: "www.google.com", - errorCallback: expectAuthorityUnknown, - }, - { - leaf: googleLeaf, - intermediates: []string{thawteIntermediate}, roots: []string{verisignRoot}, currentTime: 1, dnsName: "www.example.com", @@ -80,6 +73,9 @@ var verifyTests = []verifyTest{ currentTime: 1302726541, dnsName: "www.google.com", + // Skip when using systemVerify, since Windows + // *will* find the missing intermediate cert. + systemSkip: true, errorCallback: expectAuthorityUnknown, }, { @@ -109,6 +105,9 @@ var verifyTests = []verifyTest{ roots: []string{startComRoot}, currentTime: 1302726541, + // Skip when using systemVerify, since Windows + // can only return a single chain to us (for now). + systemSkip: true, expectedChains: [][]string{ {"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority"}, {"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority", "StartCom Certification Authority"}, @@ -148,23 +147,26 @@ func certificateFromPEM(pemBytes string) (*Certificate, error) { return ParseCertificate(block.Bytes) } -func TestVerify(t *testing.T) { +func testVerify(t *testing.T, useSystemRoots bool) { for i, test := range verifyTests { + if useSystemRoots && test.systemSkip { + continue + } + opts := VerifyOptions{ - Roots: NewCertPool(), Intermediates: NewCertPool(), DNSName: test.dnsName, CurrentTime: time.Unix(test.currentTime, 0), } - if test.nilRoots { - opts.Roots = nil - } - for j, root := range test.roots { - ok := opts.Roots.AppendCertsFromPEM([]byte(root)) - if !ok { - t.Errorf("#%d: failed to parse root #%d", i, j) - return + if !useSystemRoots { + opts.Roots = NewCertPool() + for j, root := range test.roots { + ok := opts.Roots.AppendCertsFromPEM([]byte(root)) + if !ok { + t.Errorf("#%d: failed to parse root #%d", i, j) + return + } } } @@ -225,6 +227,19 @@ func TestVerify(t *testing.T) { } } +func TestGoVerify(t *testing.T) { + testVerify(t, false) +} + +func TestSystemVerify(t *testing.T) { + if runtime.GOOS != "windows" { + t.Logf("skipping verify test using system APIs on %q", runtime.GOOS) + return + } + + testVerify(t, true) +} + func chainToDebugString(chain []*Certificate) string { var chainStr string for _, cert := range chain { diff --git a/libgo/go/crypto/x509/x509.go b/libgo/go/crypto/x509/x509.go index f5da86b54a0..8dae7e7fcf9 100644 --- a/libgo/go/crypto/x509/x509.go +++ b/libgo/go/crypto/x509/x509.go @@ -429,7 +429,7 @@ func (h UnhandledCriticalExtension) Error() string { type basicConstraints struct { IsCA bool `asn1:"optional"` - MaxPathLen int `asn1:"optional"` + MaxPathLen int `asn1:"optional,default:-1"` } // RFC 5280 4.2.1.4 |