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

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

このコミットは、Go言語のcrypto/x509パッケージにおいて、X.509証明書の署名に使用されるデフォルトのハッシュアルゴリズムをSHA-1からSHA-256に変更し、さらに呼び出し元が署名アルゴリズムを明示的に指定できるようにする機能を追加したものです。これは、SHA-1のセキュリティ上の懸念と、MicrosoftによるX.509証明書におけるSHA-1の非推奨化ポリシーに対応するための重要な変更です。

コミット

commit ca3ff9251dbe34edb539b661a30222d0f3d755bd
Author: Adam Langley <agl@golang.org>
Date:   Wed Dec 18 10:57:56 2013 -0500

    crypto/x509: set default signature hash to SHA256 and allow override.
    
    Previously the hash used when signing an X.509 certificate was fixed
    and, for RSA, it was fixed to SHA1. Since Microsoft have announced the
    deprecation of SHA1 in X.509 certificates, this change switches the
    default to SHA256.
    
    It also allows the hash function to be controlled by the caller by
    setting the SignatureAlgorithm field of the template.
    
    [1] http://blogs.technet.com/b/pki/archive/2013/11/12/sha1-deprecation-policy.aspx
    
    Fixes #5302.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/40720047

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

https://github.com/golang/go/commit/ca3ff9251dbe34edb539b661a30222d0f3d755bd

元コミット内容

crypto/x509: set default signature hash to SHA256 and allow override.

以前は、X.509証明書に署名する際に使用されるハッシュは固定されており、RSAの場合、SHA-1に固定されていました。MicrosoftがX.509証明書におけるSHA-1の非推奨化を発表したため、この変更はデフォルトをSHA-256に切り替えます。

また、テンプレートのSignatureAlgorithmフィールドを設定することで、呼び出し元がハッシュ関数を制御できるようにします。

[1] http://blogs.technet.com/b/pki/archive/2013/11/12/sha1-deprecation-policy.aspx

Fixes #5302.

変更の背景

この変更の主な背景は、SHA-1ハッシュアルゴリズムのセキュリティ上の脆弱性と、それに対応する業界の動向です。

  1. SHA-1の脆弱性: SHA-1(Secure Hash Algorithm 1)は、かつて広く使用されていた暗号学的ハッシュ関数ですが、2005年頃から衝突攻撃(異なる入力から同じハッシュ値が生成されること)の可能性が指摘され始め、セキュリティ上の懸念が高まっていました。実際に、2017年にはSHA-1に対する実用的な衝突攻撃が成功したことがGoogleによって発表されています。このような脆弱性があるハッシュ関数をデジタル署名に使用することは、署名の偽造や証明書のなりすましのリスクを高めます。

  2. MicrosoftによるSHA-1非推奨化ポリシー: コミットメッセージに記載されているように、Microsoftは2013年11月12日に、X.509証明書におけるSHA-1の非推奨化ポリシーを発表しました。このポリシーは、2017年1月1日以降、SHA-1で署名されたSSL/TLS証明書を主要なWebブラウザ(Internet Explorer、Edge)で信頼しないことを示唆していました。これにより、SHA-1を使用しているWebサイトやサービスは、互換性の問題やセキュリティ警告に直面する可能性がありました。

Go言語のcrypto/x509パッケージは、X.509証明書の生成と検証を扱うため、この業界の動向に追従し、よりセキュアなハッシュアルゴリズムへの移行を促進する必要がありました。特に、RSA鍵を用いた署名においてデフォルトでSHA-1が使用されていたため、この部分の変更が急務でした。

このコミットは、デフォルトのハッシュアルゴリズムをSHA-256に切り替えることで、Goアプリケーションが生成する証明書のセキュリティを向上させ、将来的な互換性問題を回避することを目的としています。また、開発者が特定の要件に応じて署名アルゴリズムを明示的に選択できる柔軟性も提供しています。

前提知識の解説

このコミットを理解するためには、以下の暗号学およびPKI(公開鍵基盤)に関する基本的な知識が必要です。

  1. X.509証明書:

    • X.509は、公開鍵証明書の標準フォーマットです。デジタル証明書は、公開鍵と、その公開鍵の所有者(エンティティ)の身元情報を結びつける役割を果たします。
    • WebサイトのSSL/TLS通信、コード署名、電子メールの暗号化など、様々なセキュリティプロトコルで利用されます。
    • 証明書は、信頼された第三者である認証局(CA)によってデジタル署名されます。この署名により、証明書の改ざんが検出され、その正当性が保証されます。
  2. デジタル署名:

    • デジタル署名は、メッセージや文書の真正性、完全性、否認防止を保証するための暗号技術です。
    • 仕組み:
      1. 署名者は、署名したいデータ(この場合はX.509証明書の内容)のハッシュ値を計算します。
      2. 計算されたハッシュ値を、署名者の秘密鍵で暗号化(署名)します。
      3. 署名されたハッシュ値(デジタル署名)を元のデータに添付して送信します。
      4. 検証者は、元のデータからハッシュ値を再計算します。
      5. 検証者は、署名者の公開鍵を使用してデジタル署名を復号し、元のハッシュ値を取得します。
      6. 両方のハッシュ値が一致すれば、データは改ざんされておらず、署名者が署名したことが確認できます。
    • 重要性: X.509証明書の場合、CAが証明書に署名することで、その証明書がCAによって発行された正当なものであることを保証します。
  3. ハッシュ関数(暗号学的ハッシュ関数):

    • 任意の長さの入力データから、固定長の短い出力(ハッシュ値、メッセージダイジェスト、フィンガープリントとも呼ばれる)を生成する一方向関数です。
    • 暗号学的ハッシュ関数の特性:
      • 一方向性: ハッシュ値から元のデータを復元することが計算上困難である。
      • 衝突耐性: 異なる入力から同じハッシュ値が生成されること(衝突)が計算上困難である。
      • 原像計算困難性: 特定のハッシュ値を持つ入力を見つけることが計算上困難である。
      • 第二原像計算困難性: 特定の入力と同じハッシュ値を持つ別の入力を見つけることが計算上困難である。
    • SHA-1 (Secure Hash Algorithm 1): 160ビットのハッシュ値を生成します。かつて広く使われましたが、衝突攻撃の脆弱性が発見され、現在では非推奨とされています。
    • SHA-2 (Secure Hash Algorithm 2): SHA-256, SHA-384, SHA-512など、複数のバリエーションがあります。それぞれ異なる長さのハッシュ値を生成し、SHA-1よりも高いセキュリティを提供します。SHA-256は256ビットのハッシュ値を生成し、現在広く推奨されているハッシュ関数の一つです。
  4. 公開鍵アルゴリズム (RSA, ECDSA):

    • デジタル署名に使用される主要な公開鍵暗号アルゴリズムです。
    • RSA: Rivest–Shamir–Adlemanの略。最も広く使用されている公開鍵暗号アルゴリズムの一つで、デジタル署名と鍵交換の両方に使用されます。
    • ECDSA (Elliptic Curve Digital Signature Algorithm): 楕円曲線暗号に基づくデジタル署名アルゴリズムです。RSAよりも短い鍵長で同等のセキュリティレベルを提供できるため、モバイルデバイスなどリソースが限られた環境での利用が増えています。

これらの要素が組み合わさって、X.509証明書の信頼性とセキュリティが確立されます。このコミットは、その中でも特にデジタル署名におけるハッシュ関数の選択という、セキュリティの根幹に関わる部分を改善しています。

技術的詳細

このコミットは、Go言語のcrypto/x509パッケージにおける証明書署名ロジックを修正し、以下の主要な変更を導入しています。

  1. デフォルト署名ハッシュのSHA-256への変更:

    • 以前は、CreateCertificate関数内でRSA秘密鍵が使用される場合、署名アルゴリズムはoidSignatureSHA1WithRSA(SHA1WithRSA)に、ハッシュ関数はcrypto.SHA1に固定されていました。
    • このコミットにより、RSA秘密鍵の場合のデフォルトがoidSignatureSHA256WithRSA(SHA256WithRSA)とcrypto.SHA256に変更されました。これにより、明示的な指定がない限り、よりセキュアなSHA-256が使用されるようになります。
  2. SignatureAlgorithmフィールドによる署名アルゴリズムのオーバーライド:

    • Certificate構造体にSignatureAlgorithmという新しいフィールドが追加され、証明書テンプレート作成時に署名アルゴリズムを明示的に指定できるようになりました。
    • CreateCertificate関数内で、template.SignatureAlgorithmが0(デフォルト値)でない場合、ユーザーが指定した署名アルゴリズムが優先されます。
    • 指定されたSignatureAlgorithmが、使用される秘密鍵のタイプ(RSA, ECDSAなど)と互換性があるかどうかの検証が追加されました。例えば、RSA鍵でECDSA署名アルゴリズムを指定することはできません。
    • 指定された署名アルゴリズムに対応するハッシュ関数が利用可能かどうかもチェックされます。
  3. 署名アルゴリズム詳細のテーブル化:

    • signatureAlgorithmDetailsという新しいグローバル変数([]structのスライス)が導入されました。これは、各SignatureAlgorithmに対応するOID(Object Identifier)、公開鍵アルゴリズムのタイプ、および使用されるcrypto.Hash関数をマッピングするテーブルです。
    • これにより、getSignatureAlgorithmFromOID関数内の冗長なswitch文が、このテーブルをループして検索する形に置き換えられ、コードの可読性と保守性が向上しました。新しい署名アルゴリズムが追加された場合も、このテーブルにエントリを追加するだけで済みます。
  4. テストケースの追加と修正:

    • TestCreateSelfSignedCertificateテスト関数が修正され、SignatureAlgorithmフィールドを明示的に設定するテストケースが追加されました。これにより、新しい機能が正しく動作すること、およびデフォルトの変更が意図通りであることを確認できます。
    • テスト構造体testsigAlgo SignatureAlgorithmフィールドが追加され、各テストケースで異なる署名アルゴリズムを指定できるようになりました。
    • 生成された証明書のSignatureAlgorithmがテンプレートで指定されたものと一致するかどうかの検証が追加されました。

これらの変更により、Goのcrypto/x509パッケージは、より現代的でセキュアな暗号学的プラクティスに準拠し、開発者により柔軟な制御を提供できるようになりました。

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

src/pkg/crypto/x509/x509.go

  1. signatureAlgorithmDetails テーブルの追加:

    var signatureAlgorithmDetails = []struct {
    	algo       SignatureAlgorithm
    	oid        asn1.ObjectIdentifier
    	pubKeyAlgo PublicKeyAlgorithm
    	hash       crypto.Hash
    }{
    	{MD2WithRSA, oidSignatureMD2WithRSA, RSA, crypto.Hash(0) /* no value for MD2 */},
    	{MD5WithRSA, oidSignatureMD5WithRSA, RSA, crypto.MD5},
    	{SHA1WithRSA, oidSignatureSHA1WithRSA, RSA, crypto.SHA1},
    	{SHA256WithRSA, oidSignatureSHA256WithRSA, RSA, crypto.SHA256}, // <-- SHA256WithRSAが追加
    	{SHA384WithRSA, oidSignatureSHA384WithRSA, RSA, crypto.SHA384},
    	{SHA512WithRSA, oidSignatureSHA512WithRSA, RSA, crypto.SHA512},
    	{DSAWithSHA1, oidSignatureDSAWithSHA1, DSA, crypto.SHA1},
    	{DSAWithSHA256, oidSignatureDSAWithSHA256, DSA, crypto.SHA256},
    	{ECDSAWithSHA1, oidSignatureECDSAWithSHA1, ECDSA, crypto.SHA1},
    	{ECDSAWithSHA256, oidSignatureECDSAWithSHA256, ECDSA, crypto.SHA256},
    	{ECDSAWithSHA384, oidSignatureECDSAWithSHA384, ECDSA, crypto.SHA384},
    	{ECDSAWithSHA512, oidSignatureECDSAWithSHA512, ECDSA, crypto.SHA512},
    }
    
  2. getSignatureAlgorithmFromOID 関数の簡素化: switch文がsignatureAlgorithmDetailsテーブルのループ検索に置き換えられました。

    func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) SignatureAlgorithm {
    	for _, details := range signatureAlgorithmDetails {
    		if oid.Equal(details.oid) {
    			return details.algo
    		}
    	}
    	return UnknownSignatureAlgorithm
    }
    
  3. CreateCertificate 関数におけるデフォルトハッシュの変更とオーバーライドロジックの追加:

    • RSA秘密鍵の場合のデフォルトハッシュがSHA-1からSHA-256に変更されました。
    • template.SignatureAlgorithmが設定されている場合の処理が追加されました。
    func CreateCertificate(rand io.Reader, template, parent *Certificate, pub interface{}, priv interface{}) (certBytes []byte, err error) {
    	// ... 既存のコード ...
    
    	var signatureAlgorithm pkix.AlgorithmIdentifier
    	var hashFunc crypto.Hash
    	var privType PublicKeyAlgorithm // <-- 追加: 秘密鍵のタイプを保持
    
    	switch priv := priv.(type) {
    	case *rsa.PrivateKey:
    		privType = RSA // <-- 追加
    		signatureAlgorithm.Algorithm = oidSignatureSHA256WithRSA // <-- SHA256がデフォルトに
    		hashFunc = crypto.SHA256 // <-- SHA256がデフォルトに
    	case *ecdsa.PrivateKey:
    		privType = ECDSA // <-- 追加
    		// ... 既存のECDSAハッシュ選択ロジック ...
    	default:
    		return nil, errors.New("x509: only RSA and ECDSA private keys supported")
    	}
    
    	// <-- ここから新しいロジックの追加 -->
    	if template.SignatureAlgorithm != 0 { // テンプレートで署名アルゴリズムが指定されている場合
    		found := false
    		for _, details := range signatureAlgorithmDetails {
    			if details.algo == template.SignatureAlgorithm {
    				if details.pubKeyAlgo != privType { // 秘密鍵のタイプと指定されたアルゴリズムの互換性チェック
    					return nil, errors.New("x509: requested SignatureAlgorithm does not match private key type")
    				}
    				signatureAlgorithm.Algorithm, hashFunc = details.oid, details.hash
    				if hashFunc == 0 { // ハッシュ関数が利用可能かチェック
    					return nil, errors.New("x509: cannot sign with hash function requested")
    				}
    				found = true
    				break
    			}
    		}
    		if !found { // 未知の署名アルゴリズムの場合
    			return nil, errors.New("x509: unknown SignatureAlgorithm")
    		}
    	}
    	// <-- 新しいロジックの追加ここまで -->
    
    	// ... 既存のコード ...
    }
    

src/pkg/crypto/x509/x509_test.go

  1. テスト構造体 test の変更: sigAlgo SignatureAlgorithmフィールドが追加されました。

    func TestCreateSelfSignedCertificate(t *testing.T) {
    	tests := []struct {
    		name      string
    		pub, priv interface{}
    		checkSig  bool
    		sigAlgo   SignatureAlgorithm // <-- 追加
    	}{
    		{"RSA/RSA", &rsaPriv.PublicKey, rsaPriv, true, SHA1WithRSA}, // <-- sigAlgoの指定
    		{"RSA/ECDSA", &rsaPriv.PublicKey, ecdsaPriv, false, ECDSAWithSHA384}, // <-- sigAlgoの指定
    		{"ECDSA/RSA", &ecdsaPriv.PublicKey, rsaPriv, false, SHA256WithRSA}, // <-- sigAlgoの指定
    		{"ECDSA/ECDSA", &ecdsaPriv.PublicKey, ecdsaPriv, true, ECDSAWithSHA1}, // <-- sigAlgoの指定
    	}
    	// ...
    }
    
  2. 証明書テンプレートへの SignatureAlgorithm の設定:

    func TestCreateSelfSignedCertificate(t *testing.T) {
    	// ...
    	template := Certificate{
    		// ...
    		SignatureAlgorithm: test.sigAlgo, // <-- テンプレートにsigAlgoを設定
    		// ...
    	}
    	// ...
    }
    
  3. 生成された証明書の SignatureAlgorithm の検証:

    func TestCreateSelfSignedCertificate(t *testing.T) {
    	// ...
    	if cert.SignatureAlgorithm != test.sigAlgo {
    		t.Errorf("%s: SignatureAlgorithm wasn't copied from template. Got %s, want %s", test.name, cert.SignatureAlgorithm, test.sigAlgo)
    	}
    	// ...
    }
    

コアとなるコードの解説

src/pkg/crypto/x509/x509.go

  • signatureAlgorithmDetails テーブル: このテーブルは、Goのcrypto/x509パッケージがサポートするすべての署名アルゴリズムに関するメタデータを一元的に管理するために導入されました。各エントリは、SignatureAlgorithmの列挙値、対応するASN.1 Object Identifier (OID)、そのアルゴリズムが使用する公開鍵のタイプ(RSA, ECDSAなど)、そして署名時に使用される暗号学的ハッシュ関数(crypto.Hash)を定義しています。 このテーブルの導入により、以前はgetSignatureAlgorithmFromOID関数内にハードコードされていたOIDとアルゴリズムのマッピングがデータ駆動型になり、コードの重複が排除され、新しいアルゴリズムの追加や既存アルゴリズムの変更が容易になりました。特に、SHA256WithRSAのエントリが追加され、これがRSA署名の新しいデフォルトとして機能するための基盤となります。

  • getSignatureAlgorithmFromOID 関数の変更: 以前は長いswitch文でOIDをSignatureAlgorithmにマッピングしていましたが、この変更によりsignatureAlgorithmDetailsテーブルを線形探索するシンプルなループに置き換えられました。これにより、コードがより簡潔になり、保守性が向上しました。

  • CreateCertificate 関数における署名アルゴリズムの選択ロジック: この関数はX.509証明書を生成し、署名するGoの主要なAPIです。

    1. デフォルトの変更: 最も重要な変更は、RSA秘密鍵が使用される場合のデフォルトの署名アルゴリズムがSHA1WithRSAからSHA256WithRSA(および対応するcrypto.SHA1からcrypto.SHA256)に変更された点です。これにより、明示的に指定しない限り、Goで生成されるRSA署名付き証明書はよりセキュアなSHA-256を使用するようになります。
    2. template.SignatureAlgorithmによるオーバーライド: CertificateテンプレートにSignatureAlgorithmフィールドが追加されたことで、開発者は証明書生成時に特定の署名アルゴリズムを明示的に指定できるようになりました。
      • このフィールドが0(デフォルト値)でない場合、指定されたアルゴリズムが優先されます。
      • 互換性チェック: 指定されたSignatureAlgorithmが、実際に署名に使用される秘密鍵のタイプ(RSA鍵でECDSA署名アルゴリズムを指定するなど)と互換性があるかどうかが厳密にチェックされます。これにより、無効な組み合わせによるエラーを防ぎます。
      • ハッシュ関数利用可能性チェック: 指定された署名アルゴリズムに対応するハッシュ関数がcryptoパッケージでサポートされているかどうかも確認されます。
      • これらのチェックは、証明書生成の堅牢性を高め、開発者が誤った設定を行うことを防ぐための重要なバリデーションです。

src/pkg/crypto/x509/x509_test.go

  • TestCreateSelfSignedCertificate の変更: このテストは、自己署名証明書の生成と検証を行います。
    1. sigAlgo フィールドの追加: テストケースを定義する匿名構造体にsigAlgo SignatureAlgorithmフィールドが追加されました。これにより、各テストシナリオで異なる署名アルゴリズムを試すことが可能になりました。
    2. テンプレートへの設定: 生成される証明書のテンプレートにtest.sigAlgoが設定されるようになりました。これにより、CreateCertificate関数がSignatureAlgorithmフィールドを正しく処理するかどうかがテストされます。
    3. 検証の追加: 生成された証明書のSignatureAlgorithmが、テンプレートで指定されたtest.sigAlgoと一致するかどうかの検証が追加されました。これは、CreateCertificate関数がテンプレートから署名アルゴリズムを正しくコピーしていることを保証します。

これらのコード変更は、GoのX.509証明書処理におけるセキュリティと柔軟性を大幅に向上させるものです。

関連リンク

参考にした情報源リンク