[インデックス 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ハンドシェイクは、以下の主要なステップで構成されます。
- ClientHello: クライアントがサーバーに接続を要求し、サポートするTLSバージョン、暗号スイート、圧縮方式、そして32バイトのランダムなデータ(
random
フィールド)などを送信します。 - ServerHello: サーバーはClientHelloに応答し、選択したTLSバージョン、暗号スイート、圧縮方式、そしてサーバー側の32バイトのランダムなデータなどを送信します。
- 証明書交換: サーバーは自身のデジタル証明書をクライアントに提示し、クライアントはそれを検証します。
- 鍵交換: クライアントとサーバーは、共有の秘密鍵を安全に確立します。
- 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つのファイルが変更されています。
src/pkg/crypto/tls/handshake_client.go
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
フィールドを生成する部分が変更されました。
変更前:
t := uint32(c.config.time().Unix())
で現在のUnixタイムスタンプを取得します。hello.random[0]
からhello.random[3]
までの最初の4バイトに、このタイムスタンプをバイト列として書き込みます(ビッグエンディアン形式)。io.ReadFull(c.config.rand(), hello.random[4:])
で、random
フィールドの残りの28バイトを暗号学的に安全な乱数で埋めます。
変更後:
_, err := io.ReadFull(c.config.rand(), hello.random)
の1行に集約されました。- これにより、
hello.random
の32バイト全体が、c.config.rand()
(設定された乱数生成器、通常はcrypto/rand
パッケージのReader
)から取得される暗号学的に安全な乱数で埋められます。タイムスタンプの埋め込みは完全に削除されました。
handshake_server.go
の変更点
サーバー側でも、ServerHello
メッセージのrandom
フィールドを生成する部分が同様に変更されました。
変更前:
t := uint32(config.time().Unix())
で現在のUnixタイムスタンプを取得します。hs.hello.random = make([]byte, 32)
で32バイトのスライスを初期化します。hs.hello.random[0]
からhs.hello.random[3]
までの最初の4バイトに、このタイムスタンプをバイト列として書き込みます。io.ReadFull(config.rand(), hs.hello.random[4:])
で、random
フィールドの残りの28バイトを乱数で埋めます。hs.hello.secureRenegotiation = hs.clientHello.secureRenegotiation
の行は、乱数生成の直後にありました。
変更後:
hs.hello.random = make([]byte, 32)
で32バイトのスライスを初期化します。_, err = io.ReadFull(config.rand(), hs.hello.random)
の1行で、hs.hello.random
の32バイト全体が暗号学的に安全な乱数で埋められます。タイムスタンプの埋め込みは削除されました。hs.hello.secureRenegotiation = hs.clientHello.secureRenegotiation
の行は、乱数生成の後に移動しましたが、機能的な変更はありません。
これらの変更により、GoのTLS実装は、ClientHelloおよびServerHelloメッセージのrandom
フィールドにおいて、タイムスタンプではなく完全にランダムなデータを使用するようになり、TLS接続のフィンガープリンティング耐性が向上しました。
関連リンク
参考にした情報源リンク
- OpenSSLの関連コミット: http://git.openssl.org/gitweb/?p=openssl.git;a=commit;h=2016265dfb
- RFC 5246 (TLS 1.2) - 7.4.1.2. Client Hello: https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.1.2
- RFC 5246 (TLS 1.2) - 7.4.1.3. Server Hello: https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.1.3
- TLS Fingerprinting (一般的な情報): https://www.eff.org/deeplinks/2010/03/how-identify-and-track-web-browsers-using-tls
- TLS Fingerprinting (より技術的な情報): https://www.blackhat.com/docs/us-14/materials/us-14-Durumeric-The-Internet-Census-2012-Revisited-WP.pdf (特にTLS ClientHelloのフィンガープリンティングに関する議論)