mirror of https://github.com/golang/go.git
crypto/x509: enforce all name constraints and support IP, email and URI constraints
This change makes crypto/x509 enforce name constraints for all names in a leaf certificate, not just the name being validated. Thus, after this change, if a certificate validates then all the names in it can be trusted – one doesn't have a validate again for each interesting name. Making extended key usage work in this fashion still remains to be done. Updates #15196 Change-Id: I72ed5ff2f7284082d5bf3e1e86faf76cef62f9b5 Reviewed-on: https://go-review.googlesource.com/62693 Run-TryBot: Adam Langley <agl@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
a4aa5c3181
commit
9e76ce7070
File diff suppressed because it is too large
Load Diff
|
|
@ -87,7 +87,7 @@ func checkChainTrustStatus(c *Certificate, chainCtx *syscall.CertChainContext) e
|
||||||
status := chainCtx.TrustStatus.ErrorStatus
|
status := chainCtx.TrustStatus.ErrorStatus
|
||||||
switch status {
|
switch status {
|
||||||
case syscall.CERT_TRUST_IS_NOT_TIME_VALID:
|
case syscall.CERT_TRUST_IS_NOT_TIME_VALID:
|
||||||
return CertificateInvalidError{c, Expired}
|
return CertificateInvalidError{c, Expired, ""}
|
||||||
default:
|
default:
|
||||||
return UnknownAuthorityError{c, nil, nil}
|
return UnknownAuthorityError{c, nil, nil}
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +125,7 @@ func checkChainSSLServerPolicy(c *Certificate, chainCtx *syscall.CertChainContex
|
||||||
if status.Error != 0 {
|
if status.Error != 0 {
|
||||||
switch status.Error {
|
switch status.Error {
|
||||||
case syscall.CERT_E_EXPIRED:
|
case syscall.CERT_E_EXPIRED:
|
||||||
return CertificateInvalidError{c, Expired}
|
return CertificateInvalidError{c, Expired, ""}
|
||||||
case syscall.CERT_E_CN_NO_MATCH:
|
case syscall.CERT_E_CN_NO_MATCH:
|
||||||
return HostnameError{c, opts.DNSName}
|
return HostnameError{c, opts.DNSName}
|
||||||
case syscall.CERT_E_UNTRUSTEDROOT:
|
case syscall.CERT_E_UNTRUSTEDROOT:
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -25,8 +27,8 @@ const (
|
||||||
// given in the VerifyOptions.
|
// given in the VerifyOptions.
|
||||||
Expired
|
Expired
|
||||||
// CANotAuthorizedForThisName results when an intermediate or root
|
// CANotAuthorizedForThisName results when an intermediate or root
|
||||||
// certificate has a name constraint which doesn't include the name
|
// certificate has a name constraint which doesn't permit a DNS or
|
||||||
// being checked.
|
// other name (including IP address) in the leaf certificate.
|
||||||
CANotAuthorizedForThisName
|
CANotAuthorizedForThisName
|
||||||
// TooManyIntermediates results when a path length constraint is
|
// TooManyIntermediates results when a path length constraint is
|
||||||
// violated.
|
// violated.
|
||||||
|
|
@ -37,6 +39,20 @@ const (
|
||||||
// NameMismatch results when the subject name of a parent certificate
|
// NameMismatch results when the subject name of a parent certificate
|
||||||
// does not match the issuer name in the child.
|
// does not match the issuer name in the child.
|
||||||
NameMismatch
|
NameMismatch
|
||||||
|
// NameConstraintsWithoutSANs results when a leaf certificate doesn't
|
||||||
|
// contain a Subject Alternative Name extension, but a CA certificate
|
||||||
|
// contains name constraints.
|
||||||
|
NameConstraintsWithoutSANs
|
||||||
|
// UnconstrainedName results when a CA certificate contains permitted
|
||||||
|
// name constraints, but leaf certificate contains a name of an
|
||||||
|
// unsupported or unconstrained type.
|
||||||
|
UnconstrainedName
|
||||||
|
// TooManyConstraints results when the number of comparision operations
|
||||||
|
// needed to check a certificate exceeds the limit set by
|
||||||
|
// VerifyOptions.MaxConstraintComparisions. This limit exists to
|
||||||
|
// prevent pathological certificates can consuming excessive amounts of
|
||||||
|
// CPU time to verify.
|
||||||
|
TooManyConstraints
|
||||||
)
|
)
|
||||||
|
|
||||||
// CertificateInvalidError results when an odd error occurs. Users of this
|
// CertificateInvalidError results when an odd error occurs. Users of this
|
||||||
|
|
@ -44,6 +60,7 @@ const (
|
||||||
type CertificateInvalidError struct {
|
type CertificateInvalidError struct {
|
||||||
Cert *Certificate
|
Cert *Certificate
|
||||||
Reason InvalidReason
|
Reason InvalidReason
|
||||||
|
Detail string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e CertificateInvalidError) Error() string {
|
func (e CertificateInvalidError) Error() string {
|
||||||
|
|
@ -53,13 +70,17 @@ func (e CertificateInvalidError) Error() string {
|
||||||
case Expired:
|
case Expired:
|
||||||
return "x509: certificate has expired or is not yet valid"
|
return "x509: certificate has expired or is not yet valid"
|
||||||
case CANotAuthorizedForThisName:
|
case CANotAuthorizedForThisName:
|
||||||
return "x509: a root or intermediate certificate is not authorized to sign in this domain"
|
return "x509: a root or intermediate certificate is not authorized to sign for this name: " + e.Detail
|
||||||
case TooManyIntermediates:
|
case TooManyIntermediates:
|
||||||
return "x509: too many intermediates for path length constraint"
|
return "x509: too many intermediates for path length constraint"
|
||||||
case IncompatibleUsage:
|
case IncompatibleUsage:
|
||||||
return "x509: certificate specifies an incompatible key usage"
|
return "x509: certificate specifies an incompatible key usage"
|
||||||
case NameMismatch:
|
case NameMismatch:
|
||||||
return "x509: issuer name does not match subject from issuing certificate"
|
return "x509: issuer name does not match subject from issuing certificate"
|
||||||
|
case NameConstraintsWithoutSANs:
|
||||||
|
return "x509: issuer has name constraints but leaf doesn't have a SAN extension"
|
||||||
|
case UnconstrainedName:
|
||||||
|
return "x509: issuer has name constraints but leaf contains unknown or unconstrained name: " + e.Detail
|
||||||
}
|
}
|
||||||
return "x509: unknown error"
|
return "x509: unknown error"
|
||||||
}
|
}
|
||||||
|
|
@ -156,6 +177,12 @@ type VerifyOptions struct {
|
||||||
// constraint down the chain which mirrors Windows CryptoAPI behavior,
|
// constraint down the chain which mirrors Windows CryptoAPI behavior,
|
||||||
// but not the spec. To accept any key usage, include ExtKeyUsageAny.
|
// but not the spec. To accept any key usage, include ExtKeyUsageAny.
|
||||||
KeyUsages []ExtKeyUsage
|
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 pathalogical
|
||||||
|
// certificates from consuming excessive amounts of CPU time when
|
||||||
|
// validating.
|
||||||
|
MaxConstraintComparisions int
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -164,32 +191,354 @@ const (
|
||||||
rootCertificate
|
rootCertificate
|
||||||
)
|
)
|
||||||
|
|
||||||
func matchNameConstraint(domain, constraint string) bool {
|
// rfc2821Mailbox represents a “mailbox” (which is an email address to most
|
||||||
|
// people) by breaking it into the “local” (i.e. before the '@') and “domain”
|
||||||
|
// parts.
|
||||||
|
type rfc2821Mailbox struct {
|
||||||
|
local, domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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”.
|
||||||
|
func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
|
||||||
|
localPartBytes := make([]byte, 0, len(in)/2)
|
||||||
|
|
||||||
|
if in[0] == '"' {
|
||||||
|
// Quoted-string = DQUOTE *qcontent DQUOTE
|
||||||
|
// non-whitespace-control = %d1-8 / %d11 / %d12 / %d14-31 / %d127
|
||||||
|
// qcontent = qtext / quoted-pair
|
||||||
|
// qtext = non-whitespace-control /
|
||||||
|
// %d33 / %d35-91 / %d93-126
|
||||||
|
// 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.)
|
||||||
|
in = in[1:]
|
||||||
|
QuotedString:
|
||||||
|
for {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
c := in[0]
|
||||||
|
in = in[1:]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c == '"':
|
||||||
|
break QuotedString
|
||||||
|
|
||||||
|
case c == '\\':
|
||||||
|
// quoted-pair
|
||||||
|
if len(in) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
if in[0] == 11 ||
|
||||||
|
in[0] == 12 ||
|
||||||
|
(1 <= in[0] && in[0] <= 9) ||
|
||||||
|
(14 <= in[0] && in[0] <= 127) {
|
||||||
|
localPartBytes = append(localPartBytes, in[0])
|
||||||
|
in = in[1:]
|
||||||
|
} else {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case c == 11 ||
|
||||||
|
c == 12 ||
|
||||||
|
// Space (char 32) is not allowed based on the
|
||||||
|
// BNF, but RFC 3696 gives an example that
|
||||||
|
// assumes that it is. Several “verified”
|
||||||
|
// errata continue to argue about this point.
|
||||||
|
// We choose to accept it.
|
||||||
|
c == 32 ||
|
||||||
|
c == 33 ||
|
||||||
|
c == 127 ||
|
||||||
|
(1 <= c && c <= 8) ||
|
||||||
|
(14 <= c && c <= 31) ||
|
||||||
|
(35 <= c && c <= 91) ||
|
||||||
|
(93 <= c && c <= 126):
|
||||||
|
// qtext
|
||||||
|
localPartBytes = append(localPartBytes, c)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Atom ("." Atom)*
|
||||||
|
NextChar:
|
||||||
|
for len(in) > 0 {
|
||||||
|
// atext from https://tools.ietf.org/html/rfc2822#section-3.2.4
|
||||||
|
c := in[0]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c == '\\':
|
||||||
|
// Examples given in RFC 3696 suggest that
|
||||||
|
// escaped characters can appear outside of a
|
||||||
|
// quoted string. Several “verified” errata
|
||||||
|
// continue to argue the point. We choose to
|
||||||
|
// accept it.
|
||||||
|
in = in[1:]
|
||||||
|
if len(in) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case ('0' <= c && c <= '9') ||
|
||||||
|
('a' <= c && c <= 'z') ||
|
||||||
|
('A' <= c && c <= 'Z') ||
|
||||||
|
c == '!' || c == '#' || c == '$' || c == '%' ||
|
||||||
|
c == '&' || c == '\'' || c == '*' || c == '+' ||
|
||||||
|
c == '-' || c == '/' || c == '=' || c == '?' ||
|
||||||
|
c == '^' || c == '_' || c == '`' || c == '{' ||
|
||||||
|
c == '|' || c == '}' || c == '~' || c == '.':
|
||||||
|
localPartBytes = append(localPartBytes, in[0])
|
||||||
|
in = in[1:]
|
||||||
|
|
||||||
|
default:
|
||||||
|
break NextChar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(localPartBytes) == 0 {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc3696#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.”
|
||||||
|
twoDots := []byte{'.', '.'}
|
||||||
|
if localPartBytes[0] == '.' ||
|
||||||
|
localPartBytes[len(localPartBytes)-1] == '.' ||
|
||||||
|
bytes.Contains(localPartBytes, twoDots) {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(in) == 0 || in[0] != '@' {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
in = in[1:]
|
||||||
|
|
||||||
|
// The RFC species a format for domains, but that's known to be
|
||||||
|
// violated in practice so we accept that anything after an '@' is the
|
||||||
|
// domain part.
|
||||||
|
if _, ok := domainToReverseLabels(in); !ok {
|
||||||
|
return mailbox, false
|
||||||
|
}
|
||||||
|
|
||||||
|
mailbox.local = string(localPartBytes)
|
||||||
|
mailbox.domain = in
|
||||||
|
return mailbox, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// domainToReverseLabels converts a textual domain name like foo.example.com to
|
||||||
|
// the list of labels in reverse order, e.g. ["com", "example", "foo"].
|
||||||
|
func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
|
||||||
|
for len(domain) > 0 {
|
||||||
|
if i := strings.LastIndexByte(domain, '.'); i == -1 {
|
||||||
|
reverseLabels = append(reverseLabels, domain)
|
||||||
|
domain = ""
|
||||||
|
} else {
|
||||||
|
reverseLabels = append(reverseLabels, domain[i+1:len(domain)])
|
||||||
|
domain = domain[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reverseLabels) > 0 && len(reverseLabels[0]) == 0 {
|
||||||
|
// An empty label at the end indicates an absolute value.
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, label := range reverseLabels {
|
||||||
|
if len(label) == 0 {
|
||||||
|
// Empty labels are otherwise invalid.
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range label {
|
||||||
|
if c < 33 || c > 126 {
|
||||||
|
// Invalid character.
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reverseLabels, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) {
|
||||||
|
// If the constraint contains an @, then it specifies an exact mailbox
|
||||||
|
// name.
|
||||||
|
if strings.Contains(constraint, "@") {
|
||||||
|
constraintMailbox, ok := parseRFC2821Mailbox(constraint)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("x509: internal error: cannot parse constraint %q", constraint)
|
||||||
|
}
|
||||||
|
return mailbox.local == constraintMailbox.local && strings.EqualFold(mailbox.domain, constraintMailbox.domain), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the constraint is like a DNS constraint of the domain part
|
||||||
|
// of the mailbox.
|
||||||
|
return matchDomainConstraint(mailbox.domain, constraint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
|
||||||
|
// https://tools.ietf.org/html/rfc5280#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
|
||||||
|
// component or includes an authority component in which the host name
|
||||||
|
// is specified as an IP address), then the application MUST reject the
|
||||||
|
// certificate.”
|
||||||
|
|
||||||
|
host := uri.Host
|
||||||
|
if len(host) == 0 {
|
||||||
|
return false, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") {
|
||||||
|
var err error
|
||||||
|
host, _, err = net.SplitHostPort(uri.Host)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") ||
|
||||||
|
net.ParseIP(host) != nil {
|
||||||
|
return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchDomainConstraint(host, constraint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) {
|
||||||
|
if len(ip) != len(constraint.IP) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range ip {
|
||||||
|
if mask := constraint.Mask[i]; ip[i]&mask != constraint.IP[i]&mask {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchDomainConstraint(domain, constraint string) (bool, error) {
|
||||||
// The meaning of zero length constraints is not specified, but this
|
// The meaning of zero length constraints is not specified, but this
|
||||||
// code follows NSS and accepts them as matching everything.
|
// code follows NSS and accepts them as matching everything.
|
||||||
if len(constraint) == 0 {
|
if len(constraint) == 0 {
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(domain) < len(constraint) {
|
domainLabels, ok := domainToReverseLabels(domain)
|
||||||
return false
|
if !ok {
|
||||||
|
return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixLen := len(domain) - len(constraint)
|
// RFC 5280 says that a leading period in a domain name means that at
|
||||||
if !strings.EqualFold(domain[prefixLen:], constraint) {
|
// least one label must be prepended, but only for URI and email
|
||||||
return false
|
// constraints, not DNS constraints. The code also supports that
|
||||||
|
// behaviour for DNS constraints.
|
||||||
|
|
||||||
|
mustHaveSubdomains := false
|
||||||
|
if constraint[0] == '.' {
|
||||||
|
mustHaveSubdomains = true
|
||||||
|
constraint = constraint[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
if prefixLen == 0 {
|
constraintLabels, ok := domainToReverseLabels(constraint)
|
||||||
return true
|
if !ok {
|
||||||
|
return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSubdomain := domain[prefixLen-1] == '.'
|
if len(domainLabels) < len(constraintLabels) ||
|
||||||
constraintHasLeadingDot := constraint[0] == '.'
|
(mustHaveSubdomains && len(domainLabels) == len(constraintLabels)) {
|
||||||
return isSubdomain != constraintHasLeadingDot
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isValid performs validity checks on the c.
|
for i, constraintLabel := range constraintLabels {
|
||||||
|
if !strings.EqualFold(constraintLabel, domainLabels[i]) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkNameConstraints checks that c permits a child certificate to claim the
|
||||||
|
// given name, of type nameType. The argument parsedName contains the parsed
|
||||||
|
// form of name, suitable for passing to the match function. The total number
|
||||||
|
// of comparisons is tracked in the given count and should not exceed the given
|
||||||
|
// limit.
|
||||||
|
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 {
|
||||||
|
|
||||||
|
excludedValue := reflect.ValueOf(excluded)
|
||||||
|
|
||||||
|
*count += excludedValue.Len()
|
||||||
|
if *count > maxConstraintComparisons {
|
||||||
|
return CertificateInvalidError{c, TooManyConstraints, ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < excludedValue.Len(); i++ {
|
||||||
|
constraint := excludedValue.Index(i).Interface()
|
||||||
|
match, err := match(parsedName, constraint)
|
||||||
|
if err != nil {
|
||||||
|
return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
return CertificateInvalidError{c, CANotAuthorizedForThisName, fmt.Sprintf("%s %q is excluded by constraint %q", nameType, name, constraint)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
permittedValue := reflect.ValueOf(permitted)
|
||||||
|
|
||||||
|
*count += permittedValue.Len()
|
||||||
|
if *count > maxConstraintComparisons {
|
||||||
|
return CertificateInvalidError{c, TooManyConstraints, ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
for i := 0; i < permittedValue.Len(); i++ {
|
||||||
|
constraint := permittedValue.Index(i).Interface()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if ok, err = match(parsedName, constraint); err != nil {
|
||||||
|
return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return CertificateInvalidError{c, CANotAuthorizedForThisName, fmt.Sprintf("%s %q is not permitted by any constraint", nameType, name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error {
|
||||||
if len(c.UnhandledCriticalExtensions) > 0 {
|
if len(c.UnhandledCriticalExtensions) > 0 {
|
||||||
return UnhandledCriticalExtension{}
|
return UnhandledCriticalExtension{}
|
||||||
|
|
@ -198,7 +547,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||||
if len(currentChain) > 0 {
|
if len(currentChain) > 0 {
|
||||||
child := currentChain[len(currentChain)-1]
|
child := currentChain[len(currentChain)-1]
|
||||||
if !bytes.Equal(child.RawIssuer, c.RawSubject) {
|
if !bytes.Equal(child.RawIssuer, c.RawSubject) {
|
||||||
return CertificateInvalidError{c, NameMismatch}
|
return CertificateInvalidError{c, NameMismatch, ""}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,26 +556,92 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
}
|
}
|
||||||
if now.Before(c.NotBefore) || now.After(c.NotAfter) {
|
if now.Before(c.NotBefore) || now.After(c.NotAfter) {
|
||||||
return CertificateInvalidError{c, Expired}
|
return CertificateInvalidError{c, Expired, ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.PermittedDNSDomains) > 0 {
|
if (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() {
|
||||||
ok := false
|
maxConstraintComparisons := opts.MaxConstraintComparisions
|
||||||
for _, constraint := range c.PermittedDNSDomains {
|
if maxConstraintComparisons == 0 {
|
||||||
ok = matchNameConstraint(opts.DNSName, constraint)
|
maxConstraintComparisons = 250000
|
||||||
if ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
if len(currentChain) == 0 {
|
||||||
|
return errors.New("x509: internal error: empty chain when appending CA cert")
|
||||||
|
}
|
||||||
|
leaf := currentChain[0]
|
||||||
|
|
||||||
|
sanExtension, ok := leaf.getSANExtension()
|
||||||
if !ok {
|
if !ok {
|
||||||
return CertificateInvalidError{c, CANotAuthorizedForThisName}
|
// 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, ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, constraint := range c.ExcludedDNSDomains {
|
err := forEachSAN(sanExtension, func(tag int, data []byte) error {
|
||||||
if matchNameConstraint(opts.DNSName, constraint) {
|
switch tag {
|
||||||
return CertificateInvalidError{c, CANotAuthorizedForThisName}
|
case nameTypeEmail:
|
||||||
|
name := string(data)
|
||||||
|
mailbox, ok := parseRFC2821Mailbox(name)
|
||||||
|
if !ok {
|
||||||
|
// This certificate should not have parsed.
|
||||||
|
return errors.New("x509: internal error: rfc822Name SAN failed to parse")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.checkNameConstraints(&count, 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 err := c.checkNameConstraints(&count, 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(&count, 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(&count, 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,13 +663,13 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||||
// encryption key could only be used for Diffie-Hellman key agreement.
|
// encryption key could only be used for Diffie-Hellman key agreement.
|
||||||
|
|
||||||
if certType == intermediateCertificate && (!c.BasicConstraintsValid || !c.IsCA) {
|
if certType == intermediateCertificate && (!c.BasicConstraintsValid || !c.IsCA) {
|
||||||
return CertificateInvalidError{c, NotAuthorizedToSign}
|
return CertificateInvalidError{c, NotAuthorizedToSign, ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.BasicConstraintsValid && c.MaxPathLen >= 0 {
|
if c.BasicConstraintsValid && c.MaxPathLen >= 0 {
|
||||||
numIntermediates := len(currentChain) - 1
|
numIntermediates := len(currentChain) - 1
|
||||||
if numIntermediates > c.MaxPathLen {
|
if numIntermediates > c.MaxPathLen {
|
||||||
return CertificateInvalidError{c, TooManyIntermediates}
|
return CertificateInvalidError{c, TooManyIntermediates, ""}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,7 +752,7 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(chains) == 0 {
|
if len(chains) == 0 {
|
||||||
err = CertificateInvalidError{c, IncompatibleUsage}
|
err = CertificateInvalidError{c, IncompatibleUsage, ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1551,22 +1551,37 @@ func TestUnknownAuthorityError(t *testing.T) {
|
||||||
|
|
||||||
var nameConstraintTests = []struct {
|
var nameConstraintTests = []struct {
|
||||||
constraint, domain string
|
constraint, domain string
|
||||||
|
expectError bool
|
||||||
shouldMatch bool
|
shouldMatch bool
|
||||||
}{
|
}{
|
||||||
{"", "anything.com", true},
|
{"", "anything.com", false, true},
|
||||||
{"example.com", "example.com", true},
|
{"example.com", "example.com", false, true},
|
||||||
{"example.com", "ExAmPle.coM", true},
|
{"example.com.", "example.com", true, false},
|
||||||
{"example.com", "exampl1.com", false},
|
{"example.com", "example.com.", true, false},
|
||||||
{"example.com", "www.ExAmPle.coM", true},
|
{"example.com", "ExAmPle.coM", false, true},
|
||||||
{"example.com", "notexample.com", false},
|
{"example.com", "exampl1.com", false, false},
|
||||||
{".example.com", "example.com", false},
|
{"example.com", "www.ExAmPle.coM", false, true},
|
||||||
{".example.com", "www.example.com", true},
|
{"example.com", "sub.www.ExAmPle.coM", false, true},
|
||||||
{".example.com", "www..example.com", false},
|
{"example.com", "notexample.com", false, false},
|
||||||
|
{".example.com", "example.com", false, false},
|
||||||
|
{".example.com", "www.example.com", false, true},
|
||||||
|
{".example.com", "www..example.com", true, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNameConstraints(t *testing.T) {
|
func TestNameConstraints(t *testing.T) {
|
||||||
for i, test := range nameConstraintTests {
|
for i, test := range nameConstraintTests {
|
||||||
result := matchNameConstraint(test.domain, test.constraint)
|
result, err := matchDomainConstraint(test.domain, test.constraint)
|
||||||
|
|
||||||
|
if err != nil && !test.expectError {
|
||||||
|
t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && test.expectError {
|
||||||
|
t.Errorf("unexpected success for test #%d: domain=%s, constraint=%s", i, test.domain, test.constraint)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if result != test.shouldMatch {
|
if result != test.shouldMatch {
|
||||||
t.Errorf("unexpected result for test #%d: domain=%s, constraint=%s, result=%t", i, test.domain, test.constraint, result)
|
t.Errorf("unexpected result for test #%d: domain=%s, constraint=%s, result=%t", i, test.domain, test.constraint, result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -698,11 +700,18 @@ type Certificate struct {
|
||||||
DNSNames []string
|
DNSNames []string
|
||||||
EmailAddresses []string
|
EmailAddresses []string
|
||||||
IPAddresses []net.IP
|
IPAddresses []net.IP
|
||||||
|
URIs []*url.URL
|
||||||
|
|
||||||
// Name constraints
|
// Name constraints
|
||||||
PermittedDNSDomainsCritical bool // if true then the name constraints are marked critical.
|
PermittedDNSDomainsCritical bool // if true then the name constraints are marked critical.
|
||||||
PermittedDNSDomains []string
|
PermittedDNSDomains []string
|
||||||
ExcludedDNSDomains []string
|
ExcludedDNSDomains []string
|
||||||
|
PermittedIPRanges []*net.IPNet
|
||||||
|
ExcludedIPRanges []*net.IPNet
|
||||||
|
PermittedEmailAddresses []string
|
||||||
|
ExcludedEmailAddresses []string
|
||||||
|
PermittedURIDomains []string
|
||||||
|
ExcludedURIDomains []string
|
||||||
|
|
||||||
// CRL Distribution Points
|
// CRL Distribution Points
|
||||||
CRLDistributionPoints []string
|
CRLDistributionPoints []string
|
||||||
|
|
@ -821,6 +830,26 @@ func (c *Certificate) CheckSignature(algo SignatureAlgorithm, signed, signature
|
||||||
return checkSignature(algo, signed, signature, c.PublicKey)
|
return checkSignature(algo, signed, signature, c.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) hasNameConstraints() bool {
|
||||||
|
for _, e := range c.Extensions {
|
||||||
|
if len(e.Id) == 4 && e.Id[0] == 2 && e.Id[1] == 5 && e.Id[2] == 29 && e.Id[3] == 30 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Certificate) getSANExtension() ([]byte, bool) {
|
||||||
|
for _, e := range c.Extensions {
|
||||||
|
if len(e.Id) == 4 && e.Id[0] == 2 && e.Id[1] == 5 && e.Id[2] == 29 && e.Id[3] == 17 {
|
||||||
|
return e.Value, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
func signaturePublicKeyAlgoMismatchError(expectedPubKeyAlgo PublicKeyAlgorithm, pubKey interface{}) error {
|
func signaturePublicKeyAlgoMismatchError(expectedPubKeyAlgo PublicKeyAlgorithm, pubKey interface{}) error {
|
||||||
return fmt.Errorf("x509: signature algorithm specifies an %s public key, but have public key of type %T", expectedPubKeyAlgo.String(), pubKey)
|
return fmt.Errorf("x509: signature algorithm specifies an %s public key, but have public key of type %T", expectedPubKeyAlgo.String(), pubKey)
|
||||||
}
|
}
|
||||||
|
|
@ -930,8 +959,18 @@ type nameConstraints struct {
|
||||||
Excluded []generalSubtree `asn1:"optional,tag:1"`
|
Excluded []generalSubtree `asn1:"optional,tag:1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
nameTypeEmail = 1
|
||||||
|
nameTypeDNS = 2
|
||||||
|
nameTypeURI = 6
|
||||||
|
nameTypeIP = 7
|
||||||
|
)
|
||||||
|
|
||||||
type generalSubtree struct {
|
type generalSubtree struct {
|
||||||
|
Email string `asn1:"tag:1,optional,ia5"`
|
||||||
Name string `asn1:"tag:2,optional,ia5"`
|
Name string `asn1:"tag:2,optional,ia5"`
|
||||||
|
URIDomain string `asn1:"tag:6,optional,ia5"`
|
||||||
|
IPAndMask []byte `asn1:"tag:7,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC 5280, 4.2.2.1
|
// RFC 5280, 4.2.2.1
|
||||||
|
|
@ -1086,14 +1125,33 @@ func forEachSAN(extension []byte, callback func(tag int, data []byte) error) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddresses []net.IP, err error) {
|
func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, err error) {
|
||||||
err = forEachSAN(value, func(tag int, data []byte) error {
|
err = forEachSAN(value, func(tag int, data []byte) error {
|
||||||
switch tag {
|
switch tag {
|
||||||
case 1:
|
case nameTypeEmail:
|
||||||
emailAddresses = append(emailAddresses, string(data))
|
mailbox := string(data)
|
||||||
case 2:
|
if _, ok := parseRFC2821Mailbox(mailbox); !ok {
|
||||||
dnsNames = append(dnsNames, string(data))
|
return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox)
|
||||||
case 7:
|
}
|
||||||
|
emailAddresses = append(emailAddresses, mailbox)
|
||||||
|
case nameTypeDNS:
|
||||||
|
domain := string(data)
|
||||||
|
if _, ok := domainToReverseLabels(domain); !ok {
|
||||||
|
return fmt.Errorf("x509: cannot parse dnsName %q", string(data))
|
||||||
|
}
|
||||||
|
dnsNames = append(dnsNames, domain)
|
||||||
|
case nameTypeURI:
|
||||||
|
uri, err := url.Parse(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("x509: cannot parse URI %q: %s", string(data), err)
|
||||||
|
}
|
||||||
|
if len(uri.Host) > 0 {
|
||||||
|
if _, ok := domainToReverseLabels(uri.Host); !ok {
|
||||||
|
return fmt.Errorf("x509: cannot parse URI %q: invalid domain", string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uris = append(uris, uri)
|
||||||
|
case nameTypeIP:
|
||||||
switch len(data) {
|
switch len(data) {
|
||||||
case net.IPv4len, net.IPv6len:
|
case net.IPv4len, net.IPv6len:
|
||||||
ipAddresses = append(ipAddresses, data)
|
ipAddresses = append(ipAddresses, data)
|
||||||
|
|
@ -1108,6 +1166,160 @@ func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddre
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidIPMask returns true iff mask consists of zero or more 1 bits, followed by zero bits.
|
||||||
|
func isValidIPMask(mask []byte) bool {
|
||||||
|
seenZero := false
|
||||||
|
|
||||||
|
for _, b := range mask {
|
||||||
|
if seenZero {
|
||||||
|
if b != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b {
|
||||||
|
case 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe:
|
||||||
|
seenZero = true
|
||||||
|
case 0xff:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandled bool, err error) {
|
||||||
|
// RFC 5280, 4.2.1.10
|
||||||
|
|
||||||
|
// NameConstraints ::= SEQUENCE {
|
||||||
|
// permittedSubtrees [0] GeneralSubtrees OPTIONAL,
|
||||||
|
// excludedSubtrees [1] GeneralSubtrees OPTIONAL }
|
||||||
|
//
|
||||||
|
// GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
|
||||||
|
//
|
||||||
|
// GeneralSubtree ::= SEQUENCE {
|
||||||
|
// base GeneralName,
|
||||||
|
// minimum [0] BaseDistance DEFAULT 0,
|
||||||
|
// maximum [1] BaseDistance OPTIONAL }
|
||||||
|
//
|
||||||
|
// BaseDistance ::= INTEGER (0..MAX)
|
||||||
|
|
||||||
|
var constraints nameConstraints
|
||||||
|
if rest, err := asn1.Unmarshal(e.Value, &constraints); err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if len(rest) != 0 {
|
||||||
|
return false, errors.New("x509: trailing data after X.509 NameConstraints")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(constraints.Permitted) == 0 && len(constraints.Excluded) == 0 {
|
||||||
|
// https://tools.ietf.org/html/rfc5280#section-4.2.1.10:
|
||||||
|
// “either the permittedSubtrees field
|
||||||
|
// or the excludedSubtrees MUST be
|
||||||
|
// present”
|
||||||
|
return false, errors.New("x509: empty name constraints extension")
|
||||||
|
}
|
||||||
|
|
||||||
|
getValues := func(subtrees []generalSubtree) (dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) {
|
||||||
|
for _, subtree := range subtrees {
|
||||||
|
switch {
|
||||||
|
case len(subtree.Name) != 0:
|
||||||
|
domain := subtree.Name
|
||||||
|
if len(domain) > 0 && domain[0] == '.' {
|
||||||
|
// constraints can have a leading
|
||||||
|
// period to exclude the domain
|
||||||
|
// itself, but that's not valid in a
|
||||||
|
// normal domain name.
|
||||||
|
domain = domain[1:]
|
||||||
|
}
|
||||||
|
if _, ok := domainToReverseLabels(domain); !ok {
|
||||||
|
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", subtree.Name)
|
||||||
|
}
|
||||||
|
dnsNames = append(dnsNames, subtree.Name)
|
||||||
|
|
||||||
|
case len(subtree.IPAndMask) != 0:
|
||||||
|
l := len(subtree.IPAndMask)
|
||||||
|
var ip, mask []byte
|
||||||
|
|
||||||
|
switch l {
|
||||||
|
case 8:
|
||||||
|
ip = subtree.IPAndMask[:4]
|
||||||
|
mask = subtree.IPAndMask[4:]
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
ip = subtree.IPAndMask[:16]
|
||||||
|
mask = subtree.IPAndMask[16:]
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValidIPMask(mask) {
|
||||||
|
return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
ips = append(ips, &net.IPNet{IP: net.IP(ip), Mask: net.IPMask(mask)})
|
||||||
|
|
||||||
|
case len(subtree.Email) != 0:
|
||||||
|
constraint := subtree.Email
|
||||||
|
// If the constraint contains an @ then
|
||||||
|
// it specifies an exact mailbox name.
|
||||||
|
if strings.Contains(constraint, "@") {
|
||||||
|
if _, ok := parseRFC2821Mailbox(constraint); !ok {
|
||||||
|
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise it's a domain name.
|
||||||
|
domain := constraint
|
||||||
|
if len(domain) > 0 && domain[0] == '.' {
|
||||||
|
domain = domain[1:]
|
||||||
|
}
|
||||||
|
if _, ok := domainToReverseLabels(domain); !ok {
|
||||||
|
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emails = append(emails, constraint)
|
||||||
|
|
||||||
|
case len(subtree.URIDomain) != 0:
|
||||||
|
domain := subtree.URIDomain
|
||||||
|
|
||||||
|
if net.ParseIP(domain) != nil {
|
||||||
|
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", subtree.URIDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domain) > 0 && domain[0] == '.' {
|
||||||
|
// constraints can have a leading
|
||||||
|
// period to exclude the domain
|
||||||
|
// itself, but that's not valid in a
|
||||||
|
// normal domain name.
|
||||||
|
domain = domain[1:]
|
||||||
|
}
|
||||||
|
if _, ok := domainToReverseLabels(domain); !ok {
|
||||||
|
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", subtree.URIDomain)
|
||||||
|
}
|
||||||
|
uriDomains = append(uriDomains, subtree.URIDomain)
|
||||||
|
|
||||||
|
default:
|
||||||
|
unhandled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dnsNames, ips, emails, uriDomains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(constraints.Permitted); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(constraints.Excluded); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
out.PermittedDNSDomainsCritical = e.Critical
|
||||||
|
|
||||||
|
return unhandled, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseCertificate(in *certificate) (*Certificate, error) {
|
func parseCertificate(in *certificate) (*Certificate, error) {
|
||||||
out := new(Certificate)
|
out := new(Certificate)
|
||||||
out.Raw = in.Raw
|
out.Raw = in.Raw
|
||||||
|
|
@ -1187,69 +1399,22 @@ func parseCertificate(in *certificate) (*Certificate, error) {
|
||||||
out.MaxPathLenZero = out.MaxPathLen == 0
|
out.MaxPathLenZero = out.MaxPathLen == 0
|
||||||
// TODO: map out.MaxPathLen to 0 if it has the -1 default value? (Issue 19285)
|
// TODO: map out.MaxPathLen to 0 if it has the -1 default value? (Issue 19285)
|
||||||
case 17:
|
case 17:
|
||||||
out.DNSNames, out.EmailAddresses, out.IPAddresses, err = parseSANExtension(e.Value)
|
out.DNSNames, out.EmailAddresses, out.IPAddresses, out.URIs, err = parseSANExtension(e.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(out.DNSNames) == 0 && len(out.EmailAddresses) == 0 && len(out.IPAddresses) == 0 {
|
if len(out.DNSNames) == 0 && len(out.EmailAddresses) == 0 && len(out.IPAddresses) == 0 && len(out.URIs) == 0 {
|
||||||
// If we didn't parse anything then we do the critical check, below.
|
// If we didn't parse anything then we do the critical check, below.
|
||||||
unhandled = true
|
unhandled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
case 30:
|
case 30:
|
||||||
// RFC 5280, 4.2.1.10
|
unhandled, err = parseNameConstraintsExtension(out, e)
|
||||||
|
if err != nil {
|
||||||
// NameConstraints ::= SEQUENCE {
|
|
||||||
// permittedSubtrees [0] GeneralSubtrees OPTIONAL,
|
|
||||||
// excludedSubtrees [1] GeneralSubtrees OPTIONAL }
|
|
||||||
//
|
|
||||||
// GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
|
|
||||||
//
|
|
||||||
// GeneralSubtree ::= SEQUENCE {
|
|
||||||
// base GeneralName,
|
|
||||||
// minimum [0] BaseDistance DEFAULT 0,
|
|
||||||
// maximum [1] BaseDistance OPTIONAL }
|
|
||||||
//
|
|
||||||
// BaseDistance ::= INTEGER (0..MAX)
|
|
||||||
|
|
||||||
var constraints nameConstraints
|
|
||||||
if rest, err := asn1.Unmarshal(e.Value, &constraints); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if len(rest) != 0 {
|
|
||||||
return nil, errors.New("x509: trailing data after X.509 NameConstraints")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(constraints.Permitted) == 0 && len(constraints.Excluded) == 0 {
|
|
||||||
// https://tools.ietf.org/html/rfc5280#section-4.2.1.10:
|
|
||||||
// “either the permittedSubtrees field
|
|
||||||
// or the excludedSubtrees MUST be
|
|
||||||
// present”
|
|
||||||
return nil, errors.New("x509: empty name constraints extension")
|
|
||||||
}
|
|
||||||
|
|
||||||
getDNSNames := func(subtrees []generalSubtree, isCritical bool) (dnsNames []string, err error) {
|
|
||||||
for _, subtree := range subtrees {
|
|
||||||
if len(subtree.Name) == 0 {
|
|
||||||
if isCritical {
|
|
||||||
return nil, UnhandledCriticalExtension{}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dnsNames = append(dnsNames, subtree.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dnsNames, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.PermittedDNSDomains, err = getDNSNames(constraints.Permitted, e.Critical); err != nil {
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
if out.ExcludedDNSDomains, err = getDNSNames(constraints.Excluded, e.Critical); err != nil {
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
out.PermittedDNSDomainsCritical = e.Critical
|
|
||||||
|
|
||||||
case 31:
|
case 31:
|
||||||
// RFC 5280, 4.2.1.13
|
// RFC 5280, 4.2.1.13
|
||||||
|
|
||||||
|
|
@ -1483,13 +1648,13 @@ func oidInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) boo
|
||||||
|
|
||||||
// marshalSANs marshals a list of addresses into a the contents of an X.509
|
// marshalSANs marshals a list of addresses into a the contents of an X.509
|
||||||
// SubjectAlternativeName extension.
|
// SubjectAlternativeName extension.
|
||||||
func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP) (derBytes []byte, err error) {
|
func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL) (derBytes []byte, err error) {
|
||||||
var rawValues []asn1.RawValue
|
var rawValues []asn1.RawValue
|
||||||
for _, name := range dnsNames {
|
for _, name := range dnsNames {
|
||||||
rawValues = append(rawValues, asn1.RawValue{Tag: 2, Class: 2, Bytes: []byte(name)})
|
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeDNS, Class: 2, Bytes: []byte(name)})
|
||||||
}
|
}
|
||||||
for _, email := range emailAddresses {
|
for _, email := range emailAddresses {
|
||||||
rawValues = append(rawValues, asn1.RawValue{Tag: 1, Class: 2, Bytes: []byte(email)})
|
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeEmail, Class: 2, Bytes: []byte(email)})
|
||||||
}
|
}
|
||||||
for _, rawIP := range ipAddresses {
|
for _, rawIP := range ipAddresses {
|
||||||
// If possible, we always want to encode IPv4 addresses in 4 bytes.
|
// If possible, we always want to encode IPv4 addresses in 4 bytes.
|
||||||
|
|
@ -1497,7 +1662,10 @@ func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP) (derBy
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
ip = rawIP
|
ip = rawIP
|
||||||
}
|
}
|
||||||
rawValues = append(rawValues, asn1.RawValue{Tag: 7, Class: 2, Bytes: ip})
|
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeIP, Class: 2, Bytes: ip})
|
||||||
|
}
|
||||||
|
for _, uri := range uris {
|
||||||
|
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeURI, Class: 2, Bytes: []byte(uri.String())})
|
||||||
}
|
}
|
||||||
return asn1.Marshal(rawValues)
|
return asn1.Marshal(rawValues)
|
||||||
}
|
}
|
||||||
|
|
@ -1608,10 +1776,10 @@ func buildExtensions(template *Certificate, authorityKeyId []byte) (ret []pkix.E
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0) &&
|
if (len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0 || len(template.URIs) > 0) &&
|
||||||
!oidInExtensions(oidExtensionSubjectAltName, template.ExtraExtensions) {
|
!oidInExtensions(oidExtensionSubjectAltName, template.ExtraExtensions) {
|
||||||
ret[n].Id = oidExtensionSubjectAltName
|
ret[n].Id = oidExtensionSubjectAltName
|
||||||
ret[n].Value, err = marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses)
|
ret[n].Value, err = marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses, template.URIs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -1632,20 +1800,50 @@ func buildExtensions(template *Certificate, authorityKeyId []byte) (ret []pkix.E
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len(template.PermittedDNSDomains) > 0 || len(template.ExcludedDNSDomains) > 0) &&
|
if (len(template.PermittedDNSDomains) > 0 || len(template.ExcludedDNSDomains) > 0 ||
|
||||||
|
len(template.PermittedIPRanges) > 0 || len(template.ExcludedIPRanges) > 0 ||
|
||||||
|
len(template.PermittedEmailAddresses) > 0 || len(template.ExcludedEmailAddresses) > 0 ||
|
||||||
|
len(template.PermittedURIDomains) > 0 || len(template.ExcludedURIDomains) > 0) &&
|
||||||
!oidInExtensions(oidExtensionNameConstraints, template.ExtraExtensions) {
|
!oidInExtensions(oidExtensionNameConstraints, template.ExtraExtensions) {
|
||||||
ret[n].Id = oidExtensionNameConstraints
|
ret[n].Id = oidExtensionNameConstraints
|
||||||
ret[n].Critical = template.PermittedDNSDomainsCritical
|
ret[n].Critical = template.PermittedDNSDomainsCritical
|
||||||
|
|
||||||
var out nameConstraints
|
var out nameConstraints
|
||||||
|
|
||||||
out.Permitted = make([]generalSubtree, len(template.PermittedDNSDomains))
|
ipAndMask := func(ipNet *net.IPNet) []byte {
|
||||||
for i, permitted := range template.PermittedDNSDomains {
|
maskedIP := ipNet.IP.Mask(ipNet.Mask)
|
||||||
out.Permitted[i] = generalSubtree{Name: permitted}
|
ipAndMask := make([]byte, 0, len(maskedIP)+len(ipNet.Mask))
|
||||||
|
ipAndMask = append(ipAndMask, maskedIP...)
|
||||||
|
ipAndMask = append(ipAndMask, ipNet.Mask...)
|
||||||
|
return ipAndMask
|
||||||
}
|
}
|
||||||
out.Excluded = make([]generalSubtree, len(template.ExcludedDNSDomains))
|
|
||||||
for i, excluded := range template.ExcludedDNSDomains {
|
out.Permitted = make([]generalSubtree, 0, len(template.PermittedDNSDomains)+len(template.PermittedIPRanges))
|
||||||
out.Excluded[i] = generalSubtree{Name: excluded}
|
for _, permitted := range template.PermittedDNSDomains {
|
||||||
|
out.Permitted = append(out.Permitted, generalSubtree{Name: permitted})
|
||||||
|
}
|
||||||
|
for _, permitted := range template.PermittedIPRanges {
|
||||||
|
out.Permitted = append(out.Permitted, generalSubtree{IPAndMask: ipAndMask(permitted)})
|
||||||
|
}
|
||||||
|
for _, permitted := range template.PermittedEmailAddresses {
|
||||||
|
out.Permitted = append(out.Permitted, generalSubtree{Email: permitted})
|
||||||
|
}
|
||||||
|
for _, permitted := range template.PermittedURIDomains {
|
||||||
|
out.Permitted = append(out.Permitted, generalSubtree{URIDomain: permitted})
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Excluded = make([]generalSubtree, 0, len(template.ExcludedDNSDomains)+len(template.ExcludedIPRanges))
|
||||||
|
for _, excluded := range template.ExcludedDNSDomains {
|
||||||
|
out.Excluded = append(out.Excluded, generalSubtree{Name: excluded})
|
||||||
|
}
|
||||||
|
for _, excluded := range template.ExcludedIPRanges {
|
||||||
|
out.Excluded = append(out.Excluded, generalSubtree{IPAndMask: ipAndMask(excluded)})
|
||||||
|
}
|
||||||
|
for _, excluded := range template.ExcludedEmailAddresses {
|
||||||
|
out.Excluded = append(out.Excluded, generalSubtree{Email: excluded})
|
||||||
|
}
|
||||||
|
for _, excluded := range template.ExcludedURIDomains {
|
||||||
|
out.Excluded = append(out.Excluded, generalSubtree{URIDomain: excluded})
|
||||||
}
|
}
|
||||||
|
|
||||||
ret[n].Value, err = asn1.Marshal(out)
|
ret[n].Value, err = asn1.Marshal(out)
|
||||||
|
|
@ -1997,6 +2195,7 @@ type CertificateRequest struct {
|
||||||
DNSNames []string
|
DNSNames []string
|
||||||
EmailAddresses []string
|
EmailAddresses []string
|
||||||
IPAddresses []net.IP
|
IPAddresses []net.IP
|
||||||
|
URIs []*url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// These structures reflect the ASN.1 structure of X.509 certificate
|
// These structures reflect the ASN.1 structure of X.509 certificate
|
||||||
|
|
@ -2088,7 +2287,7 @@ func parseCSRExtensions(rawAttributes []asn1.RawValue) ([]pkix.Extension, error)
|
||||||
|
|
||||||
// CreateCertificateRequest creates a new certificate request based on a
|
// CreateCertificateRequest creates a new certificate request based on a
|
||||||
// template. The following members of template are used: Attributes, DNSNames,
|
// template. The following members of template are used: Attributes, DNSNames,
|
||||||
// EmailAddresses, ExtraExtensions, IPAddresses, SignatureAlgorithm, and
|
// EmailAddresses, ExtraExtensions, IPAddresses, URIs, SignatureAlgorithm, and
|
||||||
// Subject. The private key is the private key of the signer.
|
// Subject. The private key is the private key of the signer.
|
||||||
//
|
//
|
||||||
// The returned slice is the certificate request in DER encoding.
|
// The returned slice is the certificate request in DER encoding.
|
||||||
|
|
@ -2117,9 +2316,9 @@ func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv
|
||||||
|
|
||||||
var extensions []pkix.Extension
|
var extensions []pkix.Extension
|
||||||
|
|
||||||
if (len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0) &&
|
if (len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0 || len(template.URIs) > 0) &&
|
||||||
!oidInExtensions(oidExtensionSubjectAltName, template.ExtraExtensions) {
|
!oidInExtensions(oidExtensionSubjectAltName, template.ExtraExtensions) {
|
||||||
sanBytes, err := marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses)
|
sanBytes, err := marshalSANs(template.DNSNames, template.EmailAddresses, template.IPAddresses, template.URIs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -2294,7 +2493,7 @@ func parseCertificateRequest(in *certificateRequest) (*CertificateRequest, error
|
||||||
|
|
||||||
for _, extension := range out.Extensions {
|
for _, extension := range out.Extensions {
|
||||||
if extension.Id.Equal(oidExtensionSubjectAltName) {
|
if extension.Id.Equal(oidExtensionSubjectAltName) {
|
||||||
out.DNSNames, out.EmailAddresses, out.IPAddresses, err = parseSANExtension(extension.Value)
|
out.DNSNames, out.EmailAddresses, out.IPAddresses, out.URIs, err = parseSANExtension(extension.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"internal/testenv"
|
"internal/testenv"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
@ -352,6 +353,22 @@ var certBytes = "308203223082028ba00302010202106edf0d9499fd4533dd1297fc42a93be13
|
||||||
"9048084225c53e8acb7feb6f04d16dc574a2f7a27c7b603c77cd0ece48027f012fb69b37e02a2a" +
|
"9048084225c53e8acb7feb6f04d16dc574a2f7a27c7b603c77cd0ece48027f012fb69b37e02a2a" +
|
||||||
"36dcd585d6ace53f546f961e05af"
|
"36dcd585d6ace53f546f961e05af"
|
||||||
|
|
||||||
|
func parseCIDR(s string) *net.IPNet {
|
||||||
|
_, net, err := net.ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return net
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseURI(s string) *url.URL {
|
||||||
|
uri, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateSelfSignedCertificate(t *testing.T) {
|
func TestCreateSelfSignedCertificate(t *testing.T) {
|
||||||
random := rand.Reader
|
random := rand.Reader
|
||||||
|
|
||||||
|
|
@ -423,10 +440,17 @@ func TestCreateSelfSignedCertificate(t *testing.T) {
|
||||||
DNSNames: []string{"test.example.com"},
|
DNSNames: []string{"test.example.com"},
|
||||||
EmailAddresses: []string{"gopher@golang.org"},
|
EmailAddresses: []string{"gopher@golang.org"},
|
||||||
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")},
|
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")},
|
||||||
|
URIs: []*url.URL{parseURI("https://foo.com/wibble#foo")},
|
||||||
|
|
||||||
PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3}},
|
PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3}},
|
||||||
PermittedDNSDomains: []string{".example.com", "example.com"},
|
PermittedDNSDomains: []string{".example.com", "example.com"},
|
||||||
ExcludedDNSDomains: []string{"bar.example.com"},
|
ExcludedDNSDomains: []string{"bar.example.com"},
|
||||||
|
PermittedIPRanges: []*net.IPNet{parseCIDR("192.168.1.1/16"), parseCIDR("1.2.3.4/8")},
|
||||||
|
ExcludedIPRanges: []*net.IPNet{parseCIDR("2001:db8::/48")},
|
||||||
|
PermittedEmailAddresses: []string{"foo@example.com"},
|
||||||
|
ExcludedEmailAddresses: []string{".example.com", "example.com"},
|
||||||
|
PermittedURIDomains: []string{".bar.com", "bar.com"},
|
||||||
|
ExcludedURIDomains: []string{".bar2.com", "bar2.com"},
|
||||||
|
|
||||||
CRLDistributionPoints: []string{"http://crl1.example.com/ca1.crl", "http://crl2.example.com/ca1.crl"},
|
CRLDistributionPoints: []string{"http://crl1.example.com/ca1.crl", "http://crl2.example.com/ca1.crl"},
|
||||||
|
|
||||||
|
|
@ -468,6 +492,30 @@ func TestCreateSelfSignedCertificate(t *testing.T) {
|
||||||
t.Errorf("%s: failed to parse name constraint exclusions: %#v", test.name, cert.ExcludedDNSDomains)
|
t.Errorf("%s: failed to parse name constraint exclusions: %#v", test.name, cert.ExcludedDNSDomains)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cert.PermittedIPRanges) != 2 || cert.PermittedIPRanges[0].String() != "192.168.0.0/16" || cert.PermittedIPRanges[1].String() != "1.0.0.0/8" {
|
||||||
|
t.Errorf("%s: failed to parse IP constraints: %#v", test.name, cert.PermittedIPRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.ExcludedIPRanges) != 1 || cert.ExcludedIPRanges[0].String() != "2001:db8::/48" {
|
||||||
|
t.Errorf("%s: failed to parse IP constraint exclusions: %#v", test.name, cert.ExcludedIPRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.PermittedEmailAddresses) != 1 || cert.PermittedEmailAddresses[0] != "foo@example.com" {
|
||||||
|
t.Errorf("%s: failed to parse permitted email addreses: %#v", test.name, cert.PermittedEmailAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.ExcludedEmailAddresses) != 2 || cert.ExcludedEmailAddresses[0] != ".example.com" || cert.ExcludedEmailAddresses[1] != "example.com" {
|
||||||
|
t.Errorf("%s: failed to parse excluded email addreses: %#v", test.name, cert.ExcludedEmailAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.PermittedURIDomains) != 2 || cert.PermittedURIDomains[0] != ".bar.com" || cert.PermittedURIDomains[1] != "bar.com" {
|
||||||
|
t.Errorf("%s: failed to parse permitted URIs: %#v", test.name, cert.PermittedURIDomains)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cert.ExcludedURIDomains) != 2 || cert.ExcludedURIDomains[0] != ".bar2.com" || cert.ExcludedURIDomains[1] != "bar2.com" {
|
||||||
|
t.Errorf("%s: failed to parse excluded URIs: %#v", test.name, cert.ExcludedURIDomains)
|
||||||
|
}
|
||||||
|
|
||||||
if cert.Subject.CommonName != commonName {
|
if cert.Subject.CommonName != commonName {
|
||||||
t.Errorf("%s: subject wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Subject.CommonName, commonName)
|
t.Errorf("%s: subject wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Subject.CommonName, commonName)
|
||||||
}
|
}
|
||||||
|
|
@ -519,6 +567,10 @@ func TestCreateSelfSignedCertificate(t *testing.T) {
|
||||||
t.Errorf("%s: SAN emails differ from template. Got %v, want %v", test.name, cert.EmailAddresses, template.EmailAddresses)
|
t.Errorf("%s: SAN emails differ from template. Got %v, want %v", test.name, cert.EmailAddresses, template.EmailAddresses)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cert.URIs) != 1 || cert.URIs[0].String() != "https://foo.com/wibble#foo" {
|
||||||
|
t.Errorf("%s: URIs differ from template. Got %v, want %v", test.name, cert.URIs, template.URIs)
|
||||||
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(cert.IPAddresses, template.IPAddresses) {
|
if !reflect.DeepEqual(cert.IPAddresses, template.IPAddresses) {
|
||||||
t.Errorf("%s: SAN IPs differ from template. Got %v, want %v", test.name, cert.IPAddresses, template.IPAddresses)
|
t.Errorf("%s: SAN IPs differ from template. Got %v, want %v", test.name, cert.IPAddresses, template.IPAddresses)
|
||||||
}
|
}
|
||||||
|
|
@ -1012,7 +1064,7 @@ func marshalAndParseCSR(t *testing.T, template *CertificateRequest) *Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCertificateRequestOverrides(t *testing.T) {
|
func TestCertificateRequestOverrides(t *testing.T) {
|
||||||
sanContents, err := marshalSANs([]string{"foo.example.com"}, nil, nil)
|
sanContents, err := marshalSANs([]string{"foo.example.com"}, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -1069,7 +1121,7 @@ func TestCertificateRequestOverrides(t *testing.T) {
|
||||||
t.Errorf("bad attributes: %#v\n", csr.Attributes)
|
t.Errorf("bad attributes: %#v\n", csr.Attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
sanContents2, err := marshalSANs([]string{"foo2.example.com"}, nil, nil)
|
sanContents2, err := marshalSANs([]string{"foo2.example.com"}, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -1567,3 +1619,69 @@ func TestRDNSequenceString(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const criticalNameConstraintWithUnknownTypePEM = `
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC/TCCAeWgAwIBAgICEjQwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UEAxMdRW1w
|
||||||
|
dHkgbmFtZSBjb25zdHJhaW50cyBpc3N1ZXIwHhcNMTMwMjAxMDAwMDAwWhcNMjAw
|
||||||
|
NTMwMTA0ODM4WjAhMR8wHQYDVQQDExZFbXB0eSBuYW1lIGNvbnN0cmFpbnRzMIIB
|
||||||
|
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwriElUIt3LCqmJObs+yDoWPD
|
||||||
|
F5IqgWk6moIobYjPfextZiYU6I3EfvAwoNxPDkN2WowcocUZMJbEeEq5ebBksFnx
|
||||||
|
f12gBxlIViIYwZAzu7aFvhDMyPKQI3C8CG0ZSC9ABZ1E3umdA3CEueNOmP/TChNq
|
||||||
|
Cl23+BG1Qb/PJkpAO+GfpWSVhTcV53Mf/cKvFHcjGNrxzdSoq9fyW7a6gfcGEQY0
|
||||||
|
LVkmwFWUfJ0wT8kaeLr0E0tozkIfo01KNWNzv6NcYP80QOBRDlApWu9ODmEVJHPD
|
||||||
|
blx4jzTQ3JLa+4DvBNOjVUOp+mgRmjiW0rLdrxwOxIqIOwNjweMCp/hgxX/hTQID
|
||||||
|
AQABozgwNjA0BgNVHR4BAf8EKjAooCQwIokgIACrzQAAAAAAAAAAAAAAAP////8A
|
||||||
|
AAAAAAAAAAAAAAChADANBgkqhkiG9w0BAQsFAAOCAQEAWG+/zUMHQhP8uNCtgSHy
|
||||||
|
im/vh7wminwAvWgMKxlkLBFns6nZeQqsOV1lABY7U0Zuoqa1Z5nb6L+iJa4ElREJ
|
||||||
|
Oi/erLc9uLwBdDCAR0hUTKD7a6i4ooS39DTle87cUnj0MW1CUa6Hv5SsvpYW+1Xl
|
||||||
|
eYJk/axQOOTcy4Es53dvnZsjXH0EA/QHnn7UV+JmlE3rtVxcYp6MLYPmRhTioROA
|
||||||
|
/drghicRkiu9hxdPyxkYS16M5g3Zj30jdm+k/6C6PeNtN9YmOOganCOSyFYfGhqO
|
||||||
|
ANYzpmuV+oIedAsPpIbfIzN8njYUs1zio+1IoI4o8ddM9sCbtPU8o+WoY6IsCKXV
|
||||||
|
/g==
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
func TestCriticalNameConstraintWithUnknownType(t *testing.T) {
|
||||||
|
block, _ := pem.Decode([]byte(criticalNameConstraintWithUnknownTypePEM))
|
||||||
|
cert, err := ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected parsing failure: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(cert.UnhandledCriticalExtensions); l != 1 {
|
||||||
|
t.Fatalf("expected one unhandled critical extension, but found %d", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const badIPMaskPEM = `
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICzzCCAbegAwIBAgICEjQwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UEAxMSQmFk
|
||||||
|
IElQIG1hc2sgaXNzdWVyMB4XDTEzMDIwMTAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
|
||||||
|
FjEUMBIGA1UEAxMLQmFkIElQIG1hc2swggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||||
|
ggEKAoIBAQDCuISVQi3csKqYk5uz7IOhY8MXkiqBaTqagihtiM997G1mJhTojcR+
|
||||||
|
8DCg3E8OQ3ZajByhxRkwlsR4Srl5sGSwWfF/XaAHGUhWIhjBkDO7toW+EMzI8pAj
|
||||||
|
cLwIbRlIL0AFnUTe6Z0DcIS5406Y/9MKE2oKXbf4EbVBv88mSkA74Z+lZJWFNxXn
|
||||||
|
cx/9wq8UdyMY2vHN1Kir1/JbtrqB9wYRBjQtWSbAVZR8nTBPyRp4uvQTS2jOQh+j
|
||||||
|
TUo1Y3O/o1xg/zRA4FEOUCla704OYRUkc8NuXHiPNNDcktr7gO8E06NVQ6n6aBGa
|
||||||
|
OJbSst2vHA7Eiog7A2PB4wKn+GDFf+FNAgMBAAGjIDAeMBwGA1UdHgEB/wQSMBCg
|
||||||
|
DDAKhwgBAgME//8BAKEAMA0GCSqGSIb3DQEBCwUAA4IBAQBYb7/NQwdCE/y40K2B
|
||||||
|
IfKKb++HvCaKfAC9aAwrGWQsEWezqdl5Cqw5XWUAFjtTRm6iprVnmdvov6IlrgSV
|
||||||
|
EQk6L96stz24vAF0MIBHSFRMoPtrqLiihLf0NOV7ztxSePQxbUJRroe/lKy+lhb7
|
||||||
|
VeV5gmT9rFA45NzLgSznd2+dmyNcfQQD9AeeftRX4maUTeu1XFxinowtg+ZGFOKh
|
||||||
|
E4D92uCGJxGSK72HF0/LGRhLXozmDdmPfSN2b6T/oLo942031iY46BqcI5LIVh8a
|
||||||
|
Go4A1jOma5X6gh50Cw+kht8jM3yeNhSzXOKj7Uigjijx10z2wJu09Tyj5ahjoiwI
|
||||||
|
pdX+
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
func TestBadIPMask(t *testing.T) {
|
||||||
|
block, _ := pem.Decode([]byte(badIPMaskPEM))
|
||||||
|
_, err := ParseCertificate(block.Bytes)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("unexpected success")
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = "contained invalid mask"
|
||||||
|
if !strings.Contains(err.Error(), expected) {
|
||||||
|
t.Fatalf("expected %q in error but got: %s", expected, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -377,7 +377,7 @@ var pkgDeps = map[string][]string{
|
||||||
},
|
},
|
||||||
"crypto/x509": {
|
"crypto/x509": {
|
||||||
"L4", "CRYPTO-MATH", "OS", "CGO",
|
"L4", "CRYPTO-MATH", "OS", "CGO",
|
||||||
"crypto/x509/pkix", "encoding/pem", "encoding/hex", "net", "os/user", "syscall",
|
"crypto/x509/pkix", "encoding/pem", "encoding/hex", "net", "os/user", "syscall", "net/url",
|
||||||
},
|
},
|
||||||
"crypto/x509/pkix": {"L4", "CRYPTO-MATH", "encoding/hex"},
|
"crypto/x509/pkix": {"L4", "CRYPTO-MATH", "encoding/hex"},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue