mirror of
https://github.com/golang/go
synced 2024-11-25 14:07:56 -07:00
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
This commit is contained in:
parent
77b1378c3e
commit
7f689864d8
@ -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
|
||||
}
|
||||
|
@ -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-----`
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user