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

[インデックス 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 フィールドを適切に処理し、生成される証明書の「拡張キー使用法」拡張にこれらを組み込むようにしたことです。

具体的には、以下の点が改善されています。

  1. OIDとExtKeyUsageのマッピングの導入:

    • extKeyUsageOIDs という新しいグローバル変数([]struct { extKeyUsage ExtKeyUsage; oid asn1.ObjectIdentifier } 型のスライス)が導入されました。これは、Goの ExtKeyUsage 列挙値と対応するASN.1 OIDとの間のマッピングを定義します。これにより、既知のEKUを数値のOIDとGoの型の間で相互変換できるようになります。
    • extKeyUsageFromOID 関数: OIDを受け取り、対応する ExtKeyUsage 列挙値と、マッピングが見つかったかどうかを示すブール値を返します。
    • oidFromExtKeyUsage 関数: ExtKeyUsage 列挙値を受け取り、対応するOIDと、マッピングが見つかったかどうかを示すブール値を返します。
  2. parseCertificate 関数の改善:

    • 証明書を解析する際に、ExtKeyUsage 拡張から読み取った各OIDを、新しい extKeyUsageFromOID 関数を使って ExtKeyUsage 型に変換するように変更されました。
    • 以前は switch 文で各OIDを個別にチェックしていましたが、これによりコードが簡潔になり、新しいEKUが追加された場合でも変更が容易になります。
    • Goの既知の ExtKeyUsage にマッピングできないOIDは、引き続き UnknownExtKeyUsage フィールドに追加されます。
  3. buildExtensions 関数の改善:

    • CreateCertificate 関数内で証明書の拡張を構築する buildExtensions 関数が修正されました。
    • テンプレートの ExtKeyUsage または UnknownExtKeyUsage フィールドに値がある場合、oidExtensionExtendedKeyUsage (EKU拡張のOID) を持つ新しい拡張が追加されるようになりました。
    • テンプレートの ExtKeyUsage に含まれる各 ExtKeyUsage 列挙値は、oidFromExtKeyUsage 関数を使って対応するOIDに変換され、UnknownExtKeyUsage のOIDと結合されて、ASN.1でエンコードされます。これにより、生成される証明書にテンプレートで指定された全てのEKUが正確に反映されます。
    • buildExtensions が返す拡張の最大数を示すコメントが 7 から 8 に更新され、EKU拡張のためのスペースが確保されました。
  4. CreateCertificate 関数のドキュメント更新:

    • CreateCertificate 関数のコメントが更新され、テンプレートから ExtKeyUsageUnknownExtKeyUsage フィールドが使用されることが明示されました。

これらの変更により、CreateCertificate 関数は、証明書テンプレートで指定された拡張キー使用法を正確に解釈し、生成される証明書に適用できるようになりました。これは、X.509証明書のセキュリティとポリシー適用において非常に重要な機能です。

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

src/pkg/crypto/x509/x509.go

  1. 新しいグローバル変数とヘルパー関数の追加:

    • extKeyUsageOIDs スライスが追加され、ExtKeyUsage とそのOIDのマッピングを定義。
    • extKeyUsageFromOID(oid asn1.ObjectIdentifier) (eku ExtKeyUsage, ok bool) 関数が追加。
    • oidFromExtKeyUsage(eku ExtKeyUsage) (oid asn1.ObjectIdentifier, ok bool) 関数が追加。
  2. 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)
     					}
     				}
    
  3. 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

  1. テストケースの追加:

    • TestCreateSelfSignedCertificate 関数に、ExtKeyUsageUnknownExtKeyUsage を含むテストケースが追加。
    • 生成された証明書の ExtKeyUsageUnknownExtKeyUsage フィールドが、テンプレートで指定された値と一致するかを 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 に追加します。
  • 見つからなかった場合(okfalse の場合)、その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の既知の ExtKeyUsageextKeyUsageOIDs テーブルに存在しないという、通常は発生しない内部エラーを示します。
  • template.UnknownExtKeyUsage に含まれる未知のOIDも、直接 oids スライスに追加されます。
  • 最終的に、構築された oids スライスは asn1.Marshal(oids) を使用してASN.1 DER形式にエンコードされ、ret[n].Value に設定されます。これにより、生成される証明書に、テンプレートで指定された全ての既知および未知のEKUが正しく埋め込まれます。

テスト (x509_test.go) の変更

テストファイルでは、TestCreateSelfSignedCertificate 関数に新しいテストケースが追加されました。

  • testExtKeyUsagetestUnknownExtKeyUsage というスライスが定義され、それぞれ既知のEKUと未知のEKUのOIDのサンプルデータを含んでいます。
  • これらのテストデータは、証明書テンプレートの ExtKeyUsageUnknownExtKeyUsage フィールドに設定されます。
  • 証明書が生成された後、reflect.DeepEqual を使用して、生成された証明書の ExtKeyUsageUnknownExtKeyUsage フィールドが、テンプレートで指定された元の値と完全に一致するかどうかが検証されます。これにより、CreateCertificate 関数がEKUを正しく処理していることが保証されます。

これらの変更は、crypto/x509 パッケージがX.509証明書のEKUをより堅牢かつ正確に処理できるようにするための重要なステップです。

関連リンク

参考にした情報源リンク