[インデックス 13885] ファイルの概要
このコミットは、Go言語の crypto/x509
パッケージにおいて、X.509証明書の生成時にテンプレートから ExtKeyUsage
(拡張キー使用法) および UnknownExtKeyUsage
(未知の拡張キー使用法) フィールドを適切に利用するように修正するものです。これにより、証明書が意図された目的にのみ使用されるよう、より正確な制御が可能になります。
コミット
commit c993ac11bce307c08383164947417c98913da73a
Author: Andrew Harding <andrew@spacemonkey.com>
Date: Thu Sep 20 12:36:37 2012 -0400
crypto/x509: Use ExtKeyUsage and UnknownExtKeyUsage fields from template in CreateCertificate
R=golang-dev, agl
CC=golang-dev
https://golang.org/cl/6535057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c993ac11bce307c08383164947417c98913da73a
元コミット内容
crypto/x509: Use ExtKeyUsage and UnknownExtKeyUsage fields from template in CreateCertificate
変更の背景
Go言語の crypto/x509
パッケージは、X.509証明書の生成と解析を扱うための標準ライブラリです。X.509証明書には、その証明書がどのような目的で使用されるべきかを示す「拡張キー使用法 (Extended Key Usage: EKU)」という拡張フィールドが存在します。例えば、サーバー認証、クライアント認証、コード署名などがこれに該当します。
このコミット以前の CreateCertificate
関数は、証明書テンプレートで指定された ExtKeyUsage
および UnknownExtKeyUsage
フィールドを適切に利用していませんでした。つまり、証明書を生成する際に、これらの重要なセキュリティ関連の制約が反映されていなかった可能性があります。これにより、生成された証明書が意図しない目的で使用されるリスクや、証明書のポリシーが正しく適用されない問題が生じる可能性がありました。
この変更の背景には、CreateCertificate
関数が証明書テンプレートの全ての関連フィールドを正確に処理し、生成される証明書がX.509標準とセキュリティ要件に完全に準拠するようにするという目的があります。特に、ExtKeyUsage
は証明書の用途を厳密に制限するために不可欠な要素であり、その適切な取り扱いは証明書運用のセキュリティと信頼性を高める上で非常に重要です。
前提知識の解説
X.509 証明書
X.509は、公開鍵証明書の標準フォーマットを定義するITU-Tの標準です。公開鍵基盤 (PKI) において、エンティティ(ユーザー、サーバーなど)の公開鍵と身元情報を安全に紐付けるために使用されます。X.509証明書には、所有者の公開鍵、所有者の識別情報、発行者の識別情報、有効期間、発行者の署名などが含まれます。
拡張キー使用法 (Extended Key Usage: EKU)
EKUは、X.509証明書の拡張フィールドの一つで、証明書が使用されるべき特定の目的を示すために用いられます。例えば、ウェブサーバーのSSL/TLS通信のための「サーバー認証」、クライアントがサーバーに対して自身を認証するための「クライアント認証」、ソフトウェアの署名のための「コード署名」などがあります。EKUは、オブジェクト識別子 (OID) と呼ばれる一意の数値列で表現されます。
オブジェクト識別子 (Object Identifier: OID)
OIDは、情報技術のオブジェクトや概念を一意に識別するための階層的な数値識別子です。X.509証明書では、拡張フィールドの種類や、EKUの各用途などを識別するために広く使用されます。例えば、1.3.6.1.5.5.7.3.1
は「サーバー認証」のEKUを表すOIDです。
ASN.1 (Abstract Syntax Notation One)
ASN.1は、データ構造を記述するための標準的な記法です。X.509証明書は、ASN.1で定義された構造に従ってエンコードされます。DER (Distinguished Encoding Rules) は、ASN.1データを一意にエンコードするためのルールセットであり、X.509証明書で一般的に使用されます。
Go言語の crypto/x509
パッケージ
Go言語の標準ライブラリ crypto/x509
は、X.509証明書の解析、生成、検証などの機能を提供します。このパッケージは、TLS (Transport Layer Security) やその他のセキュリティプロトコルにおいて、証明書を扱う上で中心的な役割を果たします。
Certificate
構造体: X.509証明書の情報を保持するGoの構造体です。ExtKeyUsage
フィールドは、この証明書が許可されている拡張キー使用法のリスト(ExtKeyUsage
型の列挙値)を保持します。UnknownExtKeyUsage
フィールドは、GoのExtKeyUsage
列挙値にマッピングできない未知のEKU OIDを保持します。CreateCertificate
関数: 新しいX.509証明書を生成するための関数です。テンプレートとなるCertificate
構造体、公開鍵、署名者(親証明書と秘密鍵)などを引数に取ります。parseCertificate
関数: DERエンコードされた証明書バイト列を解析し、Certificate
構造体に変換する関数です。
技術的詳細
このコミットの主要な技術的変更点は、CreateCertificate
関数が証明書テンプレートの ExtKeyUsage
および UnknownExtKeyUsage
フィールドを適切に処理し、生成される証明書の「拡張キー使用法」拡張にこれらを組み込むようにしたことです。
具体的には、以下の点が改善されています。
-
OIDとExtKeyUsageのマッピングの導入:
extKeyUsageOIDs
という新しいグローバル変数([]struct { extKeyUsage ExtKeyUsage; oid asn1.ObjectIdentifier }
型のスライス)が導入されました。これは、GoのExtKeyUsage
列挙値と対応するASN.1 OIDとの間のマッピングを定義します。これにより、既知のEKUを数値のOIDとGoの型の間で相互変換できるようになります。extKeyUsageFromOID
関数: OIDを受け取り、対応するExtKeyUsage
列挙値と、マッピングが見つかったかどうかを示すブール値を返します。oidFromExtKeyUsage
関数:ExtKeyUsage
列挙値を受け取り、対応するOIDと、マッピングが見つかったかどうかを示すブール値を返します。
-
parseCertificate
関数の改善:- 証明書を解析する際に、
ExtKeyUsage
拡張から読み取った各OIDを、新しいextKeyUsageFromOID
関数を使ってExtKeyUsage
型に変換するように変更されました。 - 以前は
switch
文で各OIDを個別にチェックしていましたが、これによりコードが簡潔になり、新しいEKUが追加された場合でも変更が容易になります。 - Goの既知の
ExtKeyUsage
にマッピングできないOIDは、引き続きUnknownExtKeyUsage
フィールドに追加されます。
- 証明書を解析する際に、
-
buildExtensions
関数の改善:CreateCertificate
関数内で証明書の拡張を構築するbuildExtensions
関数が修正されました。- テンプレートの
ExtKeyUsage
またはUnknownExtKeyUsage
フィールドに値がある場合、oidExtensionExtendedKeyUsage
(EKU拡張のOID) を持つ新しい拡張が追加されるようになりました。 - テンプレートの
ExtKeyUsage
に含まれる各ExtKeyUsage
列挙値は、oidFromExtKeyUsage
関数を使って対応するOIDに変換され、UnknownExtKeyUsage
のOIDと結合されて、ASN.1でエンコードされます。これにより、生成される証明書にテンプレートで指定された全てのEKUが正確に反映されます。 buildExtensions
が返す拡張の最大数を示すコメントが7
から8
に更新され、EKU拡張のためのスペースが確保されました。
-
CreateCertificate
関数のドキュメント更新:CreateCertificate
関数のコメントが更新され、テンプレートからExtKeyUsage
とUnknownExtKeyUsage
フィールドが使用されることが明示されました。
これらの変更により、CreateCertificate
関数は、証明書テンプレートで指定された拡張キー使用法を正確に解釈し、生成される証明書に適用できるようになりました。これは、X.509証明書のセキュリティとポリシー適用において非常に重要な機能です。
コアとなるコードの変更箇所
src/pkg/crypto/x509/x509.go
-
新しいグローバル変数とヘルパー関数の追加:
extKeyUsageOIDs
スライスが追加され、ExtKeyUsage
とそのOIDのマッピングを定義。extKeyUsageFromOID(oid asn1.ObjectIdentifier) (eku ExtKeyUsage, ok bool)
関数が追加。oidFromExtKeyUsage(eku ExtKeyUsage) (oid asn1.ObjectIdentifier, ok bool)
関数が追加。
-
parseCertificate
関数の変更:ExtKeyUsage
拡張の解析部分で、switch
文によるOIDのチェックが削除され、新しく追加されたextKeyUsageFromOID
関数を使用するように変更。
--- a/src/pkg/crypto/x509/x509.go +++ b/src/pkg/crypto/x509/x509.go @@ -865,28 +900,9 @@ func parseCertificate(in *certificate) (*Certificate, error) { } for _, u := range keyUsage { - switch { - case u.Equal(oidExtKeyUsageAny): - out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageAny) - case u.Equal(oidExtKeyUsageServerAuth): - out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageServerAuth) - case u.Equal(oidExtKeyUsageClientAuth): - out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageClientAuth) - case u.Equal(oidExtKeyUsageCodeSigning): - 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): - out.ExtKeyUsage = append(out.ExtKeyUsage, ExtKeyUsageOCSPSigning) - default: + if extKeyUsage, ok := extKeyUsageFromOID(u); ok { + out.ExtKeyUsage = append(out.ExtKeyUsage, extKeyUsage) + } else { out.UnknownExtKeyUsage = append(out.UnknownExtKeyUsage, u) } }
-
buildExtensions
関数の変更:oidExtensionExtendedKeyUsage
の定義が追加。ret
スライスの初期サイズが7
から8
に変更。template.ExtKeyUsage
またはtemplate.UnknownExtKeyUsage
が存在する場合に、EKU拡張を構築する新しいロジックが追加。oidFromExtKeyUsage
を使用してOIDを生成し、ASN.1でエンコード。
--- a/src/pkg/crypto/x509/x509.go +++ b/src/pkg/crypto/x509/x509.go @@ -975,6 +991,7 @@ func reverseBitsInAByte(in byte) byte { var ( oidExtensionSubjectKeyId = []int{2, 5, 29, 14} oidExtensionKeyUsage = []int{2, 5, 29, 15} + oidExtensionExtendedKeyUsage = []int{2, 5, 29, 37} oidExtensionAuthorityKeyId = []int{2, 5, 29, 35} oidExtensionBasicConstraints = []int{2, 5, 29, 19} oidExtensionSubjectAltName = []int{2, 5, 29, 17} @@ -983,7 +1000,7 @@ var ( ) func buildExtensions(template *Certificate) (ret []pkix.Extension, err error) { - ret = make([]pkix.Extension, 7 /* maximum number of elements. */) + ret = make([]pkix.Extension, 8 /* maximum number of elements. */) n := 0 if template.KeyUsage != 0 { @@ -1006,6 +1023,27 @@ func buildExtensions(template *Certificate) (ret []pkix.Extension, err error) { n++ } + if len(template.ExtKeyUsage) > 0 || len(template.UnknownExtKeyUsage) > 0 { + ret[n].Id = oidExtensionExtendedKeyUsage + + var oids []asn1.ObjectIdentifier + for _, u := range template.ExtKeyUsage { + if oid, ok := oidFromExtKeyUsage(u); ok { + oids = append(oids, oid) + } else { + panic("internal error") + }\n+\t\t}\n+\n+\t\toids = append(oids, template.UnknownExtKeyUsage...)\n+\n+\t\tret[n].Value, err = asn1.Marshal(oids)\n+\t\tif err != nil {\n+\t\t\treturn\n+\t\t}\n+\t\tn++\n+\t}\n+\n if template.BasicConstraintsValid { ret[n].Id = oidExtensionBasicConstraints ret[n].Value, err = asn1.Marshal(basicConstraints{template.IsCA, template.MaxPathLen}) @@ -1092,8 +1130,9 @@ func subjectBytes(cert *Certificate) ([]byte, error) { // CreateCertificate creates a new certificate based on a template. The // following members of template are used: SerialNumber, Subject, NotBefore,\n-// NotAfter, KeyUsage, BasicConstraintsValid, IsCA, MaxPathLen, SubjectKeyId,\n-// DNSNames, PermittedDNSDomainsCritical, PermittedDNSDomains.\n+// NotAfter, KeyUsage, ExtKeyUsage, UnknownExtKeyUsage, BasicConstraintsValid,\n+// IsCA, MaxPathLen, SubjectKeyId, DNSNames, PermittedDNSDomainsCritical,\n+// PermittedDNSDomains.\n // // 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
src/pkg/crypto/x509/x509_test.go
-
テストケースの追加:
TestCreateSelfSignedCertificate
関数に、ExtKeyUsage
とUnknownExtKeyUsage
を含むテストケースが追加。- 生成された証明書の
ExtKeyUsage
とUnknownExtKeyUsage
フィールドが、テンプレートで指定された値と一致するかをreflect.DeepEqual
を使って検証するアサーションが追加。
--- a/src/pkg/crypto/x509/x509_test.go +++ b/src/pkg/crypto/x509/x509_test.go @@ -19,6 +19,7 @@ import ( "encoding/hex" "encoding/pem" "math/big" + "reflect" "testing" "time" ) @@ -262,6 +263,9 @@ func TestCreateSelfSignedCertificate(t *testing.T) { {"ECDSA/ECDSA", &ecdsaPriv.PublicKey, ecdsaPriv, true}, } + testExtKeyUsage := []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageServerAuth} + testUnknownExtKeyUsage := []asn1.ObjectIdentifier{[]int{1, 2, 3}, []int{3, 2, 1}} + for _, test := range tests { commonName := "test.example.com" template := Certificate{ @@ -276,6 +280,9 @@ func TestCreateSelfSignedCertificate(t *testing.T) { SubjectKeyId: []byte{1, 2, 3, 4}, KeyUsage: KeyUsageCertSign, + ExtKeyUsage: testExtKeyUsage, + UnknownExtKeyUsage: testUnknownExtKeyUsage, + BasicConstraintsValid: true, IsCA: true, DNSNames: []string{"test.example.com"}, @@ -312,6 +319,14 @@ func TestCreateSelfSignedCertificate(t *testing.T) { t.Errorf("%s: issuer wasn't correctly copied from the template. Got %s, want %s", test.name, cert.Issuer.CommonName, commonName) } + if !reflect.DeepEqual(cert.ExtKeyUsage, testExtKeyUsage) { + t.Errorf("%s: extkeyusage wasn\'t correctly copied from the template. Got %v, want %v", test.name, cert.ExtKeyUsage, testExtKeyUsage) + } + + if !reflect.DeepEqual(cert.UnknownExtKeyUsage, testUnknownExtKeyUsage) { + t.Errorf("%s: unknown extkeyusage wasn\'t correctly copied from the template. Got %v, want %v", test.name, cert.UnknownExtKeyUsage, testUnknownExtKeyUsage) + } + if test.checkSig { err = cert.CheckSignatureFrom(cert) if err != nil {
コアとなるコードの解説
extKeyUsageOIDs
, extKeyUsageFromOID
, oidFromExtKeyUsage
これらの追加は、Goの内部表現である ExtKeyUsage
列挙型と、X.509証明書で実際に使用されるASN.1 OIDとの間の双方向マッピングを効率的に行うためのものです。
extKeyUsageOIDs
は、既知のEKUとその対応するOIDを静的に定義したテーブルです。extKeyUsageFromOID
は、証明書を解析する際に、読み取ったOIDがどのExtKeyUsage
に対応するかをこのテーブルから検索します。これにより、以前の冗長なswitch
文が不要になり、コードの可読性と保守性が向上しました。oidFromExtKeyUsage
は、証明書を生成する際に、GoのExtKeyUsage
から対応するOIDを取得するために使用されます。
これらの関数は、crypto/x509
パッケージがX.509標準に準拠しつつ、Goの型システム内でEKUを扱いやすくするための重要な抽象化レイヤーを提供します。
parseCertificate
の変更
parseCertificate
関数は、DERエンコードされた証明書をGoの Certificate
構造体に変換する役割を担います。この変更により、EKU拡張の解析部分が大幅に簡素化されました。
以前は、EKU拡張に含まれる各OIDに対して、個別の case
文を持つ大きな switch
ステートメントを使用して、対応する ExtKeyUsage
列挙値を out.ExtKeyUsage
に追加していました。この方法は、新しいEKUが追加されるたびに switch
文を更新する必要があり、拡張性に欠けていました。
新しい実装では、extKeyUsageFromOID(u)
を呼び出すことで、OID u
が既知のEKUに対応するかどうかを効率的にチェックします。
- もし対応する
ExtKeyUsage
が見つかれば、それをout.ExtKeyUsage
に追加します。 - 見つからなかった場合(
ok
がfalse
の場合)、そのOIDはUnknownExtKeyUsage
フィールドに追加されます。これにより、Goが直接サポートしていないEKUも保持できるようになり、証明書の完全な情報が失われるのを防ぎます。
buildExtensions
の変更
buildExtensions
関数は、CreateCertificate
関数によって呼び出され、新しい証明書に含めるべきX.509拡張を構築します。このコミットの最も重要な変更点の一つは、ExtKeyUsage
拡張の構築ロジックが追加されたことです。
oidExtensionExtendedKeyUsage
は、EKU拡張の標準OID (2.5.29.37
) を定義します。ret = make([]pkix.Extension, 8 /* maximum number of elements. */)
の変更は、EKU拡張のための追加スペースを確保するためです。- 新しい
if
ブロックは、テンプレートのExtKeyUsage
またはUnknownExtKeyUsage
フィールドにデータがある場合にのみ実行されます。 template.ExtKeyUsage
に含まれる各ExtKeyUsage
列挙値は、oidFromExtKeyUsage
を使用して対応するOIDに変換され、oids
スライスに追加されます。panic("internal error")
は、Goの既知のExtKeyUsage
がextKeyUsageOIDs
テーブルに存在しないという、通常は発生しない内部エラーを示します。template.UnknownExtKeyUsage
に含まれる未知のOIDも、直接oids
スライスに追加されます。- 最終的に、構築された
oids
スライスはasn1.Marshal(oids)
を使用してASN.1 DER形式にエンコードされ、ret[n].Value
に設定されます。これにより、生成される証明書に、テンプレートで指定された全ての既知および未知のEKUが正しく埋め込まれます。
テスト (x509_test.go
) の変更
テストファイルでは、TestCreateSelfSignedCertificate
関数に新しいテストケースが追加されました。
testExtKeyUsage
とtestUnknownExtKeyUsage
というスライスが定義され、それぞれ既知のEKUと未知のEKUのOIDのサンプルデータを含んでいます。- これらのテストデータは、証明書テンプレートの
ExtKeyUsage
とUnknownExtKeyUsage
フィールドに設定されます。 - 証明書が生成された後、
reflect.DeepEqual
を使用して、生成された証明書のExtKeyUsage
とUnknownExtKeyUsage
フィールドが、テンプレートで指定された元の値と完全に一致するかどうかが検証されます。これにより、CreateCertificate
関数がEKUを正しく処理していることが保証されます。
これらの変更は、crypto/x509
パッケージがX.509証明書のEKUをより堅牢かつ正確に処理できるようにするための重要なステップです。
関連リンク
- https://github.com/golang/go/commit/c993ac11bce307c08383164947417c98913da73a
- https://golang.org/cl/6535057 (Go Code Review - CL 6535057)
参考にした情報源リンク
- RFC 5280 - Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile (特に 4.2.1.12. Extended Key Usage 拡張について)
- X.509 - Wikipedia
- Object identifier - Wikipedia
- ASN.1 - Wikipedia
- Go言語 crypto/x509 パッケージドキュメント (コミット当時のバージョンとは異なる可能性がありますが、概念理解に役立ちます)
- Go言語 reflect パッケージドキュメント (テストで使用されている
DeepEqual
について)