267 lines
8.4 KiB
Go
267 lines
8.4 KiB
Go
|
// 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, nil, nil}
|
||
|
}
|
||
|
}
|
||
|
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 {
|
||
|
servernamep, err := syscall.UTF16PtrFromString(opts.DNSName)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
sslPara := &syscall.SSLExtraCertChainPolicyPara{
|
||
|
AuthType: syscall.AUTHTYPE_SERVER,
|
||
|
ServerName: servernamep,
|
||
|
}
|
||
|
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, nil, nil}
|
||
|
default:
|
||
|
return UnknownAuthorityError{c, nil, nil}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 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.
|
||
|
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))[:]
|
||
|
buf2 := make([]byte, cert.Length)
|
||
|
copy(buf2, buf)
|
||
|
if c, err := ParseCertificate(buf2); err == nil {
|
||
|
roots.AddCert(c)
|
||
|
}
|
||
|
}
|
||
|
return roots, nil
|
||
|
}
|