]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/x509: add extended key usage support.
authorAdam Langley <agl@golang.org>
Wed, 20 Jun 2012 20:18:56 +0000 (16:18 -0400)
committerAdam Langley <agl@golang.org>
Wed, 20 Jun 2012 20:18:56 +0000 (16:18 -0400)
Flame motivated me to get around to adding extended key usage support
so that code signing certificates can't be used for TLS server
authentication and vice versa.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6304065

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

index 307c5ef03394e581c8aaf8ef219ee500a6d7ee11..91506e87bb0a3b7a379946d71f476541c758bdb1 100644 (file)
@@ -27,6 +27,9 @@ const (
        // TooManyIntermediates results when a path length constraint is
        // violated.
        TooManyIntermediates
+       // IncompatibleUsage results when the certificate's key usage indicates
+       // that it may only be used for a different purpose.
+       IncompatibleUsage
 )
 
 // CertificateInvalidError results when an odd error occurs. Users of this
@@ -46,6 +49,8 @@ func (e CertificateInvalidError) Error() string {
                return "x509: a root or intermediate certificate is not authorized to sign in this domain"
        case TooManyIntermediates:
                return "x509: too many intermediates for path length constraint"
+       case IncompatibleUsage:
+               return "x509: certificate specifies an incompatible key usage"
        }
        return "x509: unknown error"
 }
@@ -84,6 +89,11 @@ type VerifyOptions struct {
        Intermediates *CertPool
        Roots         *CertPool // if nil, the system roots are used
        CurrentTime   time.Time // if zero, the current time is used
+       // KeyUsage specifies which Extended Key Usage values are acceptable.
+       // An empty list means ExtKeyUsageServerAuth. Key usage is considered a
+       // constraint down the chain which mirrors Windows CryptoAPI behaviour,
+       // but not the spec. To accept any key usage, include ExtKeyUsageAny.
+       KeyUsages []ExtKeyUsage
 }
 
 const (
@@ -174,7 +184,35 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
                }
        }
 
-       return c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts)
+       candidateChains, err := c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts)
+       if err != nil {
+               return
+       }
+
+       keyUsages := opts.KeyUsages
+       if len(keyUsages) == 0 {
+               keyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth}
+       }
+
+       // If any key usage is acceptable then we're done.
+       for _, usage := range keyUsages {
+               if usage == ExtKeyUsageAny {
+                       chains = candidateChains
+                       return
+               }
+       }
+
+       for _, candidate := range candidateChains {
+               if checkChainForKeyUsage(candidate, keyUsages) {
+                       chains = append(chains, candidate)
+               }
+       }
+
+       if len(chains) == 0 {
+               err = CertificateInvalidError{c, IncompatibleUsage}
+       }
+
+       return
 }
 
 func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate {
@@ -300,3 +338,56 @@ func (c *Certificate) VerifyHostname(h string) error {
 
        return HostnameError{c, h}
 }
+
+func checkChainForKeyUsage(chain []*Certificate, keyUsages []ExtKeyUsage) bool {
+       usages := make([]ExtKeyUsage, len(keyUsages))
+       copy(usages, keyUsages)
+
+       if len(chain) == 0 {
+               return false
+       }
+
+       usagesRemaining := len(usages)
+
+       // We walk down the list and cross out any usages that aren't supported
+       // by each certificate. If we cross out all the usages, then the chain
+       // is unacceptable.
+
+       for i := len(chain) - 1; i >= 0; i-- {
+               cert := chain[i]
+               if len(cert.ExtKeyUsage) == 0 && len(cert.UnknownExtKeyUsage) == 0 {
+                       // The certificate doesn't have any extended key usage specified.
+                       continue
+               }
+
+               for _, usage := range cert.ExtKeyUsage {
+                       if usage == ExtKeyUsageAny {
+                               // The certificate is explicitly good for any usage.
+                               continue
+                       }
+               }
+
+               const invalidUsage ExtKeyUsage = -1
+
+       NextRequestedUsage:
+               for i, requestedUsage := range usages {
+                       if requestedUsage == invalidUsage {
+                               continue
+                       }
+
+                       for _, usage := range cert.ExtKeyUsage {
+                               if requestedUsage == usage {
+                                       continue NextRequestedUsage
+                               }
+                       }
+
+                       usages[i] = invalidUsage
+                       usagesRemaining--
+                       if usagesRemaining == 0 {
+                               return false
+                       }
+               }
+       }
+
+       return true
+}
index 7b171b291a4b5eb645f4b7edfd231ef5987f4835..510a119ff7c07c4e7c01c8492222115ea78ece0a 100644 (file)
@@ -21,6 +21,7 @@ type verifyTest struct {
        currentTime   int64
        dnsName       string
        systemSkip    bool
+       keyUsages     []ExtKeyUsage
 
        errorCallback  func(*testing.T, int, error) bool
        expectedChains [][]string
@@ -113,6 +114,38 @@ var verifyTests = []verifyTest{
                        {"dnssec-exp", "StartCom Class 1", "StartCom Certification Authority", "StartCom Certification Authority"},
                },
        },
+       {
+               // The default configuration should reject an S/MIME chain.
+               leaf:        smimeLeaf,
+               roots:       []string{smimeIntermediate},
+               currentTime: 1339436154,
+
+               // Key usage not implemented for Windows yet.
+               systemSkip:    true,
+               errorCallback: expectUsageError,
+       },
+       {
+               leaf:        smimeLeaf,
+               roots:       []string{smimeIntermediate},
+               currentTime: 1339436154,
+               keyUsages:   []ExtKeyUsage{ExtKeyUsageServerAuth},
+
+               // Key usage not implemented for Windows yet.
+               systemSkip:    true,
+               errorCallback: expectUsageError,
+       },
+       {
+               leaf:        smimeLeaf,
+               roots:       []string{smimeIntermediate},
+               currentTime: 1339436154,
+               keyUsages:   []ExtKeyUsage{ExtKeyUsageEmailProtection},
+
+               // Key usage not implemented for Windows yet.
+               systemSkip: true,
+               expectedChains: [][]string{
+                       {"Ryan Hurst", "GlobalSign PersonalSign 2 CA - G2"},
+               },
+       },
 }
 
 func expectHostnameError(t *testing.T, i int, err error) (ok bool) {
@@ -131,6 +164,14 @@ func expectExpired(t *testing.T, i int, err error) (ok bool) {
        return true
 }
 
+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)
+               return false
+       }
+       return true
+}
+
 func expectAuthorityUnknown(t *testing.T, i int, err error) (ok bool) {
        if _, ok := err.(UnknownAuthorityError); !ok {
                t.Errorf("#%d: error was not UnknownAuthorityError: %s", i, err)
@@ -157,6 +198,7 @@ func testVerify(t *testing.T, useSystemRoots bool) {
                        Intermediates: NewCertPool(),
                        DNSName:       test.dnsName,
                        CurrentTime:   time.Unix(test.currentTime, 0),
+                       KeyUsages:     test.keyUsages,
                }
 
                if !useSystemRoots {
@@ -433,3 +475,58 @@ O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
 um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
 NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
 -----END CERTIFICATE-----`
+
+const smimeLeaf = `-----BEGIN CERTIFICATE-----
+MIIFBjCCA+6gAwIBAgISESFvrjT8XcJTEe6rBlPptILlMA0GCSqGSIb3DQEBBQUA
+MFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSowKAYD
+VQQDEyFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiAyIENBIC0gRzIwHhcNMTIwMTIz
+MTYzNjU5WhcNMTUwMTIzMTYzNjU5WjCBlDELMAkGA1UEBhMCVVMxFjAUBgNVBAgT
+DU5ldyBIYW1zcGhpcmUxEzARBgNVBAcTClBvcnRzbW91dGgxGTAXBgNVBAoTEEds
+b2JhbFNpZ24sIEluYy4xEzARBgNVBAMTClJ5YW4gSHVyc3QxKDAmBgkqhkiG9w0B
+CQEWGXJ5YW4uaHVyc3RAZ2xvYmFsc2lnbi5jb20wggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQC4ASSTvavmsFQAob60ukSSwOAL9nT/s99ltNUCAf5fPH5j
+NceMKxaQse2miOmRRIXaykcq1p/TbI70Ztce38r2mbOwqDHHPVi13GxJEyUXWgaR
+BteDMu5OGyWNG1kchVsGWpbstT0Z4v0md5m1BYFnxB20ebJyOR2lXDxsFK28nnKV
++5eMj76U8BpPQ4SCH7yTMG6y0XXsB3cCrBKr2o3TOYgEKv+oNnbaoMt3UxMt9nSf
+9jyIshjqfnT5Aew3CUNMatO55g5FXXdIukAweg1YSb1ls05qW3sW00T3d7dQs9/7
+NuxCg/A2elmVJSoy8+MLR8JSFEf/aMgjO/TyLg/jAgMBAAGjggGPMIIBizAOBgNV
+HQ8BAf8EBAMCBaAwTQYDVR0gBEYwRDBCBgorBgEEAaAyASgKMDQwMgYIKwYBBQUH
+AgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMCQGA1Ud
+EQQdMBuBGXJ5YW4uaHVyc3RAZ2xvYmFsc2lnbi5jb20wCQYDVR0TBAIwADAdBgNV
+HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwQwYDVR0fBDwwOjA4oDagNIYyaHR0
+cDovL2NybC5nbG9iYWxzaWduLmNvbS9ncy9nc3BlcnNvbmFsc2lnbjJnMi5jcmww
+VQYIKwYBBQUHAQEESTBHMEUGCCsGAQUFBzAChjlodHRwOi8vc2VjdXJlLmdsb2Jh
+bHNpZ24uY29tL2NhY2VydC9nc3BlcnNvbmFsc2lnbjJnMi5jcnQwHQYDVR0OBBYE
+FFWiECe0/L72eVYqcWYnLV6SSjzhMB8GA1UdIwQYMBaAFD8V0m18L+cxnkMKBqiU
+bCw7xe5lMA0GCSqGSIb3DQEBBQUAA4IBAQAhQi6hLPeudmf3IBF4IDzCvRI0FaYd
+BKfprSk/H0PDea4vpsLbWpA0t0SaijiJYtxKjlM4bPd+2chb7ejatDdyrZIzmDVy
+q4c30/xMninGKokpYA11/Ve+i2dvjulu65qasrtQRGybAuuZ67lrp/K3OMFgjV5N
+C3AHYLzvNU4Dwc4QQ1BaMOg6KzYSrKbABRZajfrpC9uiePsv7mDIXLx/toBPxWNl
+a5vJm5DrZdn7uHdvBCE6kMykbOLN5pmEK0UIlwKh6Qi5XD0pzlVkEZliFkBMJgub
+d/eF7xeg7TKPWC5xyOFp9SdMolJM7LTC3wnSO3frBAev+q/nGs9Xxyvs
+-----END CERTIFICATE-----`
+
+const smimeIntermediate = `-----BEGIN CERTIFICATE-----
+MIIEFjCCAv6gAwIBAgILBAAAAAABL07hL1IwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0xOTA0MTMxMDAwMDBaMFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMSowKAYDVQQDEyFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiAy
+IENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBa0H5Nez4
+En3dIlFpX7e5E0YndxQ74xOBbz7kdBd+DLX0LOQMjVPU3DAgKL9ujhH+ZhHkURbH
+3X/94TQSUL/z2JjsaQvS0NqyZXHhM5eeuquzOJRzEQ8+odETzHg2G0Erv7yjSeww
+gkwDWDJnYUDlOjYTDUEG6+i+8Mn425reo4I0E277wD542kmVWeW7+oHv5dZo9e1Q
+yWwiKTEP6BEQVVSBgThXMG4traSSDRUt3T1eQTZx5EObpiBEBO4OTqiBTJfg4vEI
+YgkXzKLpnfszTB6YMDpR9/QS6p3ANB3kfAb+t6udSO3WCst0DGrwHDLBFGDR4UeY
+T5KGGnI7cWL7AgMBAAGjgeUwgeIwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwHQYDVR0OBBYEFD8V0m18L+cxnkMKBqiUbCw7xe5lMEcGA1UdIARA
+MD4wPAYEVR0gADA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWdu
+LmNvbS9yZXBvc2l0b3J5LzAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmds
+b2JhbHNpZ24ubmV0L3Jvb3QuY3JsMB8GA1UdIwQYMBaAFGB7ZhpFDZfKiVAvfQTN
+NKj//P1LMA0GCSqGSIb3DQEBBQUAA4IBAQBDc3nMpMxJMQMcYUCB3+C73UpvwDE8
+eCOr7t2F/uaQKKcyqqstqLZc6vPwI/rcE9oDHugY5QEjQzIBIEaTnN6P0vege2IX
+eCOr7t2F/uaQKKcyqqstqLZc6vPwI/rcE9oDHugY5QEjQzIBIEaTnN6P0vege2IX
+YEvTWbWwGdPytDFPYIl3/6OqNSXSnZ7DxPcdLJq2uyiga8PB/TTIIHYkdM2+1DE0
+7y3rH/7TjwDVD7SLu5/SdOfKskuMPTjOEvz3K161mymW06klVhubCIWOro/Gx1Q2
+2FQOZ7/2k4uYoOdBTSlb8kTAuzZNgIE0rB2BIYCTz/P6zZIKW0ogbRSH
+-----END CERTIFICATE-----`
index 6848bf801d440b697903ef017c5c15fc2e078006..24e2ae1d8aee41a3e93d90db2532219f9f8579e7 100644 (file)
@@ -350,6 +350,9 @@ var (
        oidExtKeyUsageClientAuth      = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 2}
        oidExtKeyUsageCodeSigning     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 3}
        oidExtKeyUsageEmailProtection = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 4}
+       oidExtKeyUsageIPSECEndSystem  = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 5}
+       oidExtKeyUsageIPSECTunnel     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 6}
+       oidExtKeyUsageIPSECUser       = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 7}
        oidExtKeyUsageTimeStamping    = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8}
        oidExtKeyUsageOCSPSigning     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 9}
 )
@@ -364,6 +367,9 @@ const (
        ExtKeyUsageClientAuth
        ExtKeyUsageCodeSigning
        ExtKeyUsageEmailProtection
+       ExtKeyUsageIPSECEndSystem
+       ExtKeyUsageIPSECTunnel
+       ExtKeyUsageIPSECUser
        ExtKeyUsageTimeStamping
        ExtKeyUsageOCSPSigning
 )
@@ -806,6 +812,12 @@ func parseCertificate(in *certificate) (*Certificate, error) {
                                                out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageCodeSigning)
                                        case u.Equal(oidExtKeyUsageEmailProtection):
                                                out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageEmailProtection)
+                                       case u.Equal(oidExtKeyUsageIPSECEndSystem):
+                                               out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageIPSECEndSystem)
+                                       case u.Equal(oidExtKeyUsageIPSECTunnel):
+                                               out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageIPSECTunnel)
+                                       case u.Equal(oidExtKeyUsageIPSECUser):
+                                               out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageIPSECUser)
                                        case u.Equal(oidExtKeyUsageTimeStamping):
                                                out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageTimeStamping)
                                        case u.Equal(oidExtKeyUsageOCSPSigning):