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

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

このコミットは、Go言語の標準ライブラリ crypto/tls パッケージにおける、TLS (Transport Layer Security) ハンドシェイク時の暗号スイートの処理に関する重要な修正です。具体的には、Goのマップ(map)のイテレーション順序が保証されないという特性に依存していた部分を修正し、暗号スイートの選択と提示の順序が非決定論的にならないように変更しています。これにより、TLSハンドシェイクの堅牢性と予測可能性が向上します。

コミット

commit 1eb7ca924b184d06706cee78cf56d022ebb1fe5a
Author: Adam Langley <agl@golang.org>
Date:   Mon Nov 28 15:34:16 2011 -0500

    crypto/tls: don't rely on map iteration order.
    
    Previously we were using the map iteration order to set the order of
    the cipher suites in the ClientHello.
    
    R=bradfitz
    CC=golang-dev
    https://golang.org/cl/5440048

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

https://github.com/golang/go/commit/1eb7ca924b184d06706cee78cf56d022ebb1fe5a

元コミット内容

このコミットの元々の内容は、Goの crypto/tls パッケージにおいて、クライアントがサーバーに提示する暗号スイートのリスト(ClientHelloメッセージの一部)の順序が、Goのマップのイテレーション順序に依存していたという問題点を指摘し、それを修正するものです。

変更の背景

TLSハンドシェイクにおいて、クライアントはサポートする暗号スイートのリストを優先順位付きでサーバーに提示します。サーバーはこのリストの中から、自身がサポートし、かつクライアントが提示した中で最も優先度の高い暗号スイートを選択します。この「優先順位」は、セキュリティ強度やパフォーマンスの観点から非常に重要です。

Go言語の map 型は、要素の挿入順序やイテレーション順序を保証しません。つまり、同じマップに対して同じキーと値を追加しても、イテレーションするたびに異なる順序で要素が返される可能性があります。

このコミット以前の crypto/tls パッケージでは、暗号スイートの定義を map[uint16]*cipherSuite という形式で保持していました。そして、ClientHelloメッセージで暗号スイートのリストを構築する際に、このマップをイテレーションして順序を決定していました。しかし、Goのマップの非決定論的なイテレーション順序の特性により、クライアントがサーバーに提示する暗号スイートの順序が実行ごとに変わってしまう可能性がありました。

これは、TLSハンドシェイクの予測可能性を損ない、潜在的にセキュリティ上の問題(例えば、より弱い暗号スイートが意図せず優先されてしまうなど)を引き起こす可能性がありました。このコミットは、この非決定論的な挙動を排除し、暗号スイートの提示順序を明確に制御できるようにするために行われました。

前提知識の解説

TLS/SSL ハンドシェイク

TLS (Transport Layer Security) は、インターネット上でデータを安全にやり取りするための暗号化プロトコルです。かつてはSSL (Secure Sockets Layer) と呼ばれていました。TLS接続を確立する最初のステップが「ハンドシェイク」です。

ハンドシェイクの主要なステップは以下の通りです。

  1. ClientHello: クライアントがサーバーに接続を要求し、自身がサポートするTLSバージョン、暗号スイートのリスト(優先順位付き)、圧縮方式、ランダムなバイト列などを送信します。
  2. ServerHello: サーバーはClientHelloを受け取り、クライアントが提示した情報の中から、自身がサポートする最適なTLSバージョン、暗号スイート、圧縮方式、ランダムなバイト列などを選択してクライアントに返します。
  3. 証明書交換: サーバーは自身のデジタル証明書をクライアントに送信し、自身の身元を証明します。
  4. 鍵交換: クライアントとサーバーは、共通の秘密鍵を安全に生成するための鍵交換アルゴリズムを実行します。
  5. Finishedメッセージ: 両者がハンドシェイクの完了と、以降の通信が暗号化されることを確認するメッセージを交換します。

暗号スイート (Cipher Suite)

暗号スイートは、TLS接続で使用される一連の暗号アルゴリズムの組み合わせを定義するものです。通常、以下の要素を含みます。

  • 鍵交換アルゴリズム (Key Exchange Algorithm): クライアントとサーバーが共通の秘密鍵を安全に確立する方法(例: RSA, Diffie-Hellman, ECDHE)。
  • 認証アルゴリズム (Authentication Algorithm): サーバーの身元をクライアントが検証する方法(例: RSA, DSA, ECDSA)。
  • 対称暗号アルゴリズム (Symmetric Encryption Algorithm): 実際のデータ暗号化に使用されるアルゴリズム(例: AES, 3DES, RC4)。
  • ハッシュ関数 (Hash Function) / MACアルゴリズム (Message Authentication Code): メッセージの完全性と認証を保証するためのアルゴリズム(例: SHA-1, SHA-256)。

例えば、TLS_RSA_WITH_AES_128_CBC_SHA という暗号スイートは、RSAによる鍵交換と認証、AES-128-CBCによるデータ暗号化、SHAによるMACを使用することを示します。

Go言語のマップのイテレーション順序

Go言語の組み込み型である map は、キーと値のペアを格納するハッシュテーブルです。Goの仕様では、マップをイテレーションする際の順序は保証されていません。これは、マップの実装がパフォーマンス最適化のために内部的に要素の順序を動的に変更する可能性があるためです。

この非決定論的な挙動は、通常のデータ処理では問題になりませんが、順序が意味を持つ場合(例えば、プロトコルメッセージのフィールド順序や、セキュリティ関連の優先順位など)には、明示的に順序を制御する必要があります。

技術的詳細

このコミットの技術的詳細は、Goのマップの非決定論的なイテレーション順序がTLSハンドシェイクのClientHelloメッセージにおける暗号スイートの提示順序に影響を与えていた問題を、データ構造の変更によって解決した点にあります。

  1. データ構造の変更:

    • 以前は、cipherSuites という変数が map[uint16]*cipherSuite 型で定義されていました。uint16 は暗号スイートのIDを表し、*cipherSuite はその詳細な定義を持つ構造体へのポインタです。
    • このコミットでは、cipherSuites[]*cipherSuite (暗号スイート構造体へのポインタのスライス) 型に変更しました。スライスは要素の順序を保証するため、この変更により暗号スイートの定義順序が固定されます。
    • cipherSuite 構造体自体にも id uint16 フィールドが追加され、各スイートが自身のIDを持つようになりました。これにより、スライス内の各要素が自身のIDを保持し、検索時に利用できるようになります。
  2. 暗号スイート検索ロジックの変更:

    • mutualCipherSuite 関数は、クライアントとサーバー間で共通の暗号スイートを見つけるために使用されます。以前はマップから直接IDで検索していましたが、スライスに変更されたため、スライスを線形探索してIDが一致するスイートを見つけるように変更されました。
    • サーバー側のハンドシェイク処理 (handshake_server.go) においても、クライアントが提示した暗号スイートIDと、サーバーがサポートする暗号スイートを比較する際に、cipherSuites スライスをイテレーションして適切なスイートを見つけるロジックに修正されました。
  3. ClientHelloメッセージ生成への影響:

    • initDefaultCipherSuites 関数は、デフォルトの暗号スイートリストを初期化するために使用されます。この関数も、cipherSuites マップをイテレーションする代わりに、cipherSuites スライスをイテレーションして、その順序でデフォルトリストを構築するように変更されました。
    • これにより、ClientHelloメッセージで送信される暗号スイートの順序が、cipherSuites スライスで定義された順序と一致するようになり、非決定論的な挙動が排除されました。

この変更は、TLSプロトコルにおける暗号スイートの順序の重要性を認識し、Go言語の特性(マップの非決定論的な順序)を考慮した堅牢な実装への改善と言えます。

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

このコミットのコアとなる変更は、src/pkg/crypto/tls/cipher_suites.go ファイルにおける cipherSuites 変数の定義変更と、それに伴う関連関数の修正です。

src/pkg/crypto/tls/cipher_suites.go

--- a/src/pkg/crypto/tls/cipher_suites.go
+++ b/src/pkg/crypto/tls/cipher_suites.go
@@ -37,6 +37,7 @@ type keyAgreement interface {
 // A cipherSuite is a specific combination of key agreement, cipher and MAC
 // function. All cipher suites currently assume RSA key agreement.
 type cipherSuite struct {
+	id uint16 // 新しく追加されたフィールド
 	// the lengths, in bytes, of the key material needed for each component.
 	keyLen int
 	macLen int
@@ -50,13 +51,13 @@ type cipherSuite struct {
 	mac      func(version uint16, macKey []byte) macFunction
 }
 
-var cipherSuites = map[uint16]*cipherSuite{ // 変更前: マップ
-	TLS_RSA_WITH_RC4_128_SHA:            &cipherSuite{16, 20, 0, rsaKA, false, cipherRC4, macSHA1},
-	TLS_RSA_WITH_3DES_EDE_CBC_SHA:       &cipherSuite{24, 20, 8, rsaKA, false, cipher3DES, macSHA1},
-	TLS_RSA_WITH_AES_128_CBC_SHA:        &cipherSuite{16, 20, 16, rsaKA, false, cipherAES, macSHA1},
-	TLS_ECDHE_RSA_WITH_RC4_128_SHA:      &cipherSuite{16, 20, 0, ecdheRSAKA, true, cipherRC4, macSHA1},
-	TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: &cipherSuite{24, 20, 8, ecdheRSAKA, true, cipher3DES, macSHA1},
-	TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:  &cipherSuite{16, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1},
+var cipherSuites = []*cipherSuite{ // 変更後: スライス
+	&cipherSuite{TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, false, cipherRC4, macSHA1},
+	&cipherSuite{TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, false, cipher3DES, macSHA1},
+	&cipherSuite{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, false, cipherAES, macSHA1},
+	&cipherSuite{TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, true, cipherRC4, macSHA1},
+	&cipherSuite{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, true, cipher3DES, macSHA1},
+	&cipherSuite{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1},
 }
 
 func cipherRC4(key, iv []byte, isRead bool) interface{} {
@@ -159,15 +160,20 @@ func ecdheRSAKA() keyAgreement {
 	return new(ecdheRSAKeyAgreement)
 }
 
-// mutualCipherSuite returns a cipherSuite and its id given a list of supported
+// mutualCipherSuite returns a cipherSuite given a list of supported
 // ciphersuites and the id requested by the peer.
-func mutualCipherSuite(have []uint16, want uint16) (suite *cipherSuite, id uint16) { // 変更前: 戻り値にidが含まれる
+func mutualCipherSuite(have []uint16, want uint16) *cipherSuite { // 変更後: 戻り値からidが削除
 	for _, id := range have {
 		if id == want {
-			return cipherSuites[id], id // 変更前: マップから直接取得
+			for _, suite := range cipherSuites { // 変更後: スライスを線形探索
+				if suite.id == want {
+					return suite
+				}
+			}
+			return nil
 		}
 	}
-	return
+	return nil
 }
 
 // A list of the possible cipher suite ids. Taken from

コアとなるコードの解説

cipherSuite 構造体の変更

cipherSuite 構造体に id uint16 フィールドが追加されました。これにより、各暗号スイートの定義が自身のIDを内部に持つようになり、スライスとして管理する際に個々のスイートを識別できるようになります。

cipherSuites 変数の型変更

最も重要な変更は、cipherSuites 変数の型が map[uint16]*cipherSuite から []*cipherSuite へと変更された点です。

  • 変更前 (map[uint16]*cipherSuite): 暗号スイートのIDをキーとして、その定義を値として持つマップでした。マップのイテレーション順序は保証されないため、このマップをイテレーションして暗号スイートのリストを生成すると、その順序が非決定論的になっていました。
  • 変更後 ([]*cipherSuite): 暗号スイートの定義をポインタのスライスとして保持します。スライスは要素の順序を保証するため、このスライスをイテレーションすることで、定義された順序で暗号スイートのリストを取得できるようになります。これにより、ClientHelloメッセージで提示される暗号スイートの順序が固定され、予測可能になります。

mutualCipherSuite 関数の変更

この関数は、クライアントとサーバーが共通してサポートする暗号スイートを見つけるために使用されます。

  • 変更前: cipherSuites[id] のように、マップから直接IDを使って暗号スイートを取得していました。戻り値も (suite *cipherSuite, id uint16) のように、スイートとIDの両方を返していました。
  • 変更後: cipherSuites がスライスになったため、マップのように直接IDでアクセスすることはできません。そのため、for _, suite := range cipherSuites のようにスライスを線形探索し、suite.id == want でIDが一致するスイートを見つけるように変更されました。また、suite オブジェクト自体がIDを持つようになったため、戻り値から id が削除され、*cipherSuite のみを返すようになりました。

これらの変更により、crypto/tls パッケージはGoのマップの非決定論的なイテレーション順序に依存することなく、TLSハンドシェイクにおける暗号スイートの順序を確実に制御できるようになりました。

関連リンク

  • Go言語の crypto/tls パッケージのドキュメント (当時のバージョンに近いもの): 2011年当時のGoのドキュメントは現在のものとは異なる可能性がありますが、基本的なAPIは参考になります。
  • TLSプロトコルに関するRFC (例: RFC 5246 - TLS 1.2):

参考にした情報源リンク