Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13368] ファイルの概要

このコミットは、Go言語のcrypto/x509パッケージに拡張鍵用途(Extended Key Usage: EKU)のサポートを追加するものです。これにより、X.509証明書が特定の目的にのみ使用されるように制限できるようになり、セキュリティが強化されます。特に、コード署名証明書がTLSサーバー認証に誤用されるといった、意図しない用途での証明書利用を防ぐことを目的としています。

コミット

commit 7f689864d833d8cd0c59d9800f428d3d60d67ab4
Author: Adam Langley <agl@golang.org>
Date:   Wed Jun 20 16:18:56 2012 -0400

    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 307c5ef033..91506e87bb 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"
 	}\n 	return "x509: unknown error"\n }\n@@ -84,6 +89,11 @@ type VerifyOptions struct {\n 	Intermediates *CertPool\n 	Roots         *CertPool // if nil, the system roots are used\n 	CurrentTime   time.Time // if zero, the current time is used\n+\t// KeyUsage specifies which Extended Key Usage values are acceptable.\n+\t// An empty list means ExtKeyUsageServerAuth. Key usage is considered a\n+\t// constraint down the chain which mirrors Windows CryptoAPI behaviour,\n+\t// but not the spec. To accept any key usage, include ExtKeyUsageAny.\n+\tKeyUsages []ExtKeyUsage\n }\n \n const (\n@@ -174,7 +184,35 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e\n \t\t}\n \t}\n \n-\treturn c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts)\n+\tcandidateChains, err := c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts)\n+\tif err != nil {\n+\t\treturn\n+\t}\n+\n+\tkeyUsages := opts.KeyUsages\n+\tif len(keyUsages) == 0 {\n+\t\tkeyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth}\n+\t}\n+\n+\t// If any key usage is acceptable then we're done.\n+\tfor _, usage := range keyUsages {\n+\t\tif usage == ExtKeyUsageAny {\n+\t\t\tchains = candidateChains\n+\t\t\treturn\n+\t\t}\n+\t}\n+\n+\tfor _, candidate := range candidateChains {\n+\t\tif checkChainForKeyUsage(candidate, keyUsages) {\n+\t\t\tchains = append(chains, candidate)\n+\t\t}\n+\t}\n+\n+\tif len(chains) == 0 {\n+\t\terr = CertificateInvalidError{c, IncompatibleUsage}\n+\t}\n+\n+\treturn\n }\n \n func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate {\n@@ -300,3 +338,56 @@ func (c *Certificate) VerifyHostname(h string) error {\n \n \treturn HostnameError{c, h}\n }\n+\n+func checkChainForKeyUsage(chain []*Certificate, keyUsages []ExtKeyUsage) bool {\n+\tusages := make([]ExtKeyUsage, len(keyUsages))\n+\tcopy(usages, keyUsages)\n+\n+\tif len(chain) == 0 {\n+\t\treturn false\n+\t}\n+\n+\tusagesRemaining := len(usages)\n+\n+\t// We walk down the list and cross out any usages that aren't supported\n+\t// by each certificate. If we cross out all the usages, then the chain\n+\t// is unacceptable.\n+\n+\tfor i := len(chain) - 1; i >= 0; i-- {\n+\t\tcert := chain[i]\n+\t\tif len(cert.ExtKeyUsage) == 0 && len(cert.UnknownExtKeyUsage) == 0 {\n+\t\t\t// The certificate doesn't have any extended key usage specified.\n+\t\t\tcontinue\n+\t\t}\n+\n+\t\tfor _, usage := range cert.ExtKeyUsage {\n+\t\t\tif usage == ExtKeyUsageAny {\n+\t\t\t\t// The certificate is explicitly good for any usage.\n+\t\t\t\tcontinue\n+\t\t\t}\n+\t\t}\n+\n+\t\tconst invalidUsage ExtKeyUsage = -1\n+\n+\tNextRequestedUsage:\n+\t\tfor i, requestedUsage := range usages {\n+\t\t\tif requestedUsage == invalidUsage {\n+\t\t\t\tcontinue\n+\t\t\t}\n+\n+\t\t\tfor _, usage := range cert.ExtKeyUsage {\n+\t\t\t\tif requestedUsage == usage {\n+\t\t\t\t\tcontinue NextRequestedUsage\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tusages[i] = invalidUsage\n+\t\t\tusagesRemaining--\n+\t\t\tif usagesRemaining == 0 {\n+\t\t\t\treturn false\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn true\n+}\ndiff --git a/src/pkg/crypto/x509/verify_test.go b/src/pkg/crypto/x509/verify_test.go
index 7b171b291a..510a119ff7 100644
--- a/src/pkg/crypto/x509/verify_test.go
+++ b/src/pkg/crypto/x509/verify_test.go
@@ -21,6 +21,7 @@ type verifyTest struct {\n 	currentTime   int64\n 	dnsName       string\n 	systemSkip    bool\n+\tkeyUsages     []ExtKeyUsage\n \n \terrorCallback  func(*testing.T, int, error) bool\n \texpectedChains [][]string\n@@ -113,6 +114,38 @@ var verifyTests = []verifyTest{\n \t\t\t{\"dnssec-exp\", \"StartCom Class 1\", \"StartCom Certification Authority\", \"StartCom Certification Authority\"},\n \t\t},\n \t},\n+\t{\n+\t\t// The default configuration should reject an S/MIME chain.\n+\t\tleaf:        smimeLeaf,\n+\t\troots:       []string{smimeIntermediate},\n+\t\tcurrentTime: 1339436154,\n+\n+\t\t// Key usage not implemented for Windows yet.\n+\t\tsystemSkip:    true,\n+\t\terrorCallback: expectUsageError,\n+\t},\n+\t{\n+\t\tleaf:        smimeLeaf,\n+\t\troots:       []string{smimeIntermediate},\n+\t\tcurrentTime: 1339436154,\n+\t\tkeyUsages:   []ExtKeyUsage{ExtKeyUsageServerAuth},\n+\n+\t\t// Key usage not implemented for Windows yet.\n+\t\tsystemSkip:    true,\n+\t\terrorCallback: expectUsageError,\n+\t},\n+\t{\n+\t\tleaf:        smimeLeaf,\n+\t\troots:       []string{smimeIntermediate},\n+\t\tcurrentTime: 1339436154,\n+\t\tkeyUsages:   []ExtKeyUsage{ExtKeyUsageEmailProtection},\n+\n+\t\t// Key usage not implemented for Windows yet.\n+\t\tsystemSkip: true,\n+\t\texpectedChains: [][]string{\n+\t\t\t{\"Ryan Hurst\", \"GlobalSign PersonalSign 2 CA - G2\"},\n+\t\t},\n+\t},\n }\n \n func expectHostnameError(t *testing.T, i int, err error) (ok bool) {\n@@ -131,6 +164,14 @@ func expectExpired(t *testing.T, i int, err error) (ok bool) {\n \treturn true\n }\n \n+func expectUsageError(t *testing.T, i int, err error) (ok bool) {\n+\tif inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != IncompatibleUsage {\n+\t\tt.Errorf("#%d: error was not IncompatibleUsage: %s", i, err)\n+\t\treturn false\n+\t}\n+\treturn true\n+}\n+\n func expectAuthorityUnknown(t *testing.T, i int, err error) (ok bool) {\n \tif _, ok := err.(UnknownAuthorityError); !ok {\n \t\tt.Errorf("#%d: error was not UnknownAuthorityError: %s", i, err)\n@@ -157,6 +198,7 @@ func testVerify(t *testing.T, useSystemRoots bool) {\n \t\t\tIntermediates: NewCertPool(),\n \t\t\tDNSName:       test.dnsName,\n \t\t\tCurrentTime:   time.Unix(test.currentTime, 0),\n+\t\t\tKeyUsages:     test.keyUsages,\n \t\t}\n \n \t\tif !useSystemRoots {\n@@ -433,3 +475,58 @@ O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V\n um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh\n NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=\n -----END CERTIFICATE-----`\n+\n+const smimeLeaf = `-----BEGIN CERTIFICATE-----\n+MIIFBjCCA+6gAwIBAgISESFvrjT8XcJTEe6rBlPptILlMA0GCSqGSIb3DQEBBQUA\n+MFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSowKAYD\n+VQQDEyFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiAyIENBIC0gRzIwHhcNMTIwMTIz\n+MTYzNjU5WhcNMTUwMTIzMTYzNjU5WjCBlDELMAkGA1UEBhMCVVMxFjAUBgNVBAgT\n+DU5ldyBIYW1zcGhpcmUxEzARBgNVBAcTClBvcnRzbW91dGgxGTAXBgNVBAoTEEds\n+b2JhbFNpZ24sIEluYy4xEzARBgNVBAMTClJ5YW4gSHVyc3QxKDAmBgkqhkiG9w0B\n+CQEWGXJ5YW4uaHVyc3RAZ2xvYmFsc2lnbi5jb20wggEiMA0GCSqGSIb3DQEBAQUA\n+A4IBDwAwggEKAoIBAQC4ASSTvavmsFQAob60ukSSwOAL9nT/s99ltNUCAf5fPH5j\n+NceMKxaQse2miOmRRIXaykcq1p/TbI70Ztce38r2mbOwqDHHPVi13GxJEyUXWgaR\n+BteDMu5OGyWNG1kchVsGWpbstT0Z4v0md5m1BYFnxB20ebJyOR2lXDxsFK28nnKV\n++5eMj76U8BpPQ4SCH7yTMG6y0XXsB3cCrBKr2o3TOYgEKv+oNnbaoMt3UxMt9nSf\n+9jyIshjqfnT5Aew3CUNMatO55g5FXXdIukAweg1YSb1ls05qW3sW00T3d7dQs9/7\n+NuxCg/A2elmVJSoy8+MLR8JSFEf/aMgjO/TyLg/jAgMBAAGjggGPMIIBizAOBgNV\n+HQ8BAf8EBAMCBaAwTQYDVR0gBEYwRDBCBgorBgEEAaAyASgKMDQwMgYIKwYBBQUH\n+AgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMCQGA1Ud\n+EQQdMBuBGXJ5YW4uaHVyc3RAZ2xvYmFsc2lnbi5jb20wCQYDVR0TBAIwADAdBgNV\n+HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwQwYDVR0fBDwwOjA4oDagNIYyaHR0\n+cDovL2NybC5nbG9iYWxzaWduLmNvbS9ncy9nc3BlcnNvbmFsc2lnbjJnMi5jcmww\n+VQYIKwYBBQUHAQEESTBHMEUGCCsGAQUFBzAChjlodHRwOi8vc2VjdXJlLmdsb2Jh\n+bHNpZ24uY29tL2NhY2VydC9nc3BlcnNvbmFsc2lnbjJnMi5c3J0MCwHQYDVR0OBBYE\n+FFWiECe0/L72eVYqcWYnLV6SSjzhMB8GA1UdIwQYMBaAFD8V0m18L+cxnkMKBqiU\n+bCw7xe5lMA0GCSqGSIb3DQEBBQUAA4IBAQAhQi6hLPeudmf3IBF4IDzCvRI0FaYd\n+BKfprSk/H0PDea4vpsLbWpA0t0SaijiJYtxKjlM4bPd+2chb7ejatDdyrZIzmDVy\n+q4c30/xMninGKokpYA11/Ve+i2dvjulu65qasrtQRGybAuuZ67lrp/K3OMFgjV5N\n+C3AHYLzvNU4Dwc4QQ1BaMOg6KzYSrKbABRZajfrpC9uiePsv7mDIXLx/toBPxWNl\n+a5vJm5DrZdn7uHdvBCE6kMykbOLN5pmEK0UIlwKh6Qi5XD0pzlVkEZliFkBMJgub\n+d/eF7xeg7TKPWC5xyOFp9SdMolJM7LTC3wnSO3frBAev+q/nGs9Xxyvs\n+-----END CERTIFICATE-----`\n+\n+const smimeIntermediate = `-----BEGIN CERTIFICATE-----\n+MIIEFjCCAv6gAwIBAgILBAAAAAABL07hL1IwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw\n+MDBaFw0xOTA0MTMxMDAwMDBaMFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n+YWxTaWduIG52LXNhMSowKAYDVQQDEyFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiAy\n+IENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBa0H5Nez4\n+En3dIlFpX7e5E0YndxQ74xOBbz7kdBd+DLX0LOQMjVPU3DAgKL9ujhH+ZhHkURbH\n+3X/94TQSUL/z2JjsaQvS0NqyZXHhM5eeuquzOJRzEQ8+odETzHg2G0Erv7yjSeww\n+gkwDWDJnYUDlOjYTDUEG6+i+8Mn425reo4I0E277wD542kmVWeW7+oHv5dZo9e1Q\n+yWwiKTEP6BEQVVSBgThXMG4traSSDRUt3T1eQTZx5EObpiBEBO4OTqiBTJfg4vEI\n+YgkXzKLpnfszTB6YMDpR9/QS6p3ANB3kfAb+t6udSO3WCst0DGrwHDLBFGDR4UeY\n+T5KGGnI7cWL7AgMBAAGjgeUwgeIwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI\n+MAYBAf8CAQAwHQYDVR0OBBYEFD8V0m18L+cxnkMKBqiUbCw7xe5lMEcGA1UdIARA\n+MD4wPAYEVR0gADA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWdu\n+LmNvbS9yZXBvc2l0b3J5LzAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmds\n+b2JhbHNpZ24ubmV0L3Jvb3QuY3JsMB8GA1UdIwQYMBaAFGB7ZhpFDZfKiVAvfQTN\n+NKj//P1LMA0GCSqGSIb3DQEBBQUAA4IBAQBDc3nMpMxJMQMcYUCB3+C73UpvwDE8\n+eCOr7t2F/uaQKKcyqqstqLZc6vPwI/rcE9oDHugY5QEjQzIBIEaTnN6P0vege2IX\n+eCOr7t2F/uaQKKcyqqstqLZc6vPwI/rcE9oDHugY5QEjQzIBIEaTnN6P0vege2IX\n+YEvTWbWwGdPytDFPYIl3/6OqNSXSnZ7DxPcdLJq2uyiga8PB/TTIIHYkdM2+1DE0\n+7y3rH/7TjwDVD7SLu5/SdOfKskuMPTjOEvz3K161mymW06klVhubCIWOro/Gx1Q2\n+2FQOZ7/2k4uYoOdBTSlb8kTAuzZNgIE0rB2BIYCTz/P6zZIKW0ogbRSH\n+-----END CERTIFICATE-----`\ndiff --git a/src/pkg/crypto/x509/x509.go b/src/pkg/crypto/x509/x509.go
index 6848bf801d..24e2ae1d8a 100644
--- a/src/pkg/crypto/x509/x509.go
+++ b/src/pkg/crypto/x509/x509.go
@@ -350,6 +350,9 @@ var (\n 	oidExtKeyUsageClientAuth      = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 2}\n 	oidExtKeyUsageCodeSigning     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 3}\n 	oidExtKeyUsageEmailProtection = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 4}\n+\toidExtKeyUsageIPSECEndSystem  = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 5}\n+\toidExtKeyUsageIPSECTunnel     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 6}\n+\toidExtKeyUsageIPSECUser       = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 7}\n \toidExtKeyUsageTimeStamping    = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8}\n \toidExtKeyUsageOCSPSigning     = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 9}\n )\n@@ -364,6 +367,9 @@ const (\n 	ExtKeyUsageClientAuth\n 	ExtKeyUsageCodeSigning\n 	ExtKeyUsageEmailProtection\n+\tExtKeyUsageIPSECEndSystem\n+\tExtKeyUsageIPSECTunnel\n+\tExtKeyUsageIPSECUser\n \tExtKeyUsageTimeStamping\n \tExtKeyUsageOCSPSigning\n )\n@@ -806,6 +812,12 @@ func parseCertificate(in *certificate) (*Certificate, error) {\n \t\t\t\t\t\tout.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageCodeSigning)\n \t\t\t\t\tcase u.Equal(oidExtKeyUsageEmailProtection):\n \t\t\t\t\t\tout.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageEmailProtection)\n+\t\t\t\t\tcase u.Equal(oidExtKeyUsageIPSECEndSystem):\n+\t\t\t\t\t\tout.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageIPSECEndSystem)\n+\t\t\t\t\t\tout.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageIPSECTunnel)\n+\t\t\t\t\tcase u.Equal(oidExtKeyUsageIPSECUser):\n+\t\t\t\t\t\tout.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageIPSECUser)\n \t\t\t\t\tcase u.Equal(oidExtKeyUsageTimeStamping):\n \t\t\t\t\t\tout.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageTimeStamping)\n \t\t\t\t\tcase u.Equal(oidExtKeyUsageOCSPSigning):\n```

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/7f689864d833d8cd0c59d9800f428d3d60d67ab4](https://github.com/golang/go/commit/7f689864d833d8cd0c59d9800f428d3d60d67ab4)

## 元コミット内容

`crypto/x509`: 拡張鍵用途のサポートを追加。

Flameマルウェアが、コード署名証明書がTLSサーバー認証に、あるいはその逆に使用されることを防ぐために、拡張鍵用途のサポートを追加するきっかけとなった。

## 変更の背景

このコミットの背景には、2012年に発見された高度なマルウェア「Flame」(またはFlamer、sKyWIper)の存在があります。Flameマルウェアは、Microsoftの正規のソフトウェアになりすますために、巧妙な証明書悪用手法を用いていました。具体的には、MD5ハッシュアルゴリズムの脆弱性を悪用した衝突攻撃により、偽造されたMicrosoft証明書を作成し、それが正規のMicrosoft署名を持つかのように見せかけていました。これにより、マルウェアはセキュリティチェックを回避し、Windows Updateシステムを乗っ取って他のマシンに感染を広げることが可能でした。

このような攻撃は、証明書が意図された目的以外に悪用されるリスクを浮き彫りにしました。例えば、ソフトウェアの正当性を保証するための「コード署名」用途の証明書が、ウェブサイトの安全な通信を保証する「TLSサーバー認証」用途に転用されるといった事態は、セキュリティ上の大きな脅威となります。

このコミットは、このような証明書の誤用を防ぐために、X.509証明書の「拡張鍵用途(Extended Key Usage: EKU)」を厳密に検証する機能を追加しました。これにより、証明書がその発行時に指定された特定の用途にのみ使用されることを強制し、セキュリティを強化することを目的としています。コミットメッセージにある「Flame motivated me to get around to adding extended key usage support」という記述は、このマルウェア事件が本機能の実装を促した直接的な動機であることを示しています。

## 前提知識の解説

### X.509証明書

X.509証明書は、公開鍵暗号基盤(PKI)において、公開鍵の所有者の身元を検証するために使用されるデジタル証明書です。これは、公開鍵と、その公開鍵が属するエンティティ(個人、サーバー、組織など)の識別情報、そして証明書発行者(認証局、CA)のデジタル署名を含んでいます。これにより、通信相手の公開鍵が本当にその相手のものであることを信頼できる第三者(CA)が保証する仕組みです。

### 鍵用途(Key Usage)拡張

X.509証明書には、その証明書に含まれる公開鍵がどのような暗号目的で使用されるべきかを示す「鍵用途(Key Usage)」拡張が含まれています。これは、証明書の基本的な用途を定義するもので、例えば以下のような用途があります。

*   **digitalSignature**: デジタル署名に使用
*   **keyEncipherment**: 鍵の暗号化に使用
*   **dataEncipherment**: データの暗号化に使用
*   **keyCertSign**: 証明書署名に使用(CA証明書など)
*   **cRLSign**: CRL(証明書失効リスト)署名に使用

### 拡張鍵用途(Extended Key Usage: EKU)拡張

「拡張鍵用途(Extended Key Usage: EKU)」拡張は、鍵用途拡張を補完し、証明書に含まれる公開鍵が使用できるより具体的な目的を示すものです。これは、オブジェクト識別子(OID)のリストとして表現され、各OIDが特定のアプリケーションやユースケースに対応します。EKUは、証明書の適用範囲をより細かく制御し、セキュリティを向上させるために使用されます。

一般的なEKUのOIDとその目的には以下のようなものがあります。

*   **Server Authentication (1.3.6.1.5.5.7.3.1)**: サーバーがクライアントに対して自身を認証するために使用(例: SSL/TLSウェブサーバー)
*   **Client Authentication (1.3.6.1.5.5.7.3.2)**: クライアントがサーバーに対して自身を認証するために使用(例: クライアント認証SSL/TLS、VPN)
*   **Code Signing (1.3.6.1.5.5.7.3.3)**: 実行可能コードに署名し、その出所と完全性を検証するために使用
*   **Email Protection (1.3.6.1.5.5.7.3.4)**: 安全な電子メール(S/MIMEなど)に使用
*   **Time Stamping (1.3.6.1.5.5.7.3.8)**: タイムスタンプに署名し、データが特定の時刻に存在したことを証明するために使用
*   **IPsec End System (1.3.6.1.5.5.7.3.5)**: IPsec VPNに使用
*   **IPsec Tunnel (1.3.6.1.5.5.7.3.6)**: IPsec VPNに使用
*   **IPsec User (1.3.6.1.5.5.7.3.7)**: IPsec VPNに使用

アプリケーションやシステムがX.509証明書を受け取ると、その証明書が現在使用されている目的に対して承認されているかどうかをEKU拡張をチェックして判断します。必要なEKUのOIDが存在しない場合や、サポートされていないEKUのOIDが存在する場合、アプリケーションはその証明書を拒否する可能性があります。これにより、証明書が意図しない目的で悪用されることを防ぎ、セキュリティを強化します。

## 技術的詳細

このコミットでは、Go言語の`crypto/x509`パッケージにおいて、X.509証明書の検証プロセスに拡張鍵用途(EKU)のチェックを導入しています。

主な変更点は以下の通りです。

1.  **新しいエラータイプ `IncompatibleUsage` の追加**:
    `src/pkg/crypto/x509/verify.go`に、証明書の鍵用途が互換性がない場合に発生する新しいエラータイプ`IncompatibleUsage`が追加されました。これにより、検証失敗の原因がより明確になります。

2.  **`VerifyOptions`構造体への`KeyUsages`フィールドの追加**:
    `src/pkg/crypto/x509/verify.go`の`VerifyOptions`構造体に`KeyUsages []ExtKeyUsage`フィールドが追加されました。これは、証明書チェーンの検証時に許容される拡張鍵用途のリストを指定するために使用されます。
    *   このリストが空の場合、デフォルトで`ExtKeyUsageServerAuth`(サーバー認証)が使用されます。
    *   `ExtKeyUsageAny`が含まれている場合、任意の鍵用途が許容されます。
    *   このフィールドは、Windows CryptoAPIの動作を模倣しており、鍵用途が証明書チェーン全体にわたる制約として扱われます。

3.  **`Certificate.Verify`メソッドの変更**:
    `src/pkg/crypto/x509/verify.go`の`Certificate.Verify`メソッドが修正され、証明書チェーンの構築後にEKUの検証が実行されるようになりました。
    *   まず、`buildChains`メソッドで候補となる証明書チェーンが構築されます。
    *   次に、`VerifyOptions.KeyUsages`に基づいて、各候補チェーンが`checkChainForKeyUsage`関数によって検証されます。
    *   有効なEKUを持つチェーンのみが最終的な結果として返されます。
    *   もし、どのチェーンも指定されたEKUを満たさない場合、`IncompatibleUsage`エラーが返されます。

4.  **`checkChainForKeyUsage`関数の追加**:
    `src/pkg/crypto/x509/verify.go`に新しく`checkChainForKeyUsage`関数が追加されました。この関数は、与えられた証明書チェーンと要求されたEKUのリストを受け取り、チェーン内の各証明書が要求されたEKUをサポートしているかどうかを再帰的にチェックします。
    *   この関数は、チェーンをルートからリーフに向かって(またはその逆)走査し、各証明書の`ExtKeyUsage`フィールドを確認します。
    *   もし証明書が`ExtKeyUsageAny`を含んでいる場合、その証明書は任意の用途に有効とみなされます。
    *   証明書が特定のEKUをサポートしていない場合、そのEKUは「無効」とマークされ、残りの有効なEKUの数が減らされます。
    *   もしすべての要求されたEKUが無効になった場合、そのチェーンは受け入れられないと判断され、`false`を返します。

5.  **`x509.go`における新しいEKUのOIDと定数の追加**:
    `src/pkg/crypto/x509/x509.go`に、IPsec関連の新しい拡張鍵用途のOID(Object Identifier)とそれに対応する定数(`ExtKeyUsageIPSECEndSystem`, `ExtKeyUsageIPSECTunnel`, `ExtKeyUsageIPSECUser`)が追加されました。これにより、これらの特定の用途もEKUとして認識・処理できるようになります。

6.  **テストケースの追加**:
    `src/pkg/crypto/x509/verify_test.go`に、EKU検証の動作を検証するための新しいテストケースが追加されました。特に、S/MIME証明書がサーバー認証の目的で誤用されないことや、正しいEKUが指定された場合にのみ検証が成功することを確認するテストが含まれています。

これらの変更により、Goの`crypto/x509`パッケージは、証明書の検証においてEKUを考慮するようになり、より堅牢なセキュリティを提供できるようになりました。

## コアとなるコードの変更箇所

### `src/pkg/crypto/x509/verify.go`

*   `const`ブロックに`IncompatibleUsage`エラー定数を追加。
*   `CertificateInvalidError`の`Error()`メソッドに`IncompatibleUsage`のエラーメッセージを追加。
*   `VerifyOptions`構造体に`KeyUsages []ExtKeyUsage`フィールドを追加。
*   `Certificate.Verify`メソッド内で、`buildChains`で得られた候補チェーンに対して`checkChainForKeyUsage`を呼び出し、EKUによるフィルタリングとエラー処理を追加。
*   `checkChainForKeyUsage`関数を新規追加。この関数が証明書チェーン内の各証明書のEKUを検証する主要なロジックを担う。

### `src/pkg/crypto/x509/verify_test.go`

*   `verifyTest`構造体に`keyUsages []ExtKeyUsage`フィールドを追加。
*   `expectUsageError`関数を新規追加。これは`IncompatibleUsage`エラーを期待するテストヘルパー関数。
*   `verifyTests`配列に、S/MIME証明書を用いたEKU検証の新しいテストケースを追加。デフォルト設定でS/MIME証明書が拒否されること、`ExtKeyUsageServerAuth`を指定した場合も拒否されること、`ExtKeyUsageEmailProtection`を指定した場合にのみ成功することを確認。
*   `testVerify`関数内で、`VerifyOptions`に`test.keyUsages`を渡すように変更。

### `src/pkg/crypto/x509/x509.go`

*   `oidExtKeyUsageIPSECEndSystem`, `oidExtKeyUsageIPSECTunnel`, `oidExtKeyUsageIPSECUser`のOID定数を追加。
*   `ExtKeyUsageIPSECEndSystem`, `ExtKeyUsageIPSECTunnel`, `ExtKeyUsageIPSECUser`の`ExtKeyUsage`定数を追加。
*   `parseCertificate`関数内で、これらの新しいOIDを解析し、対応する`ExtKeyUsage`値を`Certificate.ExtKeyUsage`に追加するロジックを追加。

## コアとなるコードの解説

### `VerifyOptions`構造体への`KeyUsages`フィールド追加

```go
type VerifyOptions struct {
	// ... 既存のフィールド ...
	// 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
}

このフィールドは、証明書チェーンの検証を行う際に、どのような拡張鍵用途が許容されるかを呼び出し元が指定できるようにします。これにより、アプリケーションは特定の目的に合致する証明書のみを受け入れるように厳密なポリシーを適用できます。例えば、ウェブサーバーはExtKeyUsageServerAuthを持つ証明書のみを要求し、コード署名ツールはExtKeyUsageCodeSigningを持つ証明書のみを要求するといった使い方が可能になります。

Certificate.VerifyメソッドにおけるEKU検証ロジック

func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) {
	// ... 既存のチェーン構築ロジック ...

	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} // デフォルトはサーバー認証
	}

	// ExtKeyUsageAny が指定されていれば、任意の鍵用途を許容
	for _, usage := range keyUsages {
		if usage == ExtKeyUsageAny {
			chains = candidateChains
			return
		}
	}

	// 各候補チェーンに対してEKUチェックを実行
	for _, candidate := range candidateChains {
		if checkChainForKeyUsage(candidate, keyUsages) {
			chains = append(chains, candidate)
		}
	}

	// 有効なチェーンが一つもなければエラー
	if len(chains) == 0 {
		err = CertificateInvalidError{c, IncompatibleUsage}
	}

	return
}

Verifyメソッドは、まず通常のパス構築を行い、有効な候補チェーンのリストを取得します。その後、VerifyOptions.KeyUsagesで指定されたEKUに基づいて、これらの候補チェーンをフィルタリングします。もし、指定されたEKUに合致するチェーンが一つもなければ、IncompatibleUsageエラーを返します。これにより、証明書がその用途に合致しない場合に、検証プロセスが失敗するようになります。

checkChainForKeyUsage関数

func checkChainForKeyUsage(chain []*Certificate, keyUsages []ExtKeyUsage) bool {
	usages := make([]ExtKeyUsage, len(keyUsages))
	copy(usages, keyUsages) // 要求されたEKUのコピーを作成

	if len(chain) == 0 {
		return false
	}

	usagesRemaining := len(usages)

	// チェーンをルートからリーフに向かって(またはその逆)走査
	for i := len(chain) - 1; i >= 0; i-- {
		cert := chain[i]
		if len(cert.ExtKeyUsage) == 0 && len(cert.UnknownExtKeyUsage) == 0 {
			// 証明書にEKUが指定されていなければスキップ
			continue
		}

		// 証明書がExtKeyUsageAnyを含んでいれば、その証明書は任意の用途に有効
		for _, usage := range cert.ExtKeyUsage {
			if usage == ExtKeyUsageAny {
				continue
			}
		}

		const invalidUsage ExtKeyUsage = -1

	NextRequestedUsage:
		for i, requestedUsage := range usages {
			if requestedUsage == invalidUsage {
				continue
			}

			// 要求されたEKUが証明書によってサポートされているかチェック
			for _, usage := range cert.ExtKeyUsage {
				if requestedUsage == usage {
					continue NextRequestedUsage // サポートされていれば次の要求されたEKUへ
				}
			}

			// サポートされていなければ、その要求されたEKUを無効化
			usages[i] = invalidUsage
			usagesRemaining--
			if usagesRemaining == 0 {
				return false // すべての要求されたEKUが無効になったら、チェーンは無効
			}
		}
	}

	return true // すべての要求されたEKUがチェーンによってサポートされている
}

この関数は、EKU検証の核心部分です。与えられた証明書チェーンを構成する各証明書が、要求されたEKUのいずれかをサポートしているかをチェックします。Windows CryptoAPIの動作を模倣し、チェーン内の各証明書が、要求されたEKUのセットを「制約」として適用します。つまり、チェーン内のいずれかの証明書が特定のEKUをサポートしていない場合、そのEKUはチェーン全体で無効とみなされます。最終的に、要求されたすべてのEKUがチェーン全体でサポートされている場合にのみ、この関数はtrueを返します。

関連リンク

参考にした情報源リンク