import (
"bytes"
- "encoding/asn1"
+ "crypto"
+ "crypto/x509/pkix"
"errors"
"fmt"
"net"
"net/url"
"reflect"
"runtime"
- "strconv"
"strings"
"time"
"unicode/utf8"
// 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
// 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
)
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:
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:
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
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 {
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 {
- // IsBoring is a validity check for BoringCrypto.
- // If not nil, it will be called to check whether a given certificate
- // can be used for constructing verification chains.
- IsBoring func(*Certificate) bool
+ // DNSName, if set, is checked against the leaf certificate with
+ // Certificate.VerifyHostname or the platform verifier.
+ DNSName string
- 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
}
}
// 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
// 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 {
// 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 {
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.”
reverseLabels = append(reverseLabels, domain)
domain = ""
} else {
- reverseLabels = append(reverseLabels, domain[i+1:len(domain)])
+ reverseLabels = append(reverseLabels, domain[i+1:])
domain = domain[:i]
}
}
}
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
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)
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 {
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
}
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.
- }
-
- 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 (certType == intermediateCertificate || certType == rootCertificate) &&
+ c.hasNameConstraints() {
+ toCheck := []*Certificate{}
+ for _, c := range currentChain {
+ if c.hasSANExtension() {
+ toCheck = append(toCheck, c)
}
}
- }
-
- 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)}
}
}
}
}
- if opts.IsBoring != nil && !opts.IsBoring(c) {
+ if !boringAllowCert(c) {
// IncompatibleUsage is not quite right here,
// but it's also the "no chains found" error
// and is close enough.
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)
- }
- return ret
-}
-
// 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
// 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
// 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) {
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 {
}
}
- 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 {
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
+}
+
+// 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
+ }
- 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
+ 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
+ }
+ }
+
+ 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...)
}
- 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
// 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
}
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
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
+}