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

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

このコミットは、Go言語のcrypto/tlsパッケージにおいて、TLS (Transport Layer Security) のHelloメッセージに現在時刻を含めないように変更するものです。これにより、TLS接続のフィンガープリント(識別)能力が低下し、プライバシーとセキュリティが向上します。

コミット

commit c2d02b3b9fd6847535ac18b24582a8bb98a3eb30
Author: Anthony Martin <ality@pbrane.org>
Date:   Tue Feb 4 10:51:37 2014 -0500

    crypto/tls: do not send the current time in hello messages
    
    This reduces the ability to fingerprint TLS connections.
    
    The impeteus for this change was a recent change to OpenSSL
    by Nick Mathewson:
    
    http://git.openssl.org/gitweb/?p=openssl.git;a=commit;h=2016265dfb
    
    LGTM=agl
    R=agl
    CC=golang-codereviews
    https://golang.org/cl/57230043

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

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

元コミット内容

crypto/tls: do not send the current time in hello messages

このコミットは、TLSのHelloメッセージにおいて現在時刻を送信しないようにするものです。これにより、TLS接続のフィンガープリント能力が低下します。この変更は、Nick MathewsonによるOpenSSLの最近の変更に触発されたものです。

変更の背景

TLSプロトコルでは、クライアントとサーバーがハンドシェイクを開始する際に、それぞれClientHelloおよびServerHelloメッセージを交換します。これらのメッセージには、セッションのランダム性を確保するための32バイトのランダムなデータが含まれています。歴史的に、この32バイトのランダムデータのうち最初の4バイトは、Unixエポックからの現在時刻(GMT)を表すgmt_unix_timeとして使用されていました。残りの28バイトは、乱数生成器から取得されるランダムなデータでした。

このgmt_unix_timeの使用は、TLSセッションの再開(セッションIDやセッションチケットを使用する場合)において、古いセッションキーの再利用を防ぐためのメカニズムとして意図されていました。しかし、このタイムスタンプは、特定のクライアントやサーバーの実装を識別するための「フィンガープリント」として悪用される可能性がありました。例えば、異なる実装が異なるタイムスタンプの生成方法や、乱数生成器の初期化方法を持つ場合、その差異がフィンガープリントとして利用され、ユーザーの追跡や特定のソフトウェアバージョンの特定に繋がる恐れがありました。

このコミットの動機は、OpenSSLが同様の変更を行ったことにあります。OpenSSLのコミット(2016265dfb)では、gmt_unix_timeフィールドを完全にランダムなデータで埋めるように変更されました。これは、TLS接続の匿名性を高め、フィンガープリンティングのリスクを軽減するためのセキュリティ強化策の一環です。Go言語のcrypto/tlsパッケージも、このセキュリティプラクティスに追従し、同様の改善を実装することになりました。

前提知識の解説

TLS (Transport Layer Security)

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

TLSハンドシェイク

TLSハンドシェイクは、以下の主要なステップで構成されます。

  1. ClientHello: クライアントがサーバーに接続を要求し、サポートするTLSバージョン、暗号スイート、圧縮方式、そして32バイトのランダムなデータ(randomフィールド)などを送信します。
  2. ServerHello: サーバーはClientHelloに応答し、選択したTLSバージョン、暗号スイート、圧縮方式、そしてサーバー側の32バイトのランダムなデータなどを送信します。
  3. 証明書交換: サーバーは自身のデジタル証明書をクライアントに提示し、クライアントはそれを検証します。
  4. 鍵交換: クライアントとサーバーは、共有の秘密鍵を安全に確立します。
  5. Finishedメッセージ: ハンドシェイクの完了と、その後の通信が暗号化されることを確認します。

ClientHello/ServerHelloメッセージのrandomフィールド

ClientHelloおよびServerHelloメッセージに含まれる32バイトのrandomフィールドは、セッションごとに異なる値を持ち、リプレイ攻撃を防ぐための重要な要素です。RFC 5246 (TLS 1.2) のセクション 7.4.1.2 (ClientHello) および 7.4.1.3 (ServerHello) にて、このrandomフィールドの構造が定義されています。

歴史的に、この32バイトのrandomフィールドの最初の4バイトは、gmt_unix_timeとして、Unixエポックからの秒数(GMT)が格納されていました。残りの28バイトは、乱数生成器から取得されるデータでした。

フィンガープリンティング

フィンガープリンティングとは、特定のシステム、ソフトウェア、またはユーザーを識別するために、そのシステムやソフトウェアが生成するユニークな特性やパターンを利用する技術です。TLS接続においては、ClientHelloメッセージの特定のフィールド(サポートする暗号スイートの順序、拡張、そしてこのrandomフィールドのgmt_unix_time部分など)が、クライアントの実装(OS、ブラウザ、TLSライブラリのバージョンなど)によって異なる場合があります。これらの差異を組み合わせることで、個々の接続を識別し、ユーザーの追跡やプロファイリングに利用される可能性があります。

gmt_unix_timeは、その値が時間とともに変化するものの、特定の時間帯における値の分布や、乱数部分との組み合わせ方によって、実装固有のパターンを生み出す可能性がありました。例えば、システム時刻がずれている場合や、乱数生成器の初期化タイミングが異なる場合などです。

技術的詳細

このコミットの技術的な核心は、TLSのClientHelloおよびServerHelloメッセージのrandomフィールドから、現在時刻(Unixタイムスタンプ)を削除し、その代わりに完全に暗号学的に安全な乱数で埋めることです。

変更前は、randomフィールドの最初の4バイトにuint32型のUnixタイムスタンプが格納されていました。これは、c.config.time().Unix()(クライアント側)またはconfig.time().Unix()(サーバー側)で取得された現在時刻をバイト列に変換して設定されていました。残りの28バイトは、io.ReadFull(c.config.rand(), hello.random[4:])(クライアント側)またはio.ReadFull(config.rand(), hs.hello.random[4:])(サーバー側)によって、乱数生成器から読み込まれていました。

変更後は、randomフィールド全体(32バイト)が、io.ReadFull(c.config.rand(), hello.random)(クライアント側)またはio.ReadFull(config.rand(), hs.hello.random)(サーバー側)によって、乱数生成器から直接読み込まれるようになりました。これにより、randomフィールドは完全に予測不可能なデータで構成されることになり、タイムスタンプに基づくフィンガープリンティングの可能性が排除されます。

この変更は、TLSプロトコルの仕様自体を変更するものではなく、randomフィールドの最初の4バイトをgmt_unix_timeとして使用するという「推奨」または「慣習」を、よりセキュアな「完全にランダムなデータ」で置き換えるものです。RFC 5246では、randomフィールドの最初の4バイトがgmt_unix_timeであると述べられていますが、これは必須ではなく、実装の自由度が残されています。この変更は、その自由度を利用してセキュリティを強化するものです。

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

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

  1. src/pkg/crypto/tls/handshake_client.go
  2. src/pkg/crypto/tls/handshake_server.go

src/pkg/crypto/tls/handshake_client.go の変更

--- a/src/pkg/crypto/tls/handshake_client.go
+++ b/src/pkg/crypto/tls/handshake_client.go
@@ -63,12 +63,7 @@ NextCipherSuite:
 		}
 	}
 
-	t := uint32(c.config.time().Unix())
-	hello.random[0] = byte(t >> 24)
-	hello.random[1] = byte(t >> 16)
-	hello.random[2] = byte(t >> 8)
-	hello.random[3] = byte(t)
-	_, err := io.ReadFull(c.config.rand(), hello.random[4:])
+	_, err := io.ReadFull(c.config.rand(), hello.random)
 	if err != nil {
 		c.sendAlert(alertInternalError)
 		return errors.New("tls: short read from Rand: " + err.Error())

src/pkg/crypto/tls/handshake_server.go の変更

--- a/src/pkg/crypto/tls/handshake_server.go
+++ b/src/pkg/crypto/tls/handshake_server.go
@@ -146,17 +146,12 @@ Curves:
 	}
 
 	hs.hello.vers = c.vers
-	t := uint32(config.time().Unix())
 	hs.hello.random = make([]byte, 32)
-	hs.hello.random[0] = byte(t >> 24)
-	hs.hello.random[1] = byte(t >> 16)
-	hs.hello.random[2] = byte(t >> 8)
-	hs.hello.random[3] = byte(t)
-	hs.hello.secureRenegotiation = hs.clientHello.secureRenegotiation
-	_, err = io.ReadFull(config.rand(), hs.hello.random[4:])
+	_, err = io.ReadFull(config.rand(), hs.hello.random)
 	if err != nil {
 		return false, c.sendAlert(alertInternalError)
 	}
+	hs.hello.secureRenegotiation = hs.clientHello.secureRenegotiation
 	hs.hello.compressionMethod = compressionNone
 	if len(hs.clientHello.serverName) > 0 {
 		c.serverName = hs.clientHello.serverName

コアとなるコードの解説

handshake_client.go の変更点

クライアント側では、ClientHelloメッセージのrandomフィールドを生成する部分が変更されました。

変更前:

  1. t := uint32(c.config.time().Unix()) で現在のUnixタイムスタンプを取得します。
  2. hello.random[0]からhello.random[3]までの最初の4バイトに、このタイムスタンプをバイト列として書き込みます(ビッグエンディアン形式)。
  3. io.ReadFull(c.config.rand(), hello.random[4:]) で、randomフィールドの残りの28バイトを暗号学的に安全な乱数で埋めます。

変更後:

  1. _, err := io.ReadFull(c.config.rand(), hello.random) の1行に集約されました。
  2. これにより、hello.randomの32バイト全体が、c.config.rand()(設定された乱数生成器、通常はcrypto/randパッケージのReader)から取得される暗号学的に安全な乱数で埋められます。タイムスタンプの埋め込みは完全に削除されました。

handshake_server.go の変更点

サーバー側でも、ServerHelloメッセージのrandomフィールドを生成する部分が同様に変更されました。

変更前:

  1. t := uint32(config.time().Unix()) で現在のUnixタイムスタンプを取得します。
  2. hs.hello.random = make([]byte, 32) で32バイトのスライスを初期化します。
  3. hs.hello.random[0]からhs.hello.random[3]までの最初の4バイトに、このタイムスタンプをバイト列として書き込みます。
  4. io.ReadFull(config.rand(), hs.hello.random[4:]) で、randomフィールドの残りの28バイトを乱数で埋めます。
  5. hs.hello.secureRenegotiation = hs.clientHello.secureRenegotiation の行は、乱数生成の直後にありました。

変更後:

  1. hs.hello.random = make([]byte, 32) で32バイトのスライスを初期化します。
  2. _, err = io.ReadFull(config.rand(), hs.hello.random) の1行で、hs.hello.randomの32バイト全体が暗号学的に安全な乱数で埋められます。タイムスタンプの埋め込みは削除されました。
  3. hs.hello.secureRenegotiation = hs.clientHello.secureRenegotiation の行は、乱数生成の後に移動しましたが、機能的な変更はありません。

これらの変更により、GoのTLS実装は、ClientHelloおよびServerHelloメッセージのrandomフィールドにおいて、タイムスタンプではなく完全にランダムなデータを使用するようになり、TLS接続のフィンガープリンティング耐性が向上しました。

関連リンク

参考にした情報源リンク