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

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

このコミットは、Go言語の標準ライブラリ crypto/x509 パッケージにおける、X.509証明書の「名前制約 (Name Constraints)」の検証ロジックの修正に関するものです。具体的には、複数の名前制約が設定されている場合に、それらの制約が「論理和 (disjunction)」として評価されるべきであるにもかかわらず、以前の実装では「論理積 (conjunction)」として評価されていた問題を修正しています。

コミット

commit 7d9acff7511c85ee6d187bdb33b3237f0c04df55
Author: Adam Langley <agl@golang.org>
Date:   Mon Oct 21 19:01:24 2013 -0400

    crypto/x509: name constraints should be a disjunction.
    
    The code was requiring that all constraints be met, but it should be
    satisfied by meeting *any* of them.
    
    R=golang-dev, bradfitz, r
    CC=golang-dev
    https://golang.org/cl/15570044

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

https://github.com/golang/go/commit/7d9acff7511c85ee6d187bdb33b3237f0c04df55

元コミット内容

crypto/x509: name constraints should be a disjunction.

The code was requiring that all constraints be met, but it should be satisfied by meeting *any* of them.

R=golang-dev, bradfitz, r CC=golang-dev https://golang.org/cl/15570044

変更の背景

X.509証明書には、その証明書が発行できる下位証明書やエンドエンティティ証明書の名前空間を制限するための「名前制約 (Name Constraints)」という拡張機能があります。これは、認証局 (CA) が特定のドメインやIPアドレス範囲にのみ証明書を発行することを許可する、といったポリシーを強制するために使用されます。

このコミット以前のGoの crypto/x509 パッケージの実装では、証明書に複数の名前制約(例えば、PermittedDNSDomains フィールドに複数のドメインがリストされている場合)が設定されている場合、それら全ての制約が満たされる必要がある、つまり「論理積 (AND)」として評価されていました。

しかし、X.509の仕様(RFC 5280など)では、複数の名前制約が設定されている場合、それらの制約のいずれか一つでも満たされれば良い、つまり「論理和 (OR)」として評価されるべきであると規定されています。この実装の不一致は、正当な証明書が誤って無効と判断される可能性があり、相互運用性の問題や、特定の証明書チェーンの検証失敗につながる可能性がありました。

このコミットは、この仕様との不一致を解消し、名前制約の評価ロジックをX.509の標準に準拠させることを目的としています。

前提知識の解説

X.509 証明書とPKI

X.509は、公開鍵基盤 (PKI) における公開鍵証明書の標準フォーマットです。WebサイトのHTTPS通信、電子メールの署名、コード署名など、様々なセキュリティプロトコルで利用されています。X.509証明書は、公開鍵と、その公開鍵の所有者に関する情報(ドメイン名、組織名など)を、信頼された第三者である認証局 (CA) がデジタル署名したものです。

証明書チェーンと検証

証明書は通常、単独で信頼されるのではなく、信頼の階層(証明書チェーン)を形成します。エンドエンティティ証明書は中間CAによって署名され、中間CA証明書はさらに上位のCAによって署名され、最終的に自己署名されたルートCA証明書にたどり着きます。証明書の検証プロセスでは、このチェーンをルートCAまで遡り、各証明書の署名が有効であること、有効期限内であること、失効していないこと、そして各種制約(名前制約など)が満たされていることを確認します。

名前制約 (Name Constraints)

名前制約は、X.509証明書の拡張フィールドの一つで、主に中間CA証明書に設定されます。この拡張は、そのCAが発行できる下位証明書やエンドエンティティ証明書に含まれる名前(DNS名、IPアドレス、メールアドレスなど)の範囲を制限するために使用されます。

名前制約には主に以下の2種類があります。

  • Permitted Subtrees (許可されたサブツリー): 証明書に含まれる名前が、指定された名前空間のいずれかに属していなければならないことを示します。例えば、DNS名の場合、「.example.com」と指定されていれば、「www.example.com」や「mail.example.com」は許可されますが、「www.another.com」は許可されません。
  • Excluded Subtrees (除外されたサブツリー): 証明書に含まれる名前が、指定された名前空間のいずれにも属してはならないことを示します。これは、許可されたサブツリー内でさらに細かい除外ルールを設定する場合に有用です。

本コミットで修正されているのは PermittedDNSDomains であり、これは Permitted Subtrees のDNS名に関する制約に該当します。

論理積 (Conjunction) と 論理和 (Disjunction)

  • 論理積 (AND): 複数の条件がある場合、全ての条件が真である場合にのみ結果が真となる論理演算です。
  • 論理和 (OR): 複数の条件がある場合、いずれかの条件が真であれば結果が真となる論理演算です。

名前制約の文脈では、複数の PermittedDNSDomains が設定されている場合、その証明書が検証される対象のDNS名が、リストされているいずれかのドメイン制約を満たせば良い、というのがX.509の仕様です。つまり、domain1 OR domain2 OR domain3 のように論理和で評価されるべきです。

技術的詳細

このコミットは、src/pkg/crypto/x509/verify.go ファイル内の Certificate.isValid メソッドにおける PermittedDNSDomains の検証ロジックを変更しています。

変更前のコードでは、c.PermittedDNSDomains に含まれる各ドメインに対してループを回し、opts.DNSName がそのドメイン制約を満たさない場合に即座にエラーを返していました。これは、全てのドメイン制約を満たす必要がある、という論理積の挙動に相当します。

// 変更前のロジックの擬似コード
if len(c.PermittedDNSDomains) > 0 {
    for _, domain := range c.PermittedDNSDomains {
        if !matches(opts.DNSName, domain) { // ここでマッチしないと即エラー
            return CertificateInvalidError{c, CANotAuthorizedForThisName}
        }
    }
}

変更後のコードでは、ok というブーリアン変数を導入し、ループ内でいずれかのドメイン制約が満たされた場合に oktrue に設定し、ループを break します。ループが終了した後で okfalse のままであれば、どのドメイン制約も満たされなかったことになるため、その場合にのみエラーを返します。これにより、論理和の評価が実現されます。

// 変更後のロジックの擬似コード
if len(c.PermittedDNSDomains) > 0 {
    ok := false // いずれかの制約が満たされたかを示すフラグ
    for _, domain := range c.PermittedDNSDomains {
        if matches(opts.DNSName, domain) { // いずれか一つでもマッチすれば
            ok = true
            break // ループを抜ける
        }
    }

    if !ok { // ループ終了後、一つもマッチしなかった場合のみエラー
        return CertificateInvalidError{c, CANotAuthorizedForThisName}
    }
}

この変更により、Goの crypto/x509 パッケージは、X.509の名前制約の評価に関して、より標準に準拠した振る舞いをするようになります。

また、src/pkg/crypto/x509/verify_test.go には、この変更を検証するための包括的なテストケースが追加されています。特に、複数の名前制約を持つ中間証明書が正しく検証されることを確認する新しいテストが追加されており、これが変更の正当性を保証しています。

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

src/pkg/crypto/x509/verify.goCertificate.isValid メソッド内:

--- a/src/pkg/crypto/x509/verify.go
+++ b/src/pkg/crypto/x509/verify.go
@@ -154,14 +154,18 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
 	}\n 
 	if len(c.PermittedDNSDomains) > 0 {
+		ok := false
 		for _, domain := range c.PermittedDNSDomains {
 			if opts.DNSName == domain ||
 				(strings.HasSuffix(opts.DNSName, domain) &&
 					len(opts.DNSName) >= 1+len(domain) &&
 					opts.DNSName[len(opts.DNSName)-len(domain)-1] == '.') {
-				continue
+				ok = true
+				break
 			}
+		}
 
+		if !ok {
 			return CertificateInvalidError{c, CANotAuthorizedForThisName}
 		}
 	}

src/pkg/crypto/x509/verify_test.go には、この変更を検証するための新しいテストケースが追加されています。

コアとなるコードの解説

変更の核心は、PermittedDNSDomains のループ処理に ok フラグと break ステートメントを導入した点です。

  • ok := false: ループに入る前に ok フラグを false で初期化します。これは、まだどのドメイン制約も満たされていない状態を示します。
  • if opts.DNSName == domain || (strings.HasSuffix(opts.DNSName, domain) && ...): この条件は、検証対象のDNS名 (opts.DNSName) が現在の domain 制約に合致するかどうかをチェックします。strings.HasSuffixlen(opts.DNSName) >= 1+len(domain) && opts.DNSName[len(opts.DNSName)-len(domain)-1] == '.' の部分は、サブドメインのマッチング(例: secure.iddl.vt.edu.vt.edu にマッチするか)を正確に行うためのロジックです。
  • ok = true; break: もし opts.DNSName が現在の domain 制約に合致した場合、ok フラグを true に設定し、break でループを即座に終了します。これは、「いずれか一つでも満たされれば良い」という論理和の性質を反映しています。
  • if !ok { return CertificateInvalidError{c, CANotAuthorizedForThisName} }: ループが終了した後、okfalse のままであれば、それは PermittedDNSDomains のリスト内のどのドメイン制約も満たされなかったことを意味します。この場合にのみ、CANotAuthorizedForThisName エラーを返して検証を失敗させます。

このシンプルな変更により、GoのX.509証明書検証ロジックが、名前制約の評価においてX.509標準に準拠するようになりました。

関連リンク

参考にした情報源リンク