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

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

コミット

commit 5b5d3efcf3003de01e0cd8ecb248c9ef72d30b10
Author: Adam Langley <agl@golang.org>
Date:   Mon Jan 21 11:25:28 2013 -0500

    crypto/x509: return a better error when we fail to load system roots.
    
    R=golang-dev, krautz, rsc
    CC=golang-dev
    https://golang.org/cl/7157044

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

https://github.com/golang/go/commit/5b5d3efcf3003de01e0cd8ecb248c9ef72d30b10

元コミット内容

crypto/x509: return a better error when we fail to load system roots.

このコミットは、Go言語のcrypto/x509パッケージにおいて、システムルート証明書の読み込みに失敗した場合に、より適切なエラーを返すように変更するものです。

変更の背景

Go言語のcrypto/x509パッケージは、X.509証明書の検証を扱います。証明書の検証プロセスにおいて、信頼されたルート証明書(システムルート証明書)は非常に重要な役割を果たします。これらは、証明書チェーンの信頼の基点となり、中間証明書やエンドエンティティ証明書が正当なものであることを検証するために使用されます。

このコミット以前は、システムルート証明書の読み込みに失敗した場合、エラーハンドリングが不明瞭であったり、一般的なエラーが返されたりしていました。これにより、開発者は何が問題であったのかを特定しにくく、デバッグが困難になる可能性がありました。

この変更の背景には、以下の点が挙げられます。

  • エラーメッセージの改善: システムルート証明書の読み込み失敗という特定の状況に対して、より明確で具体的なエラーメッセージを提供することで、問題の診断と解決を容易にする。
  • デバッグの効率化: 開発者がエラーの原因を迅速に特定できるようにすることで、開発効率を向上させる。
  • 堅牢性の向上: 証明書検証の重要な部分であるシステムルートの読み込み失敗を、より適切に扱うことで、システムの堅牢性を高める。

特に、異なるOS環境(Darwin, Plan9, Unix, Windowsなど)でシステムルート証明書の取得方法が異なるため、それぞれの環境でのエラーハンドリングを改善する必要がありました。

前提知識の解説

X.509証明書とPKI

X.509は、公開鍵証明書の標準フォーマットを定義するITU-Tの標準です。公開鍵基盤(PKI: Public Key Infrastructure)において、エンティティの公開鍵とそのエンティティの身元を結びつけるために使用されます。X.509証明書には、公開鍵、所有者の識別情報、発行者の識別情報、有効期間、発行者のデジタル署名などが含まれます。

ルート証明書と証明書チェーン

  • ルート証明書: 自己署名された証明書で、PKIにおける信頼のアンカー(信頼の基点)となります。オペレーティングシステムやブラウザには、信頼されたルート証明書があらかじめインストールされています。
  • 中間証明書: ルート証明書によって署名された証明書、または別の中間証明書によって署名された証明書です。エンドエンティティ証明書とルート証明書の間をつなぐ役割を果たします。
  • 証明書チェーン: エンドエンティティ証明書から始まり、中間証明書を介してルート証明書に至るまでの一連の証明書のことです。証明書チェーンの各証明書は、その上位の証明書によって署名されており、最終的に信頼されたルート証明書にたどり着くことで、エンドエンティティ証明書の正当性が検証されます。

crypto/x509パッケージ

Go言語の標準ライブラリに含まれるcrypto/x509パッケージは、X.509証明書の解析、生成、検証などの機能を提供します。このパッケージは、TLS/SSL通信やコード署名など、様々なセキュリティ関連のアプリケーションで利用されます。

CertPool

CertPoolは、crypto/x509パッケージ内で使用される、証明書の集合を管理するためのデータ構造です。特に、信頼されたルート証明書や中間証明書を格納し、証明書チェーンの検証時に利用されます。AppendCertsFromPEMメソッドは、PEM形式の証明書データをCertPoolに追加するために使用されます。

PEM形式

PEM (Privacy-Enhanced Mail) は、X.509証明書や秘密鍵などの暗号化データをASCII形式で表現するための一般的なエンコーディング形式です。通常、-----BEGIN CERTIFICATE----------END CERTIFICATE-----のようなヘッダーとフッターで囲まれたBase64エンコードされたデータで構成されます。

技術的詳細

このコミットの主要な変更点は、システムルート証明書の読み込みに失敗した場合に、SystemRootsErrorという新しいエラー型を導入し、それを返すようにしたことです。

以前のバージョンでは、システムルート証明書の読み込みが失敗した場合、systemRoots変数がnilのままになるか、あるいは不適切なエラーが返される可能性がありました。これにより、Verifyメソッドが呼び出された際に、opts.Rootsnilであるにもかかわらず、その原因がシステムルートの読み込み失敗であるという情報が失われていました。

新しいSystemRootsError型は、この特定の失敗シナリオを明示的に表現します。

// SystemRootsError results when we fail to load the system root certificates.
type SystemRootsError struct {
}

func (e SystemRootsError) Error() string {
	return "x509: failed to load system roots and no roots provided"
}

verify.goVerifyメソッド内で、opts.Rootsnilの場合(つまり、呼び出し元が明示的にルート証明書を指定しなかった場合)、systemRootsPool()関数が呼び出されます。このsystemRootsPool()nilを返した場合(システムルートの読み込みに失敗した場合)、新しく導入されたSystemRootsError{}が返されるようになりました。

	if opts.Roots == nil {
		opts.Roots = systemRootsPool()
		if opts.Roots == nil {
			return nil, SystemRootsError{}
		}
	}

また、各OS固有のroot_darwin.go, root_plan9.go, root_unix.go, root_windows.goファイルにおいて、initSystemRoots()関数がシステムルートの読み込みに失敗した場合に、systemRootsnilのままになるように変更されています。これにより、verify.goでのSystemRootsErrorの検出が正しく機能するようになります。

例えば、root_plan9.goroot_unix.goでは、ルート証明書ファイルの読み込みループが全て失敗した場合に、systemRootsが設定されない(つまりnilのままになる)ように変更されています。

	// All of the files failed to load. systemRoots will be nil which will
	// trigger a specific error at verification time.

テストファイルverify_test.goには、SystemRootsErrorが正しく返されることを検証するための新しいテストケースが追加されています。testSystemRootsErrorフラグとexpectSystemRootsErrorコールバックが導入され、システムルートが意図的にnilに設定された状態で検証を行い、期待されるエラーが返されることを確認しています。

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

このコミットで変更された主要なファイルとコードの変更箇所は以下の通りです。

  • src/pkg/crypto/x509/root_darwin.go: initSystemRoots関数内で、C.FetchPEMRootsがエラーを返した場合の処理を修正し、datanilの場合に早期リターンするように変更。
  • src/pkg/crypto/x509/root_plan9.go: initSystemRoots関数内で、全てのルート証明書ファイルの読み込みに失敗した場合にsystemRootsnilのままになるように修正。
  • src/pkg/crypto/x509/root_stub.go: initSystemRoots関数からsystemRoots = NewCertPool()の行を削除。これにより、スタブ実装ではsystemRootsがデフォルトでnilになる。
  • src/pkg/crypto/x509/root_unix.go: initSystemRoots関数内で、全てのルート証明書ファイルの読み込みに失敗した場合にsystemRootsnilのままになるように修正。
  • src/pkg/crypto/x509/root_windows.go: initSystemRoots関数からsystemRoots = NewCertPool()の行を削除。これにより、Windows環境でもシステムルートの読み込みが失敗した場合にsystemRootsnilになる。
  • src/pkg/crypto/x509/verify.go:
    • SystemRootsErrorという新しいエラー型を定義。
    • Certificate.Verifyメソッド内で、opts.RootsnilかつsystemRootsPool()nilを返した場合にSystemRootsError{}を返すように変更。
  • src/pkg/crypto/x509/verify_test.go:
    • verifyTest構造体にtestSystemRootsErrorフィールドを追加。
    • expectSystemRootsErrorという新しいエラーコールバック関数を追加。
    • SystemRootsErrorが正しく発生することを確認するための新しいテストケースを追加。
    • テスト実行時にsystemRootsを一時的にnilに設定し、テスト後に元に戻すロジックを追加。

コアとなるコードの解説

src/pkg/crypto/x509/verify.go

// SystemRootsError results when we fail to load the system root certificates.
type SystemRootsError struct {
}

func (e SystemRootsError) Error() string {
	return "x509: failed to load system roots and no roots provided"
}

// ... (中略) ...

func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error) {
	// ... (中略) ...

	if opts.Roots == nil {
		opts.Roots = systemRootsPool()
		if opts.Roots == nil {
			return nil, SystemRootsError{}
		}
	}

	// ... (後略) ...
}

この部分が変更の核心です。

  1. SystemRootsErrorという空の構造体と、そのError()メソッドを定義しています。このエラーメッセージは、システムルートの読み込みに失敗し、かつ検証オプションでルートが提供されていない場合に表示されます。
  2. Certificate.Verifyメソッド内で、opts.Rootsnil(つまり、検証時に明示的なルート証明書プールが指定されていない)場合、systemRootsPool()を呼び出してシステムルート証明書プールを取得しようとします。
  3. もしsystemRootsPool()nilを返した場合(これはシステムルートの読み込みに失敗したことを意味します)、nilの証明書チェーンとSystemRootsError{}を返します。これにより、システムルートの読み込み失敗という具体的なエラーが呼び出し元に伝達されるようになります。

src/pkg/crypto/x509/root_plan9.go (他のOS固有ファイルも同様のロジック)

func initSystemRoots() {
	roots := NewCertPool()
	for _, file := range certFiles {
		data, err := ioutil.ReadFile(file)
		if err == nil {
			roots.AppendCertsFromPEM(data)
			systemRoots = roots // 成功した場合のみ設定
			return // 成功したらすぐに終了
		}
	}

	// All of the files failed to load. systemRoots will be nil which will
	// trigger a specific error at verification time.
	// ここに到達した場合、systemRootsはnilのまま
}

このコードは、Plan9システムにおけるシステムルート証明書の初期化ロジックを示しています。

  • certFilesリスト内の各証明書ファイルを順番に読み込もうとします。
  • いずれかのファイルの読み込みとPEM形式からの証明書追加に成功した場合、そのrootssystemRootsに設定し、関数を終了します。
  • 重要な変更点: 全てのファイルの読み込みに失敗した場合、以前はsystemRoots = rootsという行がループの外にあり、空のCertPoolが設定されていました。しかし、この変更により、全てのファイルの読み込みに失敗した場合はsystemRootsnilのままになります。これにより、verify.gosystemRootsPool()nilを返し、SystemRootsErrorがトリガーされるようになります。

src/pkg/crypto/x509/verify_test.go

type verifyTest struct {
	// ... (既存のフィールド) ...
	testSystemRootsError bool // 新しく追加されたフィールド
}

// ... (中略) ...

func expectSystemRootsError(t *testing.T, i int, err error) bool {
	if _, ok := err.(SystemRootsError); !ok {
		t.Errorf("#%d: error was not SystemRootsError: %s", i, err)
		return false
	}
	return true
}

var verifyTests = []verifyTest{
	{
		leaf:                 googleLeaf,
		intermediates:        []string{thawteIntermediate},
		currentTime:          1302726541,
		dnsName:              "www.google.com",
		testSystemRootsError: true, // このテストケースでSystemRootsErrorを期待する
		systemSkip:           true,

		// Without any roots specified we should get a system roots
		// error.
		errorCallback: expectSystemRootsError,
	},
	// ... (既存のテストケース) ...
}

func testVerify(t *testing.T, useSystemRoots bool) {
	// ... (中略) ...

	if test.testSystemRootsError {
		oldSystemRoots = systemRootsPool() // 現在のsystemRootsを保存
		systemRoots = nil                 // systemRootsをnilに設定してエラーを強制
		opts.Roots = nil                  // 明示的なルートもnilに
	}

	chains, err := leaf.Verify(opts)

	if test.testSystemRootsError {
		systemRoots = oldSystemRoots // テスト後にsystemRootsを元に戻す
	}

	// ... (後略) ...
}

テストコードの変更は、この機能の検証方法を示しています。

  • verifyTest構造体にtestSystemRootsErrorというブール値のフィールドが追加され、特定のテストケースでシステムルートエラーを期待するかどうかを示します。
  • expectSystemRootsError関数は、返されたエラーがSystemRootsError型であるかどうかをチェックするヘルパー関数です。
  • 新しいテストケースでは、testSystemRootsError: trueが設定され、errorCallbackとしてexpectSystemRootsErrorが指定されています。
  • testVerify関数内で、testSystemRootsErrortrueの場合、現在のsystemRootsを一時的に保存し、systemRootsnilに設定します。これにより、Verifyメソッドがシステムルートを読み込めない状況をシミュレートし、SystemRootsErrorが返されることを強制します。テストの終了後、systemRootsは元の値に戻されます。

これらの変更により、Goのcrypto/x509パッケージは、システムルート証明書の読み込み失敗という重要なエラーシナリオをより明確に報告できるようになり、デバッグとエラーハンドリングが大幅に改善されました。

関連リンク

参考にした情報源リンク