]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/crypto/x509/verify.go
crypto/x509: implement AddCertWithConstraint
[gostls13.git] / src / crypto / x509 / verify.go
index f815c3479951b748570936167a6ddebd43d927fd..9d3c3246d3098dc474ed1f0b49db3ee316fe9b6c 100644 (file)
@@ -6,14 +6,14 @@ package x509
 
 import (
        "bytes"
-       "encoding/asn1"
+       "crypto"
+       "crypto/x509/pkix"
        "errors"
        "fmt"
        "net"
        "net/url"
        "reflect"
        "runtime"
-       "strconv"
        "strings"
        "time"
        "unicode/utf8"
@@ -41,9 +41,7 @@ const (
        // NameMismatch results when the subject name of a parent certificate
        // does not match the issuer name in the child.
        NameMismatch
-       // NameConstraintsWithoutSANs results when a leaf certificate doesn't
-       // contain a Subject Alternative Name extension, but a CA certificate
-       // contains name constraints.
+       // NameConstraintsWithoutSANs is a legacy error and is no longer returned.
        NameConstraintsWithoutSANs
        // UnconstrainedName results when a CA certificate contains permitted
        // name constraints, but leaf certificate contains a name of an
@@ -56,8 +54,7 @@ const (
        // CPU time to verify.
        TooManyConstraints
        // CANotAuthorizedForExtKeyUsage results when an intermediate or root
-       // certificate does not permit an extended key usage that is claimed by
-       // the leaf certificate.
+       // certificate does not permit a requested extended key usage.
        CANotAuthorizedForExtKeyUsage
 )
 
@@ -74,7 +71,7 @@ func (e CertificateInvalidError) Error() string {
        case NotAuthorizedToSign:
                return "x509: certificate is not authorized to sign other certificates"
        case Expired:
-               return "x509: certificate has expired or is not yet valid"
+               return "x509: certificate has expired or is not yet valid: " + e.Detail
        case CANotAuthorizedForThisName:
                return "x509: a root or intermediate certificate is not authorized to sign for this name: " + e.Detail
        case CANotAuthorizedForExtKeyUsage:
@@ -82,7 +79,7 @@ func (e CertificateInvalidError) Error() string {
        case TooManyIntermediates:
                return "x509: too many intermediates for path length constraint"
        case IncompatibleUsage:
-               return "x509: certificate specifies an incompatible key usage: " + e.Detail
+               return "x509: certificate specifies an incompatible key usage"
        case NameMismatch:
                return "x509: issuer name does not match subject from issuing certificate"
        case NameConstraintsWithoutSANs:
@@ -103,6 +100,10 @@ type HostnameError struct {
 func (h HostnameError) Error() string {
        c := h.Certificate
 
+       if !c.hasSANExtension() && matchHostnames(c.Subject.CommonName, h.Host) {
+               return "x509: certificate relies on legacy Common Name field, use SANs instead"
+       }
+
        var valid string
        if ip := net.ParseIP(h.Host); ip != nil {
                // Trying to validate an IP
@@ -116,11 +117,7 @@ func (h HostnameError) Error() string {
                        valid += san.String()
                }
        } else {
-               if c.hasSANExtension() {
-                       valid = strings.Join(c.DNSNames, ", ")
-               } else {
-                       valid = c.Subject.CommonName
-               }
+               valid = strings.Join(c.DNSNames, ", ")
        }
 
        if len(valid) == 0 {
@@ -169,31 +166,40 @@ func (se SystemRootsError) Error() string {
        return msg
 }
 
+func (se SystemRootsError) Unwrap() error { return se.Err }
+
 // errNotParsed is returned when a certificate without ASN.1 contents is
 // verified. Platform-specific verification needs the ASN.1 contents.
 var errNotParsed = errors.New("x509: missing ASN.1 contents; use ParseCertificate")
 
-// VerifyOptions contains parameters for Certificate.Verify. It's a structure
-// because other PKIX verification APIs have ended up needing many options.
+// VerifyOptions contains parameters for Certificate.Verify.
 type VerifyOptions struct {
-       DNSName       string
+       // DNSName, if set, is checked against the leaf certificate with
+       // Certificate.VerifyHostname or the platform verifier.
+       DNSName string
+
+       // Intermediates is an optional pool of certificates that are not trust
+       // anchors, but can be used to form a chain from the leaf certificate to a
+       // root certificate.
        Intermediates *CertPool
-       Roots         *CertPool // if nil, the system roots are used
-       CurrentTime   time.Time // if zero, the current time is used
-       // KeyUsage specifies which Extended Key Usage values are acceptable. A leaf
-       // certificate is accepted if it contains any of the listed values. An empty
-       // list means ExtKeyUsageServerAuth. To accept any key usage, include
-       // ExtKeyUsageAny.
-       //
-       // Certificate chains are required to nest extended key usage values,
-       // irrespective of this value. This matches the Windows CryptoAPI behavior,
-       // but not the spec.
+       // Roots is the set of trusted root certificates the leaf certificate needs
+       // to chain up to. If nil, the system roots or the platform verifier are used.
+       Roots *CertPool
+
+       // CurrentTime is used to check the validity of all certificates in the
+       // chain. If zero, the current time is used.
+       CurrentTime time.Time
+
+       // KeyUsages specifies which Extended Key Usage values are acceptable. A
+       // chain is accepted if it allows any of the listed values. An empty list
+       // means ExtKeyUsageServerAuth. To accept any key usage, include ExtKeyUsageAny.
        KeyUsages []ExtKeyUsage
+
        // MaxConstraintComparisions is the maximum number of comparisons to
        // perform when checking a given certificate's name constraints. If
        // zero, a sensible default is used. This limit prevents pathological
        // certificates from consuming excessive amounts of CPU time when
-       // validating.
+       // validating. It does not apply to the platform verifier.
        MaxConstraintComparisions int
 }
 
@@ -211,10 +217,9 @@ type rfc2821Mailbox struct {
 }
 
 // parseRFC2821Mailbox parses an email address into local and domain parts,
-// based on the ABNF for a “Mailbox” from RFC 2821. According to
-// https://tools.ietf.org/html/rfc5280#section-4.2.1.6 that's correct for an
-// rfc822Name from a certificate: “The format of an rfc822Name is a "Mailbox"
-// as defined in https://tools.ietf.org/html/rfc2821#section-4.1.2”.
+// based on the ABNF for a “Mailbox” from RFC 2821. According to RFC 5280,
+// Section 4.2.1.6 that's correct for an rfc822Name from a certificate: “The
+// format of an rfc822Name is a "Mailbox" as defined in RFC 2821, Section 4.1.2”.
 func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
        if len(in) == 0 {
                return mailbox, false
@@ -231,9 +236,8 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
                // quoted-pair = ("\" text) / obs-qp
                // text = %d1-9 / %d11 / %d12 / %d14-127 / obs-text
                //
-               // (Names beginning with “obs-” are the obsolete syntax from
-               // https://tools.ietf.org/html/rfc2822#section-4. Since it has
-               // been 16 years, we no longer accept that.)
+               // (Names beginning with “obs-” are the obsolete syntax from RFC 2822,
+               // Section 4. Since it has been 16 years, we no longer accept that.)
                in = in[1:]
        QuotedString:
                for {
@@ -287,7 +291,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
                // Atom ("." Atom)*
        NextChar:
                for len(in) > 0 {
-                       // atext from https://tools.ietf.org/html/rfc2822#section-3.2.4
+                       // atext from RFC 2822, Section 3.2.4
                        c := in[0]
 
                        switch {
@@ -323,7 +327,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
                        return mailbox, false
                }
 
-               // https://tools.ietf.org/html/rfc3696#section-3
+               // From RFC 3696, Section 3:
                // “period (".") may also appear, but may not be used to start
                // or end the local part, nor may two or more consecutive
                // periods appear.”
@@ -360,7 +364,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
                        reverseLabels = append(reverseLabels, domain)
                        domain = ""
                } else {
-                       reverseLabels = append(reverseLabels, domain[i+1:len(domain)])
+                       reverseLabels = append(reverseLabels, domain[i+1:])
                        domain = domain[:i]
                }
        }
@@ -404,7 +408,7 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, erro
 }
 
 func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
-       // https://tools.ietf.org/html/rfc5280#section-4.2.1.10
+       // From RFC 5280, Section 4.2.1.10:
        // “a uniformResourceIdentifier that does not include an authority
        // component with a host name specified as a fully qualified domain
        // name (e.g., if the URI either does not include an authority
@@ -498,9 +502,9 @@ func (c *Certificate) checkNameConstraints(count *int,
        maxConstraintComparisons int,
        nameType string,
        name string,
-       parsedName interface{},
-       match func(parsedName, constraint interface{}) (match bool, err error),
-       permitted, excluded interface{}) error {
+       parsedName any,
+       match func(parsedName, constraint any) (match bool, err error),
+       permitted, excluded any) error {
 
        excludedValue := reflect.ValueOf(excluded)
 
@@ -549,51 +553,6 @@ func (c *Certificate) checkNameConstraints(count *int,
        return nil
 }
 
-const (
-       checkingAgainstIssuerCert = iota
-       checkingAgainstLeafCert
-)
-
-// ekuPermittedBy returns true iff the given extended key usage is permitted by
-// the given EKU from a certificate. Normally, this would be a simple
-// comparison plus a special case for the “any” EKU. But, in order to support
-// existing certificates, some exceptions are made.
-func ekuPermittedBy(eku, certEKU ExtKeyUsage, context int) bool {
-       if certEKU == ExtKeyUsageAny || eku == certEKU {
-               return true
-       }
-
-       // Some exceptions are made to support existing certificates. Firstly,
-       // the ServerAuth and SGC EKUs are treated as a group.
-       mapServerAuthEKUs := func(eku ExtKeyUsage) ExtKeyUsage {
-               if eku == ExtKeyUsageNetscapeServerGatedCrypto || eku == ExtKeyUsageMicrosoftServerGatedCrypto {
-                       return ExtKeyUsageServerAuth
-               }
-               return eku
-       }
-
-       eku = mapServerAuthEKUs(eku)
-       certEKU = mapServerAuthEKUs(certEKU)
-
-       if eku == certEKU {
-               return true
-       }
-
-       // If checking a requested EKU against the list in a leaf certificate there
-       // are fewer exceptions.
-       if context == checkingAgainstLeafCert {
-               return false
-       }
-
-       // ServerAuth in a CA permits ClientAuth in the leaf.
-       return (eku == ExtKeyUsageClientAuth && certEKU == ExtKeyUsageServerAuth) ||
-               // Any CA may issue an OCSP responder certificate.
-               eku == ExtKeyUsageOCSPSigning ||
-               // Code-signing CAs can use Microsoft's commercial and
-               // kernel-mode EKUs.
-               (eku == ExtKeyUsageMicrosoftCommercialCodeSigning || eku == ExtKeyUsageMicrosoftKernelCodeSigning) && certEKU == ExtKeyUsageCodeSigning
-}
-
 // isValid performs validity checks on c given that it is a candidate to append
 // to the chain in currentChain.
 func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error {
@@ -612,8 +571,18 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
        if now.IsZero() {
                now = time.Now()
        }
-       if now.Before(c.NotBefore) || now.After(c.NotAfter) {
-               return CertificateInvalidError{c, Expired, ""}
+       if now.Before(c.NotBefore) {
+               return CertificateInvalidError{
+                       Cert:   c,
+                       Reason: Expired,
+                       Detail: fmt.Sprintf("current time %s is before %s", now.Format(time.RFC3339), c.NotBefore.Format(time.RFC3339)),
+               }
+       } else if now.After(c.NotAfter) {
+               return CertificateInvalidError{
+                       Cert:   c,
+                       Reason: Expired,
+                       Detail: fmt.Sprintf("current time %s is after %s", now.Format(time.RFC3339), c.NotAfter.Format(time.RFC3339)),
+               }
        }
 
        maxConstraintComparisons := opts.MaxConstraintComparisions
@@ -622,142 +591,87 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
        }
        comparisonCount := 0
 
-       var leaf *Certificate
        if certType == intermediateCertificate || certType == rootCertificate {
                if len(currentChain) == 0 {
                        return errors.New("x509: internal error: empty chain when appending CA cert")
                }
-               leaf = currentChain[0]
        }
 
-       if (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() {
-               sanExtension, ok := leaf.getSANExtension()
-               if !ok {
-                       // This is the deprecated, legacy case of depending on
-                       // the CN as a hostname. Chains modern enough to be
-                       // using name constraints should not be depending on
-                       // CNs.
-                       return CertificateInvalidError{c, NameConstraintsWithoutSANs, ""}
-               }
-
-               err := forEachSAN(sanExtension, func(tag int, data []byte) error {
-                       switch tag {
-                       case nameTypeEmail:
-                               name := string(data)
-                               mailbox, ok := parseRFC2821Mailbox(name)
-                               if !ok {
-                                       return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox)
-                               }
-
-                               if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox,
-                                       func(parsedName, constraint interface{}) (bool, error) {
-                                               return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
-                                       }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil {
-                                       return err
-                               }
-
-                       case nameTypeDNS:
-                               name := string(data)
-                               if _, ok := domainToReverseLabels(name); !ok {
-                                       return fmt.Errorf("x509: cannot parse dnsName %q", name)
-                               }
-
-                               if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name,
-                                       func(parsedName, constraint interface{}) (bool, error) {
-                                               return matchDomainConstraint(parsedName.(string), constraint.(string))
-                                       }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil {
-                                       return err
-                               }
-
-                       case nameTypeURI:
-                               name := string(data)
-                               uri, err := url.Parse(name)
-                               if err != nil {
-                                       return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name)
-                               }
-
-                               if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri,
-                                       func(parsedName, constraint interface{}) (bool, error) {
-                                               return matchURIConstraint(parsedName.(*url.URL), constraint.(string))
-                                       }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil {
-                                       return err
-                               }
-
-                       case nameTypeIP:
-                               ip := net.IP(data)
-                               if l := len(ip); l != net.IPv4len && l != net.IPv6len {
-                                       return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data)
-                               }
-
-                               if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip,
-                                       func(parsedName, constraint interface{}) (bool, error) {
-                                               return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet))
-                                       }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil {
-                                       return err
-                               }
-
-                       default:
-                               // Unknown SAN types are ignored.
+       if (certType == intermediateCertificate || certType == rootCertificate) &&
+               c.hasNameConstraints() {
+               toCheck := []*Certificate{}
+               for _, c := range currentChain {
+                       if c.hasSANExtension() {
+                               toCheck = append(toCheck, c)
                        }
-
-                       return nil
-               })
-
-               if err != nil {
-                       return err
                }
-       }
-
-       checkEKUs := certType == intermediateCertificate
-
-       // If no extended key usages are specified, then all are acceptable.
-       if checkEKUs && (len(c.ExtKeyUsage) == 0 && len(c.UnknownExtKeyUsage) == 0) {
-               checkEKUs = false
-       }
-
-       // If the “any” key usage is permitted, then no more checks are needed.
-       if checkEKUs {
-               for _, caEKU := range c.ExtKeyUsage {
-                       comparisonCount++
-                       if caEKU == ExtKeyUsageAny {
-                               checkEKUs = false
-                               break
-                       }
-               }
-       }
-
-       if checkEKUs {
-       NextEKU:
-               for _, eku := range leaf.ExtKeyUsage {
-                       if comparisonCount > maxConstraintComparisons {
-                               return CertificateInvalidError{c, TooManyConstraints, ""}
-                       }
-
-                       for _, caEKU := range c.ExtKeyUsage {
-                               comparisonCount++
-                               if ekuPermittedBy(eku, caEKU, checkingAgainstIssuerCert) {
-                                       continue NextEKU
+               for _, sanCert := range toCheck {
+                       err := forEachSAN(sanCert.getSANExtension(), func(tag int, data []byte) error {
+                               switch tag {
+                               case nameTypeEmail:
+                                       name := string(data)
+                                       mailbox, ok := parseRFC2821Mailbox(name)
+                                       if !ok {
+                                               return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox)
+                                       }
+
+                                       if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox,
+                                               func(parsedName, constraint any) (bool, error) {
+                                                       return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
+                                               }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil {
+                                               return err
+                                       }
+
+                               case nameTypeDNS:
+                                       name := string(data)
+                                       if _, ok := domainToReverseLabels(name); !ok {
+                                               return fmt.Errorf("x509: cannot parse dnsName %q", name)
+                                       }
+
+                                       if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name,
+                                               func(parsedName, constraint any) (bool, error) {
+                                                       return matchDomainConstraint(parsedName.(string), constraint.(string))
+                                               }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil {
+                                               return err
+                                       }
+
+                               case nameTypeURI:
+                                       name := string(data)
+                                       uri, err := url.Parse(name)
+                                       if err != nil {
+                                               return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name)
+                                       }
+
+                                       if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri,
+                                               func(parsedName, constraint any) (bool, error) {
+                                                       return matchURIConstraint(parsedName.(*url.URL), constraint.(string))
+                                               }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil {
+                                               return err
+                                       }
+
+                               case nameTypeIP:
+                                       ip := net.IP(data)
+                                       if l := len(ip); l != net.IPv4len && l != net.IPv6len {
+                                               return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data)
+                                       }
+
+                                       if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip,
+                                               func(parsedName, constraint any) (bool, error) {
+                                                       return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet))
+                                               }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil {
+                                               return err
+                                       }
+
+                               default:
+                                       // Unknown SAN types are ignored.
                                }
-                       }
 
-                       oid, _ := oidFromExtKeyUsage(eku)
-                       return CertificateInvalidError{c, CANotAuthorizedForExtKeyUsage, fmt.Sprintf("EKU not permitted: %#v", oid)}
-               }
-
-       NextUnknownEKU:
-               for _, eku := range leaf.UnknownExtKeyUsage {
-                       if comparisonCount > maxConstraintComparisons {
-                               return CertificateInvalidError{c, TooManyConstraints, ""}
-                       }
+                               return nil
+                       })
 
-                       for _, caEKU := range c.UnknownExtKeyUsage {
-                               comparisonCount++
-                               if caEKU.Equal(eku) {
-                                       continue NextUnknownEKU
-                               }
+                       if err != nil {
+                               return err
                        }
-
-                       return CertificateInvalidError{c, CANotAuthorizedForExtKeyUsage, fmt.Sprintf("EKU not permitted: %#v", eku)}
                }
        }
 
@@ -789,19 +703,14 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
                }
        }
 
-       return nil
-}
-
-// formatOID formats an ASN.1 OBJECT IDENTIFER in the common, dotted style.
-func formatOID(oid asn1.ObjectIdentifier) string {
-       ret := ""
-       for i, v := range oid {
-               if i > 0 {
-                       ret += "."
-               }
-               ret += strconv.Itoa(v)
+       if !boringAllowCert(c) {
+               // IncompatibleUsage is not quite right here,
+               // but it's also the "no chains found" error
+               // and is close enough.
+               return CertificateInvalidError{c, IncompatibleUsage, ""}
        }
-       return ret
+
+       return nil
 }
 
 // Verify attempts to verify c by building one or more chains from c to a
@@ -809,8 +718,9 @@ func formatOID(oid asn1.ObjectIdentifier) string {
 // 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.
 //
-// If opts.Roots is nil and system roots are unavailable the returned error
-// will be of type SystemRootsError.
+// If opts.Roots is nil, the platform verifier might be used, and
+// verification details might differ from what is described below. If system
+// roots are unavailable the returned error will be of type SystemRootsError.
 //
 // Name constraints in the intermediates will be applied to all names claimed
 // in the chain, not just opts.DNSName. Thus it is invalid for a leaf to claim
@@ -818,9 +728,21 @@ func formatOID(oid asn1.ObjectIdentifier) string {
 // the name being validated. Note that DirectoryName constraints are not
 // supported.
 //
-// Extended Key Usage values are enforced down a chain, so an intermediate or
-// root that enumerates EKUs prevents a leaf from asserting an EKU not in that
-// list.
+// Name constraint validation follows the rules from RFC 5280, with the
+// addition that DNS name constraints may use the leading period format
+// defined for emails and URIs. When a constraint has a leading period
+// it indicates that at least one additional label must be prepended to
+// the constrained name to be considered valid.
+//
+// Extended Key Usage values are enforced nested down a chain, so an intermediate
+// or root that enumerates EKUs prevents a leaf from asserting an EKU not in that
+// list. (While this is not specified, it is common practice in order to limit
+// the types of certificates a CA can issue.)
+//
+// Certificates that use SHA1WithRSA and ECDSAWithSHA1 signatures are not supported,
+// and will not be used to build chains.
+//
+// Certificates other than c in the returned chains should not be modified.
 //
 // WARNING: this function doesn't do any revocation checking.
 func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) {
@@ -829,17 +751,33 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
        if len(c.Raw) == 0 {
                return nil, errNotParsed
        }
-       if opts.Intermediates != nil {
-               for _, intermediate := range opts.Intermediates.certs {
-                       if len(intermediate.Raw) == 0 {
-                               return nil, errNotParsed
-                       }
+       for i := 0; i < opts.Intermediates.len(); i++ {
+               c, _, err := opts.Intermediates.cert(i)
+               if err != nil {
+                       return nil, fmt.Errorf("crypto/x509: error fetching intermediate: %w", err)
+               }
+               if len(c.Raw) == 0 {
+                       return nil, errNotParsed
                }
        }
 
-       // Use Windows's own verification and chain building.
-       if opts.Roots == nil && runtime.GOOS == "windows" {
-               return c.systemVerify(&opts)
+       // Use platform verifiers, where available, if Roots is from SystemCertPool.
+       if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
+               // Don't use the system verifier if the system pool was replaced with a non-system pool,
+               // i.e. if SetFallbackRoots was called with x509usefallbackroots=1.
+               systemPool := systemRootsPool()
+               if opts.Roots == nil && (systemPool == nil || systemPool.systemPool) {
+                       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 {
@@ -861,63 +799,40 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
                }
        }
 
-       requestedKeyUsages := make([]ExtKeyUsage, len(opts.KeyUsages))
-       copy(requestedKeyUsages, opts.KeyUsages)
-       if len(requestedKeyUsages) == 0 {
-               requestedKeyUsages = append(requestedKeyUsages, ExtKeyUsageServerAuth)
+       var candidateChains [][]*Certificate
+       if opts.Roots.contains(c) {
+               candidateChains = [][]*Certificate{{c}}
+       } else {
+               candidateChains, err = c.buildChains([]*Certificate{c}, nil, &opts)
+               if err != nil {
+                       return nil, err
+               }
        }
 
-       // If no key usages are specified, then any are acceptable.
-       checkEKU := len(c.ExtKeyUsage) > 0
+       if len(opts.KeyUsages) == 0 {
+               opts.KeyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth}
+       }
 
-       for _, eku := range requestedKeyUsages {
+       for _, eku := range opts.KeyUsages {
                if eku == ExtKeyUsageAny {
-                       checkEKU = false
-                       break
+                       // If any key usage is acceptable, no need to check the chain for
+                       // key usages.
+                       return candidateChains, nil
                }
        }
 
-       if checkEKU {
-               foundMatch := false
-       NextUsage:
-               for _, eku := range requestedKeyUsages {
-                       for _, leafEKU := range c.ExtKeyUsage {
-                               if ekuPermittedBy(eku, leafEKU, checkingAgainstLeafCert) {
-                                       foundMatch = true
-                                       break NextUsage
-                               }
-                       }
-               }
-
-               if !foundMatch {
-                       msg := "leaf contains the following, recognized EKUs: "
-
-                       for i, leafEKU := range c.ExtKeyUsage {
-                               oid, ok := oidFromExtKeyUsage(leafEKU)
-                               if !ok {
-                                       continue
-                               }
-
-                               if i > 0 {
-                                       msg += ", "
-                               }
-                               msg += formatOID(oid)
-                       }
-
-                       return nil, CertificateInvalidError{c, IncompatibleUsage, msg}
+       chains = make([][]*Certificate, 0, len(candidateChains))
+       for _, candidate := range candidateChains {
+               if checkChainForKeyUsage(candidate, opts.KeyUsages) {
+                       chains = append(chains, candidate)
                }
        }
 
-       var candidateChains [][]*Certificate
-       if opts.Roots.contains(c) {
-               candidateChains = append(candidateChains, []*Certificate{c})
-       } else {
-               if candidateChains, err = c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts); err != nil {
-                       return nil, err
-               }
+       if len(chains) == 0 {
+               return nil, CertificateInvalidError{c, IncompatibleUsage, ""}
        }
 
-       return candidateChains, nil
+       return chains, nil
 }
 
 func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate {
@@ -927,67 +842,190 @@ func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate
        return n
 }
 
-func (c *Certificate) buildChains(cache map[int][][]*Certificate, currentChain []*Certificate, opts *VerifyOptions) (chains [][]*Certificate, err error) {
-       possibleRoots, failedRoot, rootErr := opts.Roots.findVerifiedParents(c)
-nextRoot:
-       for _, rootNum := range possibleRoots {
-               root := opts.Roots.certs[rootNum]
+// alreadyInChain checks whether a candidate certificate is present in a chain.
+// Rather than doing a direct byte for byte equivalency check, we check if the
+// subject, public key, and SAN, if present, are equal. This prevents loops that
+// are created by mutual cross-signatures, or other cross-signature bridge
+// oddities.
+func alreadyInChain(candidate *Certificate, chain []*Certificate) bool {
+       type pubKeyEqual interface {
+               Equal(crypto.PublicKey) bool
+       }
 
-               for _, cert := range currentChain {
-                       if cert.Equal(root) {
-                               continue nextRoot
-                       }
+       var candidateSAN *pkix.Extension
+       for _, ext := range candidate.Extensions {
+               if ext.Id.Equal(oidExtensionSubjectAltName) {
+                       candidateSAN = &ext
+                       break
                }
+       }
 
-               err = root.isValid(rootCertificate, currentChain, opts)
-               if err != nil {
+       for _, cert := range chain {
+               if !bytes.Equal(candidate.RawSubject, cert.RawSubject) {
+                       continue
+               }
+               if !candidate.PublicKey.(pubKeyEqual).Equal(cert.PublicKey) {
                        continue
                }
-               chains = append(chains, appendToFreshChain(currentChain, root))
+               var certSAN *pkix.Extension
+               for _, ext := range cert.Extensions {
+                       if ext.Id.Equal(oidExtensionSubjectAltName) {
+                               certSAN = &ext
+                               break
+                       }
+               }
+               if candidateSAN == nil && certSAN == nil {
+                       return true
+               } else if candidateSAN == nil || certSAN == nil {
+                       return false
+               }
+               if bytes.Equal(candidateSAN.Value, certSAN.Value) {
+                       return true
+               }
        }
+       return false
+}
 
-       possibleIntermediates, failedIntermediate, intermediateErr := opts.Intermediates.findVerifiedParents(c)
-nextIntermediate:
-       for _, intermediateNum := range possibleIntermediates {
-               intermediate := opts.Intermediates.certs[intermediateNum]
-               for _, cert := range currentChain {
-                       if cert.Equal(intermediate) {
-                               continue nextIntermediate
+// maxChainSignatureChecks is the maximum number of CheckSignatureFrom calls
+// that an invocation of buildChains will (transitively) make. Most chains are
+// less than 15 certificates long, so this leaves space for multiple chains and
+// for failed checks due to different intermediates having the same Subject.
+const maxChainSignatureChecks = 100
+
+func (c *Certificate) buildChains(currentChain []*Certificate, sigChecks *int, opts *VerifyOptions) (chains [][]*Certificate, err error) {
+       var (
+               hintErr  error
+               hintCert *Certificate
+       )
+
+       considerCandidate := func(certType int, candidate potentialParent) {
+               if alreadyInChain(candidate.cert, currentChain) {
+                       return
+               }
+
+               if sigChecks == nil {
+                       sigChecks = new(int)
+               }
+               *sigChecks++
+               if *sigChecks > maxChainSignatureChecks {
+                       err = errors.New("x509: signature check attempts limit reached while verifying certificate chain")
+                       return
+               }
+
+               if err := c.CheckSignatureFrom(candidate.cert); err != nil {
+                       if hintErr == nil {
+                               hintErr = err
+                               hintCert = candidate.cert
                        }
+                       return
                }
-               err = intermediate.isValid(intermediateCertificate, currentChain, opts)
+
+               err = candidate.cert.isValid(certType, currentChain, opts)
                if err != nil {
-                       continue
+                       if hintErr == nil {
+                               hintErr = err
+                               hintCert = candidate.cert
+                       }
+                       return
                }
-               var childChains [][]*Certificate
-               childChains, ok := cache[intermediateNum]
-               if !ok {
-                       childChains, err = intermediate.buildChains(cache, appendToFreshChain(currentChain, intermediate), opts)
-                       cache[intermediateNum] = childChains
+
+               if candidate.constraint != nil {
+                       if err := candidate.constraint(currentChain); err != nil {
+                               if hintErr == nil {
+                                       hintErr = err
+                                       hintCert = candidate.cert
+                               }
+                               return
+                       }
                }
-               chains = append(chains, childChains...)
+
+               switch certType {
+               case rootCertificate:
+                       chains = append(chains, appendToFreshChain(currentChain, candidate.cert))
+               case intermediateCertificate:
+                       var childChains [][]*Certificate
+                       childChains, err = candidate.cert.buildChains(appendToFreshChain(currentChain, candidate.cert), sigChecks, opts)
+                       chains = append(chains, childChains...)
+               }
+       }
+
+       for _, root := range opts.Roots.findPotentialParents(c) {
+               considerCandidate(rootCertificate, root)
+       }
+       for _, intermediate := range opts.Intermediates.findPotentialParents(c) {
+               considerCandidate(intermediateCertificate, intermediate)
        }
 
        if len(chains) > 0 {
                err = nil
        }
-
        if len(chains) == 0 && err == nil {
-               hintErr := rootErr
-               hintCert := failedRoot
-               if hintErr == nil {
-                       hintErr = intermediateErr
-                       hintCert = failedIntermediate
-               }
                err = UnknownAuthorityError{c, hintErr, hintCert}
        }
 
        return
 }
 
+func validHostnamePattern(host string) bool { return validHostname(host, true) }
+func validHostnameInput(host string) bool   { return validHostname(host, false) }
+
+// validHostname reports whether host is a valid hostname that can be matched or
+// matched against according to RFC 6125 2.2, with some leniency to accommodate
+// legacy values.
+func validHostname(host string, isPattern bool) bool {
+       if !isPattern {
+               host = strings.TrimSuffix(host, ".")
+       }
+       if len(host) == 0 {
+               return false
+       }
+
+       for i, part := range strings.Split(host, ".") {
+               if part == "" {
+                       // Empty label.
+                       return false
+               }
+               if isPattern && i == 0 && part == "*" {
+                       // Only allow full left-most wildcards, as those are the only ones
+                       // we match, and matching literal '*' characters is probably never
+                       // the expected behavior.
+                       continue
+               }
+               for j, c := range part {
+                       if 'a' <= c && c <= 'z' {
+                               continue
+                       }
+                       if '0' <= c && c <= '9' {
+                               continue
+                       }
+                       if 'A' <= c && c <= 'Z' {
+                               continue
+                       }
+                       if c == '-' && j != 0 {
+                               continue
+                       }
+                       if c == '_' {
+                               // Not a valid character in hostnames, but commonly
+                               // found in deployments outside the WebPKI.
+                               continue
+                       }
+                       return false
+               }
+       }
+
+       return true
+}
+
+func matchExactly(hostA, hostB string) bool {
+       if hostA == "" || hostA == "." || hostB == "" || hostB == "." {
+               return false
+       }
+       return toLowerCaseASCII(hostA) == toLowerCaseASCII(hostB)
+}
+
 func matchHostnames(pattern, host string) bool {
-       host = strings.TrimSuffix(host, ".")
-       pattern = strings.TrimSuffix(pattern, ".")
+       pattern = toLowerCaseASCII(pattern)
+       host = toLowerCaseASCII(strings.TrimSuffix(host, "."))
 
        if len(pattern) == 0 || len(host) == 0 {
                return false
@@ -1046,6 +1084,13 @@ func toLowerCaseASCII(in string) string {
 
 // VerifyHostname returns nil if c is a valid certificate for the named host.
 // Otherwise it returns an error describing the mismatch.
+//
+// IP addresses can be optionally enclosed in square brackets and are checked
+// against the IPAddresses field. Other names are checked case insensitively
+// against the DNSNames field. If the names are valid hostnames, the certificate
+// fields can have a wildcard as the complete left-most label (e.g. *.example.com).
+//
+// Note that the legacy Common Name field is ignored.
 func (c *Certificate) VerifyHostname(h string) error {
        // IP addresses may be written in [ ].
        candidateIP := h
@@ -1054,7 +1099,7 @@ func (c *Certificate) VerifyHostname(h string) error {
        }
        if ip := net.ParseIP(candidateIP); ip != nil {
                // We only match IP addresses against IP SANs.
-               // https://tools.ietf.org/html/rfc6125#appendix-B.2
+               // See RFC 6125, Appendix B.2.
                for _, candidate := range c.IPAddresses {
                        if ip.Equal(candidate) {
                                return nil
@@ -1063,18 +1108,79 @@ func (c *Certificate) VerifyHostname(h string) error {
                return HostnameError{c, candidateIP}
        }
 
-       lowered := toLowerCaseASCII(h)
+       candidateName := toLowerCaseASCII(h) // Save allocations inside the loop.
+       validCandidateName := validHostnameInput(candidateName)
 
-       if c.hasSANExtension() {
-               for _, match := range c.DNSNames {
-                       if matchHostnames(toLowerCaseASCII(match), lowered) {
+       for _, match := range c.DNSNames {
+               // Ideally, we'd only match valid hostnames according to RFC 6125 like
+               // browsers (more or less) do, but in practice Go is used in a wider
+               // array of contexts and can't even assume DNS resolution. Instead,
+               // always allow perfect matches, and only apply wildcard and trailing
+               // dot processing to valid hostnames.
+               if validCandidateName && validHostnamePattern(match) {
+                       if matchHostnames(match, candidateName) {
+                               return nil
+                       }
+               } else {
+                       if matchExactly(match, candidateName) {
                                return nil
                        }
                }
-               // If Subject Alt Name is given, we ignore the common name.
-       } else if matchHostnames(toLowerCaseASCII(c.Subject.CommonName), lowered) {
-               return nil
        }
 
        return HostnameError{c, h}
 }
+
+func checkChainForKeyUsage(chain []*Certificate, keyUsages []ExtKeyUsage) bool {
+       usages := make([]ExtKeyUsage, len(keyUsages))
+       copy(usages, keyUsages)
+
+       if len(chain) == 0 {
+               return false
+       }
+
+       usagesRemaining := len(usages)
+
+       // We walk down the list and cross out any usages that aren't supported
+       // by each certificate. If we cross out all the usages, then the chain
+       // is unacceptable.
+
+NextCert:
+       for i := len(chain) - 1; i >= 0; i-- {
+               cert := chain[i]
+               if len(cert.ExtKeyUsage) == 0 && len(cert.UnknownExtKeyUsage) == 0 {
+                       // The certificate doesn't have any extended key usage specified.
+                       continue
+               }
+
+               for _, usage := range cert.ExtKeyUsage {
+                       if usage == ExtKeyUsageAny {
+                               // The certificate is explicitly good for any usage.
+                               continue NextCert
+                       }
+               }
+
+               const invalidUsage ExtKeyUsage = -1
+
+       NextRequestedUsage:
+               for i, requestedUsage := range usages {
+                       if requestedUsage == invalidUsage {
+                               continue
+                       }
+
+                       for _, usage := range cert.ExtKeyUsage {
+                               if requestedUsage == usage {
+                                       continue NextRequestedUsage
+                               }
+                       }
+
+                       usages[i] = invalidUsage
+                       usagesRemaining--
+                       if usagesRemaining == 0 {
+                               return false
+                       }
+               }
+       }
+
+       return true
+}