import (
"bytes"
+ "crypto"
+ "crypto/x509/pkix"
"errors"
"fmt"
"net"
"net/url"
- "os"
"reflect"
"runtime"
"strings"
"unicode/utf8"
)
-// ignoreCN disables interpreting Common Name as a hostname. See issue 24151.
-var ignoreCN = strings.Contains(os.Getenv("GODEBUG"), "x509ignoreCN=1")
-
type InvalidReason int
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, and the Common Name can be interpreted as
- // a hostname.
- //
- // You can avoid this error by setting the experimental GODEBUG environment
- // variable to "x509ignoreCN=1", disabling Common Name matching entirely.
- // This behavior might become the default in the future.
+ // 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
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:
func (h HostnameError) Error() string {
c := h.Certificate
- if !c.hasSANExtension() && !validHostname(c.Subject.CommonName) &&
- matchHostnames(toLowerCaseASCII(c.Subject.CommonName), toLowerCaseASCII(h.Host)) {
- // This would have validated, if it weren't for the validHostname check on Common Name.
- return "x509: Common Name is not a valid hostname: " + c.Subject.CommonName
+ if !c.hasSANExtension() && matchHostnames(c.Subject.CommonName, h.Host) {
+ return "x509: certificate relies on legacy Common Name field, use SANs instead"
}
var valid string
valid += san.String()
}
} else {
- if c.commonNameAsHostname() {
- valid = c.Subject.CommonName
- } else {
- valid = strings.Join(c.DNSNames, ", ")
- }
+ 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 {
- 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 these extended key usage values.
- // (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)
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]
- }
-
- checkNameConstraints := (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints()
- if checkNameConstraints && leaf.commonNameAsHostname() {
- // This is the deprecated, legacy case of depending on the commonName as
- // a hostname. We don't enforce name constraints against the CN, but
- // VerifyHostname will look for hostnames in there if there are no SANs.
- // In order to ensure VerifyHostname will not accept an unchecked name,
- // return an error here.
- return CertificateInvalidError{c, NameConstraintsWithoutSANs, ""}
- } else if checkNameConstraints && leaf.hasSANExtension() {
- err := forEachSAN(leaf.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 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 (certType == intermediateCertificate || certType == rootCertificate) &&
+ c.hasNameConstraints() {
+ toCheck := []*Certificate{}
+ for _, c := range currentChain {
+ if c.hasSANExtension() {
+ toCheck = append(toCheck, c)
+ }
+ }
+ 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.
}
- 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
- }
+ return nil
+ })
- default:
- // Unknown SAN types are ignored.
+ if err != nil {
+ return err
}
-
- return nil
- })
-
- if err != nil {
- return err
}
}
}
}
+ 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 nil
}
// 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 {
var candidateChains [][]*Certificate
if opts.Roots.contains(c) {
- candidateChains = append(candidateChains, []*Certificate{c})
+ candidateChains = [][]*Certificate{{c}}
} else {
- if candidateChains, err = c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts); err != nil {
+ candidateChains, err = c.buildChains([]*Certificate{c}, nil, &opts)
+ if err != nil {
return nil, err
}
}
- keyUsages := opts.KeyUsages
- if len(keyUsages) == 0 {
- keyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth}
+ if len(opts.KeyUsages) == 0 {
+ opts.KeyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth}
}
- // If any key usage is acceptable then we're done.
- for _, usage := range keyUsages {
- if usage == ExtKeyUsageAny {
+ for _, eku := range opts.KeyUsages {
+ if eku == ExtKeyUsageAny {
+ // If any key usage is acceptable, no need to check the chain for
+ // key usages.
return candidateChains, nil
}
}
+ chains = make([][]*Certificate, 0, len(candidateChains))
for _, candidate := range candidateChains {
- if checkChainForKeyUsage(candidate, keyUsages) {
+ if checkChainForKeyUsage(candidate, opts.KeyUsages) {
chains = append(chains, candidate)
}
}
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
}
- chains = append(chains, appendToFreshChain(currentChain, root))
+ if !candidate.PublicKey.(pubKeyEqual).Equal(cert.PublicKey) {
+ continue
+ }
+ 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
}
-// validHostname returns whether host is a valid hostname that can be matched or
+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) bool {
- host = strings.TrimSuffix(host, ".")
-
+func validHostname(host string, isPattern bool) bool {
+ if !isPattern {
+ host = strings.TrimSuffix(host, ".")
+ }
if len(host) == 0 {
return false
}
// Empty label.
return false
}
- if i == 0 && part == "*" {
+ 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
}
if c == '_' {
- // _ is not a valid character in hostnames, but it's commonly
+ // Not a valid character in hostnames, but commonly
// found in deployments outside the WebPKI.
continue
}
return true
}
-// commonNameAsHostname reports whether the Common Name field should be
-// considered the hostname that the certificate is valid for. This is a legacy
-// behavior, disabled if the Subject Alt Name extension is present.
-//
-// It applies the strict validHostname check to the Common Name field, so that
-// certificates without SANs can still be validated against CAs with name
-// constraints if there is no risk the CN would be matched as a hostname.
-// See NameConstraintsWithoutSANs and issue 24151.
-func (c *Certificate) commonNameAsHostname() bool {
- return !ignoreCN && !c.hasSANExtension() && validHostname(c.Subject.CommonName)
+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.commonNameAsHostname() {
- if matchHostnames(toLowerCaseASCII(c.Subject.CommonName), lowered) {
- return nil
- }
- } else {
- 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
}
}
for _, usage := range cert.ExtKeyUsage {
if requestedUsage == usage {
continue NextRequestedUsage
- } else if requestedUsage == ExtKeyUsageServerAuth &&
- (usage == ExtKeyUsageNetscapeServerGatedCrypto ||
- usage == ExtKeyUsageMicrosoftServerGatedCrypto) {
- // In order to support COMODO
- // certificate chains, we have to
- // accept Netscape or Microsoft SGC
- // usages as equal to ServerAuth.
- continue NextRequestedUsage
}
}