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

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

このコミットは、Go言語の crypto/tls パッケージにおいて、TLS 1.2専用の暗号スイートがそれ以前のTLSバージョン(例: TLS 1.1)で誤って選択される問題を修正するものです。特に、NSS(Network Security Services)を使用するブラウザ(FirefoxやChromeの開発版)が、TLS 1.1のClientHelloでTLS 1.2専用の暗号スイートを通知し、サーバーがそれを選択すると問題が発生するという相互運用性の課題に対処しています。この変更により、GoクライアントはTLS 1.2を使用しない限りTLS 1.2専用の暗号スイートを通知せず、GoサーバーはTLS 1.2がネゴシエートされない限りそれらの暗号スイートを選択しないようになります。

コミット

commit f752484c7493bd55e19174418f5cd5abcb46e0e4
Author: Adam Langley <agl@golang.org>
Date:   Thu Sep 26 17:09:56 2013 -0400

    crypto/tls: don't select TLS 1.2 cipher suites in prior versions.
    
    AES-GCM cipher suites are only defined for TLS 1.2, although there's
    nothing really version specific about them. However, development
    versions of NSS (meaning Firefox and Chrome) have an issue where
    they'll advertise TLS 1.2-only cipher suites in a TLS 1.1 ClientHello
    but then balk when the server selects one.
    
    This change causes Go clients not to advertise TLS 1.2 cipher suites
    unless TLS 1.2 is being used, and prevents servers from selecting them
    unless TLS 1.2 has been negotiated.
    
    https://code.google.com/p/chromium/issues/detail?id=297151
    https://bugzilla.mozilla.org/show_bug.cgi?id=919677
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/13573047

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

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

元コミット内容

crypto/tls: don't select TLS 1.2 cipher suites in prior versions.

AES-GCM暗号スイートはTLS 1.2でのみ定義されていますが、実際にはバージョン固有の要素はほとんどありません。しかし、NSS(FirefoxやChrome)の開発バージョンには、TLS 1.1のClientHelloでTLS 1.2専用の暗号スイートを通知するものの、サーバーがそれを選択すると問題を起こすという問題がありました。

この変更により、GoクライアントはTLS 1.2を使用しない限りTLS 1.2専用の暗号スイートを通知せず、サーバーはTLS 1.2がネゴシエートされない限りそれらを選択しないようになります。

変更の背景

この変更の背景には、TLSプロトコルのバージョン間の相互運用性の問題がありました。具体的には、TLS 1.2で導入されたAES-GCM(Galois/Counter Mode)ベースの暗号スイートが、TLS 1.1以前のバージョンで誤って使用される可能性があったことです。

問題の核心は、NSS(Network Security Services)ライブラリにありました。NSSはMozillaによって開発された暗号化ライブラリで、FirefoxやChromeなどの主要なウェブブラウザでTLS/SSL通信の実装に利用されています。NSSの開発バージョンには、TLS 1.1のClientHelloメッセージ(クライアントがサーバーに自身の能力を伝える最初のメッセージ)において、本来TLS 1.2でしか定義されていないAES-GCM暗号スイートを誤って含めてしまうバグが存在しました。

このNSSのバグにより、以下のような問題が発生していました。

  1. クライアントの誤った通知: NSSを使用するブラウザがTLS 1.1で接続しようとする際、ClientHelloにTLS 1.2専用のAES-GCM暗号スイートを含めてしまう。
  2. サーバーの誤った選択: サーバーがこのClientHelloを受け取った際、クライアントが提示した暗号スイートの中から、TLS 1.1ではサポートされていないAES-GCMスイートを選択してしまう可能性がある。
  3. 通信の失敗: サーバーがTLS 1.1のコンテキストでTLS 1.2専用の暗号スイートを選択すると、クライアント側がそれを処理できず、TLSハンドシェイクが失敗し、通信が確立できないという問題が発生していました。

この問題は、ChromiumのIssue 297151とMozillaのBugzilla 919677で報告されており、Goの crypto/tls パッケージもこの相互運用性の問題に直面していました。GoのTLS実装が、NSSのこの挙動に対応し、より堅牢なハンドシェイクプロセスを保証するために、このコミットが導入されました。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

1. TLS (Transport Layer Security)

TLSは、インターネット上で安全な通信を行うための暗号化プロトコルです。ウェブブラウジング(HTTPS)、電子メール、VoIPなど、様々なアプリケーションで利用されています。TLSは、クライアントとサーバー間でデータを送受信する前に、暗号化された安全なチャネルを確立します。

2. TLSハンドシェイク

TLS通信を開始する際、クライアントとサーバーは「ハンドシェイク」と呼ばれる一連のメッセージ交換を行います。このハンドシェイクの目的は以下の通りです。

  • プロトコルバージョンのネゴシエーション(TLS 1.0, 1.1, 1.2, 1.3など)
  • 暗号スイートの選択
  • サーバー認証(通常はサーバー証明書を使用)
  • 鍵交換とセッション鍵の生成

主要なハンドシェイクメッセージ:

  • ClientHello: クライアントがサーバーに送る最初のメッセージ。クライアントがサポートするTLSバージョン、暗号スイートのリスト、圧縮方式、ランダムなバイト列などを通知します。
  • ServerHello: サーバーがClientHelloに応答するメッセージ。サーバーが選択したTLSバージョン、暗号スイート、圧縮方式、ランダムなバイト列などを通知します。

3. TLSバージョン (TLS 1.1, TLS 1.2)

TLSプロトコルには複数のバージョンがあり、新しいバージョンほどセキュリティが強化され、新しい機能が追加されています。

  • TLS 1.1: 2006年にRFC 4346として定義されました。TLS 1.0のいくつかの脆弱性に対処しています。
  • TLS 1.2: 2008年にRFC 5246として定義されました。これはTLS 1.1からの大幅な改善であり、特にハッシュ関数と暗号化アルゴリズムの柔軟性が向上しました。SHA-256などのより強力なハッシュアルゴリズムや、AES-GCMのような認証付き暗号(AEAD)モードのサポートが導入されました。

4. 暗号スイート (Cipher Suite)

暗号スイートは、TLSハンドシェイク中にネゴシエートされる一連のアルゴリズムの組み合わせです。これには、鍵交換アルゴリズム、認証アルゴリズム、バルク暗号化アルゴリズム、メッセージ認証コード(MAC)アルゴリズムが含まれます。 例: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

  • TLS: TLSプロトコルを示す。
  • ECDHE_RSA: 鍵交換アルゴリズム(楕円曲線ディフィー・ヘルマン一時鍵交換とRSA署名)。
  • AES_128_GCM: バルク暗号化アルゴリズム(AES、鍵長128ビット、GCMモード)。
  • SHA256: メッセージ認証コード(MAC)または擬似乱数関数(PRF)に使用されるハッシュアルゴリズム。

5. AES-GCM (Advanced Encryption Standard - Galois/Counter Mode)

AES-GCMは、認証付き暗号(Authenticated Encryption with Associated Data, AEAD)モードの一種です。データの機密性(暗号化)と完全性(改ざん防止)の両方を同時に提供します。TLS 1.2で導入され、それ以前のバージョンでは標準でサポートされていませんでした。従来のCBCモードとHMACの組み合わせに比べて、より効率的で安全とされています。

6. NSS (Network Security Services)

NSSは、Mozillaによって開発されたオープンソースの暗号化ライブラリです。SSL/TLS、PKI(公開鍵基盤)、暗号化、認証などのセキュリティ機能を提供します。Firefox、Chrome、Thunderbirdなど、多くのMozilla製品やその他のアプリケーションで利用されています。

技術的詳細

このコミットの技術的詳細は、Goの crypto/tls パッケージが、TLSプロトコルのバージョンと暗号スイートの選択をより厳密に管理するように変更された点にあります。

問題は、AES-GCM暗号スイートがTLS 1.2で初めて標準化されたにもかかわらず、NSSライブラリの特定のバージョンがTLS 1.1のClientHelloでこれらのスイートを提示してしまうという相互運用性の課題でした。GoのTLS実装は、この誤った提示に対応し、ハンドシェイクの失敗を防ぐ必要がありました。

この修正は、主に以下の2つの側面から行われました。

  1. 暗号スイートのメタデータ拡張: src/pkg/crypto/tls/cipher_suites.gosuiteTLS12 という新しいフラグが cipherSuite 構造体に追加されました。このフラグは、その暗号スイートがTLS 1.2以降でのみ使用可能であることを示します。

    const (
        // ...
        // suiteTLS12 indicates that the cipher suite should only be advertised
        // and accepted when using TLS 1.2.
        suiteTLS12
    )
    

    そして、AES-GCMを使用する暗号スイート(例: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)の定義にこの suiteTLS12 フラグが追加されました。これにより、GoのTLS実装は、どの暗号スイートが特定のTLSバージョンに限定されるかを内部的に識別できるようになります。

  2. クライアントとサーバーでの暗号スイート選択ロジックの変更:

    • クライアント側 (handshake_client.go): ClientHelloメッセージを構築する際に、クライアントがネゴシエートしようとしているTLSバージョン(hello.vers)がTLS 1.2未満である場合、suiteTLS12 フラグが設定されている暗号スイートは ClientHello.cipherSuites リストから除外されるようになりました。これにより、Goクライアントは、TLS 1.2以前のバージョンで接続しようとする際に、TLS 1.2専用の暗号スイートを誤ってサーバーに通知することがなくなります。

    • サーバー側 (handshake_server.go): サーバーがクライアントから受け取った暗号スイートのリストから、実際に使用する暗号スイートを選択するロジック (tryCipherSuite 関数) が変更されました。この関数は、ネゴシエートされたTLSバージョン(またはセッション再開時のセッションバージョン)を引数として受け取るようになりました。サーバーは、選択しようとしている暗号スイートに suiteTLS12 フラグが設定されており、かつネゴシエートされたTLSバージョンがTLS 1.2未満である場合、その暗号スイートを選択しないように動作します。これにより、サーバーは、クライアントが誤ってTLS 1.2専用のスイートを提示した場合でも、TLS 1.1などの古いバージョンでそれらを選択してしまうことを防ぎます。

これらの変更により、Goの crypto/tls パッケージは、NSSの特定の挙動に起因する相互運用性の問題を回避し、TLSハンドシェイクの堅牢性を向上させました。特に、TLSバージョンと暗号スイートの厳密な関連付けを行うことで、プロトコル仕様に準拠した安全な通信を保証しています。

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

このコミットでは、主に以下の4つのファイルが変更されています。

  1. src/pkg/crypto/tls/cipher_suites.go:

    • suiteTLS12 という新しい定数が const ブロックに追加されました。
    • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256flags フィールドに suiteTLS12 が追加されました。
  2. src/pkg/crypto/tls/handshake_client.go:

    • clientHelloMsgcipherSuites フィールドの初期化方法が変更されました。
    • possibleCipherSuites をループし、hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 の条件でTLS 1.2専用の暗号スイートを除外するロジックが追加されました。
  3. src/pkg/crypto/tls/handshake_server.go:

    • tryCipherSuite 関数のシグネチャが変更され、version uint16 引数が追加されました。
    • hs.suite = c.tryCipherSuite(...) の呼び出し箇所で、新しい version 引数(c.vers または hs.sessionState.vers)が渡されるようになりました。
    • tryCipherSuite 関数内で、version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 の条件でTLS 1.2専用の暗号スイートを除外するロジックが追加されました。
  4. src/pkg/crypto/tls/handshake_server_test.go:

    • TestTLS12OnlyCipherSuites という新しいテストケースが追加されました。このテストは、サーバーがTLS 1.1でネゴシエートされた際にTLS 1.2専用の暗号スイートを選択しないことを検証します。

コアとなるコードの解説

src/pkg/crypto/tls/cipher_suites.go

 // suiteTLS12 indicates that the cipher suite should only be advertised
 // and accepted when using TLS 1.2.
 suiteTLS12
)

// ...

var cipherSuites = []*cipherSuite{
    // ...
    {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadAESGCM},
    {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, nil, nil, aeadAESGCM},
    // ...
}

この変更は、暗号スイートの定義に新しいメタデータ suiteTLS12 を追加し、AES-GCMベースの暗号スイートにこのフラグを設定することで、それらがTLS 1.2以降でのみ有効であることを明示しています。これにより、GoのTLS実装は、各暗号スイートがどのTLSバージョンに属するかをプログラム的に判断できるようになります。

src/pkg/crypto/tls/handshake_client.go

    hello := &clientHelloMsg{
        vers:               c.config.maxVersion(),
        // ...
    }

    possibleCipherSuites := c.config.cipherSuites()
    hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites))

NextCipherSuite:
    for _, suiteId := range possibleCipherSuites {
        for _, suite := range cipherSuites {
            if suite.id != suiteId {
                continue
            }
            // Don't advertise TLS 1.2-only cipher suites unless
            // we're attempting TLS 1.2.
            if hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
                continue
            }
            hello.cipherSuites = append(hello.cipherSuites, suiteId)
            continue NextCipherSuite
        }
    }

クライアント側では、ClientHello メッセージを送信する前に、実際に通知する暗号スイートのリストをフィルタリングしています。hello.vers (クライアントがネゴシエートしようとしている最大TLSバージョン) が VersionTLS12 (TLS 1.2) 未満であり、かつ暗号スイートが suiteTLS12 フラグを持っている場合、その暗号スイートは ClientHello に含められません。これにより、TLS 1.1などで接続しようとする際に、TLS 1.2専用の暗号スイートを誤ってサーバーに提示することを防ぎます。

src/pkg/crypto/tls/handshake_server.go

// tryCipherSuite returns a cipherSuite with the given id if that cipher suite
// is acceptable to use.
func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version uint16, ellipticOk, ecdsaOk bool) *cipherSuite {
    for _, supported := range supportedCipherSuites {
        if id == supported {
            var candidate *cipherSuite
            // ... (既存のロジック) ...

            if version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 {
                continue
            }
            return candidate
        }
    }
    return nil
}

サーバー側では、tryCipherSuite 関数が変更され、ネゴシエートされたTLSバージョン (version 引数) を考慮するようになりました。サーバーがクライアントから提示された暗号スイートの中から一つを選択する際、もしその暗号スイートが suiteTLS12 フラグを持ち、かつネゴシエートされたバージョンがTLS 1.2未満であれば、その暗号スイートは選択肢から除外されます。これにより、サーバーはTLS 1.1などの古いバージョンでTLS 1.2専用の暗号スイートを誤って選択することを防ぎ、ハンドシェイクの失敗を回避します。

src/pkg/crypto/tls/handshake_server_test.go

func TestTLS12OnlyCipherSuites(t *testing.T) {
    // Test that a Server doesn't select a TLS 1.2-only cipher suite when
    // the client negotiates TLS 1.1.
    // ... (テストコード) ...
    clientHello := &clientHelloMsg{
        vers:   VersionTLS11, // TLS 1.1をネゴシエート
        // ...
        cipherSuites: []uint16{
            TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // TLS 1.2専用のスイート
            TLS_RSA_WITH_RC4_128_SHA,              // TLS 1.1でも利用可能なスイート
        },
        // ...
    }
    // ... (ハンドシェイクのシミュレーション) ...
    if s := serverHello.cipherSuite; s != TLS_RSA_WITH_RC4_128_SHA {
        t.Fatalf("bad cipher suite from server: %x", s)
    }
}

この新しいテストケースは、サーバーがTLS 1.1でハンドシェイクを行う際に、TLS 1.2専用のAES-GCM暗号スイートではなく、TLS 1.1でも利用可能なRC4ベースの暗号スイートを正しく選択することを確認します。これは、サーバー側の tryCipherSuite の変更が意図通りに機能していることを検証するものです。

これらの変更は、GoのTLS実装が、プロトコルバージョンと暗号スイートの互換性をより厳密に強制することで、相互運用性の問題を解決し、より堅牢なTLS通信を実現していることを示しています。

関連リンク

参考にした情報源リンク