]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/x509: support excluded domains in name constraints.
authorAdam Langley <agl@golang.org>
Thu, 9 Feb 2017 00:27:00 +0000 (16:27 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Tue, 13 Jun 2017 18:33:29 +0000 (18:33 +0000)
Change-Id: I4c2c82cb0354f843a3283a650ed2cd2b6aef5895
Reviewed-on: https://go-review.googlesource.com/36900
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Adam Langley <agl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/crypto/x509/verify.go
src/crypto/x509/verify_test.go
src/crypto/x509/x509.go
src/crypto/x509/x509_test.go

index 021fe5e644717569023b1e6a2718af404e4c2b4c..2b4f39d62ea3c889cfb02428b2d341e6b8c59e4a 100644 (file)
@@ -166,7 +166,7 @@ const (
 
 func matchNameConstraint(domain, constraint string) bool {
        // The meaning of zero length constraints is not specified, but this
-       // code follows NSS and accepts them as valid for everything.
+       // code follows NSS and accepts them as matching everything.
        if len(constraint) == 0 {
                return true
        }
@@ -220,6 +220,12 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
                }
        }
 
+       for _, constraint := range c.ExcludedDNSDomains {
+               if matchNameConstraint(opts.DNSName, constraint) {
+                       return CertificateInvalidError{c, CANotAuthorizedForThisName}
+               }
+       }
+
        // KeyUsage status flags are ignored. From Engineering Security, Peter
        // Gutmann: A European government CA marked its signing certificates as
        // being valid for encryption only, but no-one noticed. Another
index 56c365e1e8c072a8b37462f37c4d0e0d44b93a55..335c477d0da598155148b7658759aa57de2be830 100644 (file)
@@ -285,6 +285,17 @@ var verifyTests = []verifyTest{
 
                errorCallback: expectHostnameError,
        },
+       {
+               // Test that excluded names are respected.
+               leaf:          excludedNamesLeaf,
+               dnsName:       "bender.local",
+               intermediates: []string{excludedNamesIntermediate},
+               roots:         []string{excludedNamesRoot},
+               currentTime:   1486684488,
+               systemSkip:    true,
+
+               errorCallback: expectNameConstraintsError,
+       },
 }
 
 func expectHostnameError(t *testing.T, i int, err error) (ok bool) {
@@ -352,6 +363,14 @@ func expectSubjectIssuerMismatcthError(t *testing.T, i int, err error) (ok bool)
        return true
 }
 
+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)
+               return false
+       }
+       return true
+}
+
 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)
@@ -1390,6 +1409,83 @@ j2kBQyvnyKsXHLAKUoUOpd6t/1PHrfXnGj+HmzZNloJ/BZ1kiWb4eLvMljoLGkZn
 xZbqP3Krgjj4XNaXjg==
 -----END CERTIFICATE-----`
 
+const excludedNamesLeaf = `
+-----BEGIN CERTIFICATE-----
+MIID4DCCAsigAwIBAgIHDUSFtJknhzANBgkqhkiG9w0BAQsFADCBnjELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU
+MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5
+ICgzNzM0NTE1NTYyODA2Mzk3KTEhMB8GA1UEAwwYSW50ZXJtZWRpYXRlIENBIGZv
+ciAzMzkyMB4XDTE3MDIwODIxMTUwNFoXDTE4MDIwODIwMjQ1OFowgZAxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlMb3MgR2F0b3Mx
+FDASBgNVBAoMC05ldGZsaXggSW5jMS0wKwYDVQQLDCRQbGF0Zm9ybSBTZWN1cml0
+eSAoMzczNDUxNTc0ODUwMjY5NikxEzARBgNVBAMMCjE3Mi4xNi4wLjEwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCZ0oP1bMv6bOeqcKbzinnGpNOpenhA
+zdFFsgea62znWsH3Wg4+1Md8uPCqlaQIsaJQKZHc50eKD3bg0Io7c6kxHkBQr1b8
+Q7cGeK3CjdqG3NwS/aizzrLKOwL693hFwwy7JY7GGCvogbhyQRKn6iV0U9zMm7bu
+/9pQVV/wx8u01u2uAlLttjyQ5LJkxo5t8cATFVqxdN5J9eY//VSDiTwXnlpQITBP
+/Ow+zYuZ3kFlzH3CtCOhOEvNG3Ar1NvP3Icq35PlHV+Eki4otnKfixwByoiGpqCB
+UEIY04VrZJjwBxk08y/3jY2B3VLYGgi+rryyCxIqkB7UpSNPMMWSG4UpAgMBAAGj
+LzAtMAwGA1UdEwEB/wQCMAAwHQYDVR0RBBYwFIIMYmVuZGVyLmxvY2FshwSsEAAB
+MA0GCSqGSIb3DQEBCwUAA4IBAQCLW3JO8L7LKByjzj2RciPjCGH5XF87Wd20gYLq
+sNKcFwCIeyZhnQy5aZ164a5G9AIk2HLvH6HevBFPhA9Ivmyv/wYEfnPd1VcFkpgP
+hDt8MCFJ8eSjCyKdtZh1MPMLrLVymmJV+Rc9JUUYM9TIeERkpl0rskcO1YGewkYt
+qKlWE+0S16+pzsWvKn831uylqwIb8ANBPsCX4aM4muFBHavSWAHgRO+P+yXVw8Q+
+VQDnMHUe5PbZd1/+1KKVs1K/CkBCtoHNHp1d/JT+2zUQJphwja9CcgfFdVhSnHL4
+oEEOFtqVMIuQfR2isi08qW/JGOHc4sFoLYB8hvdaxKWSE19A
+-----END CERTIFICATE-----
+`
+
+const excludedNamesIntermediate = `
+-----BEGIN CERTIFICATE-----
+MIIDzTCCArWgAwIBAgIHDUSFqYeczDANBgkqhkiG9w0BAQsFADCBmTELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU
+MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5
+ICgzNzM0NTE1NDc5MDY0NjAyKTEcMBoGA1UEAwwTTG9jYWwgUm9vdCBmb3IgMzM5
+MjAeFw0xNzAyMDgyMTE1MDRaFw0xODAyMDgyMDI0NThaMIGeMQswCQYDVQQGEwJV
+UzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJTG9zIEdhdG9zMRQwEgYD
+VQQKDAtOZXRmbGl4IEluYzEtMCsGA1UECwwkUGxhdGZvcm0gU2VjdXJpdHkgKDM3
+MzQ1MTU1NjI4MDYzOTcpMSEwHwYDVQQDDBhJbnRlcm1lZGlhdGUgQ0EgZm9yIDMz
+OTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCOyEs6tJ/t9emQTvlx
+3FS7uJSou5rKkuqVxZdIuYQ+B2ZviBYUnMRT9bXDB0nsVdKZdp0hdchdiwNXDG/I
+CiWu48jkcv/BdynVyayOT+0pOJSYLaPYpzBx1Pb9M5651ct9GSbj6Tz0ChVonoIE
+1AIZ0kkebucZRRFHd0xbAKVRKyUzPN6HJ7WfgyauUp7RmlC35wTmrmARrFohQLlL
+7oICy+hIQePMy9x1LSFTbPxZ5AUUXVC3eUACU3vLClF/Xs8XGHebZpUXCdMQjOGS
+nq1eFguFHR1poSB8uSmmLqm4vqUH9CDhEgiBAC8yekJ8//kZQ7lUEqZj3YxVbk+Y
+E4H5AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+ADxrnmNX5gWChgX9K5fYwhFDj5ofxZXAKVQk+WjmkwMcmCx3dtWSm++Wdksj/ZlA
+V1cLW3ohWv1/OAZuOlw7sLf98aJpX+UUmIYYQxDubq+4/q7VA7HzEf2k/i/oN1NI
+JgtrhpPcZ/LMO6k7DYx0qlfYq8pTSfd6MI4LnWKgLc+JSPJJjmvspgio2ZFcnYr7
+A264BwLo6v1Mos1o1JUvFFcp4GANlw0XFiWh7JXYRl8WmS5DoouUC+aNJ3lmyF6z
+LbIjZCSfgZnk/LK1KU1j91FI2bc2ULYZvAC1PAg8/zvIgxn6YM2Q7ZsdEgWw0FpS
+zMBX1/lk4wkFckeUIlkD55Y=
+-----END CERTIFICATE-----`
+
+const excludedNamesRoot = `
+-----BEGIN CERTIFICATE-----
+MIIEGTCCAwGgAwIBAgIHDUSFpInn/zANBgkqhkiG9w0BAQsFADCBozELMAkGA1UE
+BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBHYXRvczEU
+MBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNlY3VyaXR5
+ICgzNzMxNTA5NDM3NDYyNDg1KTEmMCQGA1UEAwwdTmFtZSBDb25zdHJhaW50cyBU
+ZXN0IFJvb3QgQ0EwHhcNMTcwMjA4MjExNTA0WhcNMTgwMjA4MjAyNDU4WjCBmTEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCUxvcyBH
+YXRvczEUMBIGA1UECgwLTmV0ZmxpeCBJbmMxLTArBgNVBAsMJFBsYXRmb3JtIFNl
+Y3VyaXR5ICgzNzM0NTE1NDc5MDY0NjAyKTEcMBoGA1UEAwwTTG9jYWwgUm9vdCBm
+b3IgMzM5MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJymcnX29ekc
+7+MLyr8QuAzoHWznmGdDd2sITwWRjM89/21cdlHCGKSpULUNdFp9HDLWvYECtxt+
+8TuzKiQz7qAerzGUT1zI5McIjHy0e/i4xIkfiBiNeTCuB/N9QRbZlcfM80ErkaA4
+gCAFK8qZAcWkHIl6e+KaQFMPLKk9kckgAnVDHEJe8oLNCogCJ15558b65g05p9eb
+5Lg+E98hoPRTQaDwlz3CZPfTTA2EiEZInSi8qzodFCbTpJUVTbiVUH/JtVjlibbb
+smdcx5PORK+8ZJkhLEh54AjaWOX4tB/7Tkk8stg2VBmrIARt/j4UVj7cTrIWU3bV
+m8TwHJG+YgsCAwEAAaNaMFgwDwYDVR0TAQH/BAUwAwEB/zBFBgNVHR4EPjA8oBww
+CocICgEAAP//AAAwDoIMYmVuZGVyLmxvY2FsoRwwCocICgEAAP//AAAwDoIMYmVu
+ZGVyLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4IBAQAMjbheffPxtSKSv9NySW+8qmHs
+n7Mb5GGyCFu+cMZSoSaabstbml+zHEFJvWz6/1E95K4F8jKhAcu/CwDf4IZrSD2+
+Hee0DolVSQhZpnHgPyj7ZATz48e3aJaQPUlhCEOh0wwF4Y0N4FV0t7R6woLylYRZ
+yU1yRHUqUYpN0DWFpsPbBqgM6uUAVO2ayBFhPgWUaqkmSbZ/Nq7isGvknaTmcIwT
+6mOAFN0qFb4RGzfGJW7x6z7KCULS7qVDp6fU3tRoScHFEgRubks6jzQ1W5ooSm4o
++NQCZDd5eFeU8PpNX7rgaYE4GPq+EEmLVCBYmdctr8QVdqJ//8Xu3+1phjDy
+-----END CERTIFICATE-----`
+
 var unknownAuthorityErrorTests = []struct {
        cert     string
        expected string
index 3065fe260237a4f607f73a8f42769c7b67d401a6..549b64b8304902f6159fd090e1496991b3150322 100644 (file)
@@ -689,6 +689,7 @@ type Certificate struct {
        // Name constraints
        PermittedDNSDomainsCritical bool // if true then the name constraints are marked critical.
        PermittedDNSDomains         []string
+       ExcludedDNSDomains          []string
 
        // CRL Distribution Points
        CRLDistributionPoints []string
@@ -1183,19 +1184,27 @@ func parseCertificate(in *certificate) (*Certificate, error) {
                                        return nil, errors.New("x509: trailing data after X.509 NameConstraints")
                                }
 
-                               if len(constraints.Excluded) > 0 && e.Critical {
-                                       return out, UnhandledCriticalExtension{}
-                               }
-
-                               for _, subtree := range constraints.Permitted {
-                                       if len(subtree.Name) == 0 {
-                                               if e.Critical {
-                                                       return out, UnhandledCriticalExtension{}
+                               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
                                                }
-                                               continue
+                                               dnsNames = append(dnsNames, subtree.Name)
                                        }
-                                       out.PermittedDNSDomains = append(out.PermittedDNSDomains, 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:
                                // RFC 5280, 4.2.1.13
@@ -1579,16 +1588,22 @@ func buildExtensions(template *Certificate, authorityKeyId []byte) (ret []pkix.E
                n++
        }
 
-       if len(template.PermittedDNSDomains) > 0 &&
+       if (len(template.PermittedDNSDomains) > 0 || len(template.ExcludedDNSDomains) > 0) &&
                !oidInExtensions(oidExtensionNameConstraints, template.ExtraExtensions) {
                ret[n].Id = oidExtensionNameConstraints
                ret[n].Critical = template.PermittedDNSDomainsCritical
 
                var out nameConstraints
+
                out.Permitted = make([]generalSubtree, len(template.PermittedDNSDomains))
                for i, permitted := range template.PermittedDNSDomains {
                        out.Permitted[i] = generalSubtree{Name: permitted}
                }
+               out.Excluded = make([]generalSubtree, len(template.ExcludedDNSDomains))
+               for i, excluded := range template.ExcludedDNSDomains {
+                       out.Excluded[i] = generalSubtree{Name: excluded}
+               }
+
                ret[n].Value, err = asn1.Marshal(out)
                if err != nil {
                        return
@@ -1704,10 +1719,10 @@ func signingParamsForPublicKey(pub interface{}, requestedSigAlgo SignatureAlgori
 
 // CreateCertificate creates a new certificate based on a template. The
 // following members of template are used: AuthorityKeyId,
-// BasicConstraintsValid, DNSNames, ExtKeyUsage, IsCA, KeyUsage, MaxPathLen,
-// NotAfter, NotBefore, PermittedDNSDomains, PermittedDNSDomainsCritical,
-// SerialNumber, SignatureAlgorithm, Subject, SubjectKeyId, and
-// UnknownExtKeyUsage.
+// BasicConstraintsValid, DNSNames, ExcludedDNSDomains, ExtKeyUsage, IsCA,
+// KeyUsage, MaxPathLen, NotAfter, NotBefore, PermittedDNSDomains,
+// PermittedDNSDomainsCritical, SerialNumber, SignatureAlgorithm, Subject,
+// SubjectKeyId, and UnknownExtKeyUsage.
 //
 // The certificate is signed by parent. If parent is equal to template then the
 // certificate is self-signed. The parameter pub is the public key of the
index b085dad90f06a5f78a21e91d0f4ea5f2ef102522..2d1acf93bf599fb9f138a53c77d0662d740465d4 100644 (file)
@@ -405,6 +405,7 @@ func TestCreateSelfSignedCertificate(t *testing.T) {
 
                        PolicyIdentifiers:   []asn1.ObjectIdentifier{[]int{1, 2, 3}},
                        PermittedDNSDomains: []string{".example.com", "example.com"},
+                       ExcludedDNSDomains:  []string{"bar.example.com"},
 
                        CRLDistributionPoints: []string{"http://crl1.example.com/ca1.crl", "http://crl2.example.com/ca1.crl"},
 
@@ -442,6 +443,10 @@ func TestCreateSelfSignedCertificate(t *testing.T) {
                        t.Errorf("%s: failed to parse name constraints: %#v", test.name, cert.PermittedDNSDomains)
                }
 
+               if len(cert.ExcludedDNSDomains) != 1 || cert.ExcludedDNSDomains[0] != "bar.example.com" {
+                       t.Errorf("%s: failed to parse name constraint exclusions: %#v", test.name, cert.ExcludedDNSDomains)
+               }
+
                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)
                }