From 3133b14b307103b79117a033ddf1ac9d0f7a24d0 Mon Sep 17 00:00:00 2001 From: Mikkel Krautz Date: Thu, 8 Mar 2012 11:28:04 -0500 Subject: [PATCH] crypto/x509: allow server gated crypto in windows systemVerify Also factors out some code into functions to make systemVerify easier to read. R=rsc, agl CC=golang-dev https://golang.org/cl/5781054 --- src/pkg/crypto/x509/root_windows.go | 165 +++++++++++++++++----------- src/pkg/syscall/ztypes_windows.go | 4 +- 2 files changed, 104 insertions(+), 65 deletions(-) diff --git a/src/pkg/crypto/x509/root_windows.go b/src/pkg/crypto/x509/root_windows.go index 627d0592a3..8f7980ae4a 100644 --- a/src/pkg/crypto/x509/root_windows.go +++ b/src/pkg/crypto/x509/root_windows.go @@ -5,6 +5,7 @@ package x509 import ( + "errors" "syscall" "unsafe" ) @@ -58,6 +59,87 @@ func createStoreContext(leaf *Certificate, opts *VerifyOptions) (*syscall.CertCo 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 @@ -69,15 +151,23 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate para := new(syscall.CertChainPara) para.Size = uint32(unsafe.Sizeof(*para)) - para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_AND // 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]} + 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 } @@ -113,77 +203,24 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate } defer syscall.CertFreeCertificateChain(chainCtx) - if chainCtx.TrustStatus.ErrorStatus != syscall.CERT_TRUST_NO_ERROR { - status := chainCtx.TrustStatus.ErrorStatus - switch status { - case syscall.CERT_TRUST_IS_NOT_TIME_VALID: - return nil, CertificateInvalidError{c, Expired} - default: - return nil, UnknownAuthorityError{c} - } - } - - simpleChains := (*[1 << 20]*syscall.CertSimpleChain)(unsafe.Pointer(chainCtx.Chains))[:] - if chainCtx.ChainCount == 0 { - return nil, UnknownAuthorityError{c} - } - verifiedChain := simpleChains[int(chainCtx.ChainCount)-1] - - elements := (*[1 << 20]*syscall.CertChainElement)(unsafe.Pointer(verifiedChain.Elements))[:] - if verifiedChain.NumElements == 0 { - return nil, UnknownAuthorityError{c} - } - - var chain []*Certificate - for i := 0; i < int(verifiedChain.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) + err = checkChainTrustStatus(c, chainCtx) + if err != nil { + return nil, err } - // Apply the system SSL policy if VerifyOptions dictates that we - // must check for a DNS name. if hasDNSName { - 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) + err = checkChainSSLServerPolicy(c, chainCtx, opts) if err != nil { return nil, err } + } - if status.Error != 0 { - switch status.Error { - case syscall.CERT_E_EXPIRED: - return nil, CertificateInvalidError{c, Expired} - case syscall.CERT_E_CN_NO_MATCH: - return nil, HostnameError{c, opts.DNSName} - case syscall.CERT_E_UNTRUSTEDROOT: - return nil, UnknownAuthorityError{c} - default: - return nil, UnknownAuthorityError{c} - } - } + chain, err := extractSimpleChain(chainCtx.Chains, int(chainCtx.ChainCount)) + if err != nil { + return nil, err } - chains = make([][]*Certificate, 1) - chains[0] = chain + chains = append(chains, chain) return chains, nil } diff --git a/src/pkg/syscall/ztypes_windows.go b/src/pkg/syscall/ztypes_windows.go index 0b65293edb..9894ce3246 100644 --- a/src/pkg/syscall/ztypes_windows.go +++ b/src/pkg/syscall/ztypes_windows.go @@ -262,7 +262,9 @@ const ( ) var ( - OID_PKIX_KP_SERVER_AUTH = []byte("1.3.6.1.5.5.7.3.1" + string([]byte{0})) + OID_PKIX_KP_SERVER_AUTH = []byte("1.3.6.1.5.5.7.3.1\x00") + OID_SERVER_GATED_CRYPTO = []byte("1.3.6.1.4.1.311.10.3.3\x00") + OID_SGC_NETSCAPE = []byte("2.16.840.1.113730.4.1\x00") ) // Invented values to support what package os expects. -- 2.50.0