From 7f689864d833d8cd0c59d9800f428d3d60d67ab4 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 20 Jun 2012 16:18:56 -0400 Subject: [PATCH] crypto/x509: add extended key usage support. 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 | 93 +++++++++++++++++++++++++++- src/pkg/crypto/x509/verify_test.go | 97 ++++++++++++++++++++++++++++++ src/pkg/crypto/x509/x509.go | 12 ++++ 3 files changed, 201 insertions(+), 1 deletion(-) diff --git a/src/pkg/crypto/x509/verify.go b/src/pkg/crypto/x509/verify.go index 307c5ef0339..91506e87bb0 100644 --- a/src/pkg/crypto/x509/verify.go +++ b/src/pkg/crypto/x509/verify.go @@ -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 +} diff --git a/src/pkg/crypto/x509/verify_test.go b/src/pkg/crypto/x509/verify_test.go index 7b171b291a4..510a119ff7c 100644 --- a/src/pkg/crypto/x509/verify_test.go +++ b/src/pkg/crypto/x509/verify_test.go @@ -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-----` diff --git a/src/pkg/crypto/x509/x509.go b/src/pkg/crypto/x509/x509.go index 6848bf801d4..24e2ae1d8ae 100644 --- a/src/pkg/crypto/x509/x509.go +++ b/src/pkg/crypto/x509/x509.go @@ -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):