From 4f9ec2c7568fce3db51cee0bf151ccf8534868aa Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Wed, 11 Jul 2018 15:59:56 -0400 Subject: [PATCH] crypto/x509: ignore Common Name when it does not parse as a hostname The Common Name is used as a hostname when there are no Subject Alternative Names, but it is not restricted by name constraints. To protect against a name constraints bypass, we used to require SANs for constrained chains. See the NameConstraintsWithoutSANs error. This change ignores the CN when it does not look like a hostname, so we can avoid returning NameConstraintsWithoutSANs. This makes it possible to validate certificates with non-hostname CN against chains that use name constraints to disallow all names, like the Estonian IDs. Updates #24151 Change-Id: I798d797990720a01ad9b5a13336756cc472ebf44 Reviewed-on: https://go-review.googlesource.com/123355 Reviewed-by: Adam Langley --- src/crypto/x509/name_constraints_test.go | 72 ++++++++- src/crypto/x509/verify.go | 117 ++++++++++---- src/crypto/x509/verify_test.go | 192 ++++++++++++++++++++--- src/crypto/x509/x509.go | 17 +- 4 files changed, 324 insertions(+), 74 deletions(-) diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index 95d55fd761..e356fc5cb9 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -57,6 +57,7 @@ type constraintsSpec struct { type leafSpec struct { sans []string ekus []string + cn string } var nameConstraintsTests = []nameConstraintsTest{ @@ -633,7 +634,7 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, - // #30: without SANs, a certificate is rejected in a constrained chain. + // #30: without SANs, a certificate with a CN is rejected in a constrained chain. nameConstraintsTest{ roots: []constraintsSpec{ constraintsSpec{ @@ -647,9 +648,9 @@ var nameConstraintsTests = []nameConstraintsTest{ }, leaf: leafSpec{ sans: []string{}, + cn: "foo.com", }, expectedError: "leaf doesn't have a SAN extension", - noOpenSSL: true, // OpenSSL doesn't require SANs in this case. }, // #31: IPv6 addresses work in constraints: roots can permit them as @@ -1580,6 +1581,60 @@ var nameConstraintsTests = []nameConstraintsTest{ ekus: []string{"email", "serverAuth"}, }, }, + + // #82: a certificate without SANs and CN is accepted in a constrained chain. + nameConstraintsTest{ + roots: []constraintsSpec{ + constraintsSpec{ + ok: []string{"dns:foo.com", "dns:.foo.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + []constraintsSpec{ + constraintsSpec{}, + }, + }, + leaf: leafSpec{ + sans: []string{}, + }, + }, + + // #83: a certificate without SANs and with a CN that does not parse as a + // hostname is accepted in a constrained chain. + nameConstraintsTest{ + roots: []constraintsSpec{ + constraintsSpec{ + ok: []string{"dns:foo.com", "dns:.foo.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + []constraintsSpec{ + constraintsSpec{}, + }, + }, + leaf: leafSpec{ + sans: []string{}, + cn: "foo,bar", + }, + }, + + // #84: a certificate with SANs and CN is accepted in a constrained chain. + nameConstraintsTest{ + roots: []constraintsSpec{ + constraintsSpec{ + ok: []string{"dns:foo.com", "dns:.foo.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + []constraintsSpec{ + constraintsSpec{}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:foo.com"}, + cn: "foo.bar", + }, + }, } func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { @@ -1625,9 +1680,8 @@ func makeConstraintsLeafCert(leaf leafSpec, key *ecdsa.PrivateKey, parent *Certi template := &Certificate{ SerialNumber: new(big.Int).SetBytes(serialBytes[:]), Subject: pkix.Name{ - // Don't set a CommonName because OpenSSL (at least) will try to - // match it against name constraints. OrganizationalUnit: []string{"Leaf"}, + CommonName: leaf.cn, }, NotBefore: time.Unix(1000, 0), NotAfter: time.Unix(2000, 0), @@ -1899,7 +1953,9 @@ func TestConstraintCases(t *testing.T) { t.Fatalf("#%d: cannot create leaf: %s", i, err) } - if !test.noOpenSSL && testNameConstraintsAgainstOpenSSL { + // Skip tests with CommonName set because OpenSSL will try to match it + // against name constraints, while we ignore it when it's not hostname-looking. + if !test.noOpenSSL && testNameConstraintsAgainstOpenSSL && test.leaf.cn == "" { output, err := testChainAgainstOpenSSL(leafCert, intermediatePool, rootPool) if err == nil && len(test.expectedError) > 0 { t.Errorf("#%d: unexpectedly succeeded against OpenSSL", i) @@ -1912,7 +1968,7 @@ func TestConstraintCases(t *testing.T) { if _, ok := err.(*exec.ExitError); !ok { t.Errorf("#%d: OpenSSL failed to run: %s", i, err) } else if len(test.expectedError) == 0 { - t.Errorf("#%d: OpenSSL unexpectedly failed: %q", i, output) + t.Errorf("#%d: OpenSSL unexpectedly failed: %v", i, output) if debugOpenSSLFailure { return } @@ -1949,7 +2005,7 @@ func TestConstraintCases(t *testing.T) { certAsPEM := func(cert *Certificate) string { var buf bytes.Buffer pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) - return string(buf.Bytes()) + return buf.String() } t.Errorf("#%d: root:\n%s", i, certAsPEM(rootPool.certs[0])) t.Errorf("#%d: leaf:\n%s", i, certAsPEM(leafCert)) @@ -2012,7 +2068,7 @@ func testChainAgainstOpenSSL(leaf *Certificate, intermediates, roots *CertPool) cmd.Stderr = &output err := cmd.Run() - return string(output.Bytes()), err + return output.String(), err } var rfc2821Tests = []struct { diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 1ef49c0f4a..4326e39f1c 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -6,14 +6,12 @@ package x509 import ( "bytes" - "encoding/asn1" "errors" "fmt" "net" "net/url" "reflect" "runtime" - "strconv" "strings" "time" "unicode/utf8" @@ -43,7 +41,8 @@ const ( NameMismatch // NameConstraintsWithoutSANs results when a leaf certificate doesn't // contain a Subject Alternative Name extension, but a CA certificate - // contains name constraints. + // contains name constraints, and the Common Name can be interpreted as + // a hostname. NameConstraintsWithoutSANs // UnconstrainedName results when a CA certificate contains permitted // name constraints, but leaf certificate contains a name of an @@ -102,6 +101,12 @@ type HostnameError struct { 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 + } + var valid string if ip := net.ParseIP(h.Host); ip != nil { // Trying to validate an IP @@ -115,10 +120,10 @@ func (h HostnameError) Error() string { valid += san.String() } } else { - if c.hasSANExtension() { - valid = strings.Join(c.DNSNames, ", ") - } else { + if c.commonNameAsHostname() { valid = c.Subject.CommonName + } else { + valid = strings.Join(c.DNSNames, ", ") } } @@ -583,17 +588,16 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V leaf = currentChain[0] } - if (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() { - sanExtension, ok := leaf.getSANExtension() - if !ok { - // This is the deprecated, legacy case of depending on - // the CN as a hostname. Chains modern enough to be - // using name constraints should not be depending on - // CNs. - return CertificateInvalidError{c, NameConstraintsWithoutSANs, ""} - } - - err := forEachSAN(sanExtension, func(tag int, data []byte) error { + 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) @@ -692,18 +696,6 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V return nil } -// formatOID formats an ASN.1 OBJECT IDENTIFER in the common, dotted style. -func formatOID(oid asn1.ObjectIdentifier) string { - ret := "" - for i, v := range oid { - if i > 0 { - ret += "." - } - ret += strconv.Itoa(v) - } - return ret -} - // Verify attempts to verify c by building one or more chains from c to a // certificate in opts.Roots, using certificates in opts.Intermediates if // needed. If successful, it returns one or more chains where the first @@ -860,6 +852,64 @@ nextIntermediate: return } +// validHostname returns whether host is a valid hostname that can be matched or +// matched against according to RFC 6125 2.2, with some leniency to accomodate +// legacy values. +func validHostname(host string) bool { + host = strings.TrimSuffix(host, ".") + + if len(host) == 0 { + return false + } + + for i, part := range strings.Split(host, ".") { + if part == "" { + // Empty label. + return false + } + if i == 0 && part == "*" { + // Only allow full left-most wildcards, as those are the only ones + // we match, and matching literal '*' characters is probably never + // the expected behavior. + continue + } + for j, c := range part { + if 'a' <= c && c <= 'z' { + continue + } + if '0' <= c && c <= '9' { + continue + } + if 'A' <= c && c <= 'Z' { + continue + } + if c == '-' && j != 0 { + continue + } + if c == '_' { + // _ is not a valid character in hostnames, but it's commonly + // found in deployments outside the WebPKI. + continue + } + return false + } + } + + 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 !c.hasSANExtension() && validHostname(c.Subject.CommonName) +} + func matchHostnames(pattern, host string) bool { host = strings.TrimSuffix(host, ".") pattern = strings.TrimSuffix(pattern, ".") @@ -940,15 +990,16 @@ func (c *Certificate) VerifyHostname(h string) error { lowered := toLowerCaseASCII(h) - if c.hasSANExtension() { + if c.commonNameAsHostname() { + if matchHostnames(toLowerCaseASCII(c.Subject.CommonName), lowered) { + return nil + } + } else { for _, match := range c.DNSNames { if matchHostnames(toLowerCaseASCII(match), lowered) { return nil } } - // If Subject Alt Name is given, we ignore the common name. - } else if matchHostnames(toLowerCaseASCII(c.Subject.CommonName), lowered) { - return nil } return HostnameError{c, h} diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 95034dbba3..c677c03141 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -71,7 +71,16 @@ var verifyTests = []verifyTest{ currentTime: 1395785200, dnsName: "www.example.com", - errorCallback: expectHostnameError, + errorCallback: expectHostnameError("certificate is valid for"), + }, + { + leaf: googleLeaf, + intermediates: []string{giag2Intermediate}, + roots: []string{geoTrustRoot}, + currentTime: 1395785200, + dnsName: "1.2.3.4", + + errorCallback: expectHostnameError("doesn't contain any IP SANs"), }, { leaf: googleLeaf, @@ -248,7 +257,7 @@ var verifyTests = []verifyTest{ dnsName: "notfoo.example", systemSkip: true, - errorCallback: expectHostnameError, + errorCallback: expectHostnameError("certificate is valid for"), }, { // The issuer name in the leaf doesn't exactly match the @@ -281,7 +290,7 @@ var verifyTests = []verifyTest{ currentTime: 1486684488, systemSkip: true, - errorCallback: expectHostnameError, + errorCallback: expectHostnameError("certificate is not valid for any names"), }, { // Test that excluded names are respected. @@ -318,19 +327,46 @@ var verifyTests = []verifyTest{ errorCallback: expectUnhandledCriticalExtension, }, + { + // Test that invalid CN are ignored. + leaf: invalidCNWithoutSAN, + dnsName: "foo,invalid", + roots: []string{invalidCNRoot}, + currentTime: 1540000000, + systemSkip: true, + + errorCallback: expectHostnameError("Common Name is not a valid hostname"), + }, + { + // Test that valid CN are respected. + leaf: validCNWithoutSAN, + dnsName: "foo.example.com", + roots: []string{invalidCNRoot}, + currentTime: 1540000000, + systemSkip: true, + + expectedChains: [][]string{ + {"foo.example.com", "Test root"}, + }, + }, } -func expectHostnameError(t *testing.T, i int, err error) (ok bool) { - if _, ok := err.(HostnameError); !ok { - t.Errorf("#%d: error was not a HostnameError: %s", i, err) - return false +func expectHostnameError(msg string) func(*testing.T, int, error) bool { + return func(t *testing.T, i int, err error) (ok bool) { + if _, ok := err.(HostnameError); !ok { + t.Errorf("#%d: error was not a HostnameError: %v", i, err) + return false + } + if !strings.Contains(err.Error(), msg) { + t.Errorf("#%d: HostnameError did not contain %q: %v", i, msg, err) + } + return true } - return true } func expectExpired(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != Expired { - t.Errorf("#%d: error was not Expired: %s", i, err) + t.Errorf("#%d: error was not Expired: %v", i, err) return false } return true @@ -338,7 +374,7 @@ func expectExpired(t *testing.T, i int, err error) (ok bool) { func expectUsageError(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != IncompatibleUsage { - t.Errorf("#%d: error was not IncompatibleUsage: %s", i, err) + t.Errorf("#%d: error was not IncompatibleUsage: %v", i, err) return false } return true @@ -347,11 +383,11 @@ func expectUsageError(t *testing.T, i int, err error) (ok bool) { func expectAuthorityUnknown(t *testing.T, i int, err error) (ok bool) { e, ok := err.(UnknownAuthorityError) if !ok { - t.Errorf("#%d: error was not UnknownAuthorityError: %s", i, err) + t.Errorf("#%d: error was not UnknownAuthorityError: %v", i, err) return false } if e.Cert == nil { - t.Errorf("#%d: error was UnknownAuthorityError, but missing Cert: %s", i, err) + t.Errorf("#%d: error was UnknownAuthorityError, but missing Cert: %v", i, err) return false } return true @@ -359,7 +395,7 @@ func expectAuthorityUnknown(t *testing.T, i int, err error) (ok bool) { func expectSystemRootsError(t *testing.T, i int, err error) bool { if _, ok := err.(SystemRootsError); !ok { - t.Errorf("#%d: error was not SystemRootsError: %s", i, err) + t.Errorf("#%d: error was not SystemRootsError: %v", i, err) return false } return true @@ -371,7 +407,7 @@ func expectHashError(t *testing.T, i int, err error) bool { return false } if expected := "algorithm unimplemented"; !strings.Contains(err.Error(), expected) { - t.Errorf("#%d: error resulting from invalid hash didn't contain '%s', rather it was: %s", i, expected, err) + t.Errorf("#%d: error resulting from invalid hash didn't contain '%s', rather it was: %v", i, expected, err) return false } return true @@ -379,7 +415,7 @@ func expectHashError(t *testing.T, i int, err error) bool { func expectSubjectIssuerMismatcthError(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != NameMismatch { - t.Errorf("#%d: error was not a NameMismatch: %s", i, err) + t.Errorf("#%d: error was not a NameMismatch: %v", i, err) return false } return true @@ -387,7 +423,7 @@ func expectSubjectIssuerMismatcthError(t *testing.T, i int, err error) (ok bool) func expectNameConstraintsError(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != CANotAuthorizedForThisName { - t.Errorf("#%d: error was not a CANotAuthorizedForThisName: %s", i, err) + t.Errorf("#%d: error was not a CANotAuthorizedForThisName: %v", i, err) return false } return true @@ -395,7 +431,7 @@ func expectNameConstraintsError(t *testing.T, i int, err error) (ok bool) { func expectNotAuthorizedError(t *testing.T, i int, err error) (ok bool) { if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != NotAuthorizedToSign { - t.Errorf("#%d: error was not a NotAuthorizedToSign: %s", i, err) + t.Errorf("#%d: error was not a NotAuthorizedToSign: %v", i, err) return false } return true @@ -403,7 +439,7 @@ func expectNotAuthorizedError(t *testing.T, i int, err error) (ok bool) { func expectUnhandledCriticalExtension(t *testing.T, i int, err error) (ok bool) { if _, ok := err.(UnhandledCriticalExtension); !ok { - t.Errorf("#%d: error was not an UnhandledCriticalExtension: %s", i, err) + t.Errorf("#%d: error was not an UnhandledCriticalExtension: %v", i, err) return false } return true @@ -454,7 +490,7 @@ func testVerify(t *testing.T, useSystemRoots bool) { leaf, err := certificateFromPEM(test.leaf) if err != nil { - t.Errorf("#%d: failed to parse leaf: %s", i, err) + t.Errorf("#%d: failed to parse leaf: %v", i, err) return } @@ -472,7 +508,7 @@ func testVerify(t *testing.T, useSystemRoots bool) { } if test.errorCallback == nil && err != nil { - t.Errorf("#%d: unexpected error: %s", i, err) + t.Errorf("#%d: unexpected error: %v", i, err) } if test.errorCallback != nil { if !test.errorCallback(t, i, err) { @@ -1513,6 +1549,95 @@ yU1yRHUqUYpN0DWFpsPbBqgM6uUAVO2ayBFhPgWUaqkmSbZ/Nq7isGvknaTmcIwT +NQCZDd5eFeU8PpNX7rgaYE4GPq+EEmLVCBYmdctr8QVdqJ//8Xu3+1phjDy -----END CERTIFICATE-----` +const invalidCNRoot = ` +-----BEGIN CERTIFICATE----- +MIIBFjCBvgIJAIsu4r+jb70UMAoGCCqGSM49BAMCMBQxEjAQBgNVBAsMCVRlc3Qg +cm9vdDAeFw0xODA3MTExODMyMzVaFw0yODA3MDgxODMyMzVaMBQxEjAQBgNVBAsM +CVRlc3Qgcm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABF6oDgMg0LV6YhPj +QXaPXYCc2cIyCdqp0ROUksRz0pOLTc5iY2nraUheRUD1vRRneq7GeXOVNn7uXONg +oCGMjNwwCgYIKoZIzj0EAwIDRwAwRAIgDSiwgIn8g1lpruYH0QD1GYeoWVunfmrI +XzZZl0eW/ugCICgOfXeZ2GGy3wIC0352BaC3a8r5AAb2XSGNe+e9wNN6 +-----END CERTIFICATE----- +` + +const invalidCNWithoutSAN = ` +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 07:ba:bc:b7:d9:ab:0c:02:fe:50:1d:4e:15:a3:0d:e4:11:16:14:a2 + Signature Algorithm: ecdsa-with-SHA256 + Issuer: OU = Test root + Validity + Not Before: Jul 11 18:35:21 2018 GMT + Not After : Jul 8 18:35:21 2028 GMT + Subject: CN = "foo,invalid" + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:a7:a6:7c:22:33:a7:47:7f:08:93:2d:5f:61:35: + 2e:da:45:67:76:f2:97:73:18:b0:01:12:4a:1a:d5: + b7:6f:41:3c:bb:05:69:f4:06:5d:ff:eb:2b:a7:85: + 0b:4c:f7:45:4e:81:40:7a:a9:c6:1d:bb:ba:d9:b9: + 26:b3:ca:50:90 + ASN1 OID: prime256v1 + NIST CURVE: P-256 + Signature Algorithm: ecdsa-with-SHA256 + 30:45:02:21:00:85:96:75:b6:72:3c:67:12:a0:7f:86:04:81: + d2:dd:c8:67:50:d7:5f:85:c0:54:54:fc:e6:6b:45:08:93:d3: + 2a:02:20:60:86:3e:d6:28:a6:4e:da:dd:6e:95:89:cc:00:76: + 78:1c:03:80:85:a6:5a:0b:eb:c5:f3:9c:2e:df:ef:6e:fa +-----BEGIN CERTIFICATE----- +MIIBJDCBywIUB7q8t9mrDAL+UB1OFaMN5BEWFKIwCgYIKoZIzj0EAwIwFDESMBAG +A1UECwwJVGVzdCByb290MB4XDTE4MDcxMTE4MzUyMVoXDTI4MDcwODE4MzUyMVow +FjEUMBIGA1UEAwwLZm9vLGludmFsaWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AASnpnwiM6dHfwiTLV9hNS7aRWd28pdzGLABEkoa1bdvQTy7BWn0Bl3/6yunhQtM +90VOgUB6qcYdu7rZuSazylCQMAoGCCqGSM49BAMCA0gAMEUCIQCFlnW2cjxnEqB/ +hgSB0t3IZ1DXX4XAVFT85mtFCJPTKgIgYIY+1iimTtrdbpWJzAB2eBwDgIWmWgvr +xfOcLt/vbvo= +-----END CERTIFICATE----- +` + +const validCNWithoutSAN = ` +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 07:ba:bc:b7:d9:ab:0c:02:fe:50:1d:4e:15:a3:0d:e4:11:16:14:a4 + Signature Algorithm: ecdsa-with-SHA256 + Issuer: OU = Test root + Validity + Not Before: Jul 11 18:47:24 2018 GMT + Not After : Jul 8 18:47:24 2028 GMT + Subject: CN = foo.example.com + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:a7:a6:7c:22:33:a7:47:7f:08:93:2d:5f:61:35: + 2e:da:45:67:76:f2:97:73:18:b0:01:12:4a:1a:d5: + b7:6f:41:3c:bb:05:69:f4:06:5d:ff:eb:2b:a7:85: + 0b:4c:f7:45:4e:81:40:7a:a9:c6:1d:bb:ba:d9:b9: + 26:b3:ca:50:90 + ASN1 OID: prime256v1 + NIST CURVE: P-256 + Signature Algorithm: ecdsa-with-SHA256 + 30:44:02:20:53:6c:d7:b7:59:61:51:72:a5:18:a3:4b:0d:52: + ea:15:fa:d0:93:30:32:54:4b:ed:0f:58:85:b8:a8:1a:82:3b: + 02:20:14:77:4b:0e:7e:4f:0a:4f:64:26:97:dc:d0:ed:aa:67: + 1d:37:85:da:b4:87:ba:25:1c:2a:58:f7:23:11:8b:3d +-----BEGIN CERTIFICATE----- +MIIBJzCBzwIUB7q8t9mrDAL+UB1OFaMN5BEWFKQwCgYIKoZIzj0EAwIwFDESMBAG +A1UECwwJVGVzdCByb290MB4XDTE4MDcxMTE4NDcyNFoXDTI4MDcwODE4NDcyNFow +GjEYMBYGA1UEAwwPZm9vLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEp6Z8IjOnR38Iky1fYTUu2kVndvKXcxiwARJKGtW3b0E8uwVp9AZd/+sr +p4ULTPdFToFAeqnGHbu62bkms8pQkDAKBggqhkjOPQQDAgNHADBEAiBTbNe3WWFR +cqUYo0sNUuoV+tCTMDJUS+0PWIW4qBqCOwIgFHdLDn5PCk9kJpfc0O2qZx03hdq0 +h7olHCpY9yMRiz0= +-----END CERTIFICATE----- +` + var unknownAuthorityErrorTests = []struct { cert string expected string @@ -1530,7 +1655,7 @@ func TestUnknownAuthorityError(t *testing.T) { } c, err := ParseCertificate(der.Bytes) if err != nil { - t.Errorf("#%d: Unable to parse certificate -> %s", i, err) + t.Errorf("#%d: Unable to parse certificate -> %v", i, err) } uae := &UnknownAuthorityError{ Cert: c, @@ -1702,3 +1827,28 @@ UNhY4JhezH9gQYqvDMWrWDAbBgNVHSMEFDASgBArF29S5Bnqw7de8GzGA1nfMAoG CCqGSM49BAMCA0gAMEUCIQClA3d4tdrDu9Eb5ZBpgyC+fU1xTZB0dKQHz6M5fPZA 2AIgN96lM+CPGicwhN24uQI6flOsO3H0TJ5lNzBYLtnQtlc= -----END CERTIFICATE-----` + +func TestValidHostname(t *testing.T) { + tests := []struct { + host string + want bool + }{ + {"example.com", true}, + {"eXample123-.com", true}, + {"-eXample123-.com", false}, + {"", false}, + {".", false}, + {"example..com", false}, + {".example.com", false}, + {"*.example.com", true}, + {"*foo.example.com", false}, + {"foo.*.example.com", false}, + {"exa_mple.com", true}, + {"foo,bar", false}, + } + for _, tt := range tests { + if got := validHostname(tt.host); got != tt.want { + t.Errorf("validHostname(%q) = %v, want %v", tt.host, got, tt.want) + } + } +} diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 34eb8b0105..2e72471de2 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -843,23 +843,16 @@ func (c *Certificate) CheckSignature(algo SignatureAlgorithm, signed, signature } 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 + return oidInExtensions(oidExtensionNameConstraints, c.Extensions) } -func (c *Certificate) getSANExtension() ([]byte, bool) { +func (c *Certificate) getSANExtension() []byte { 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 + if e.Id.Equal(oidExtensionSubjectAltName) { + return e.Value } } - - return nil, false + return nil } func signaturePublicKeyAlgoMismatchError(expectedPubKeyAlgo PublicKeyAlgorithm, pubKey interface{}) error { -- 2.50.0