TLS 1.3 開発日記 その7 0RTT
これは、http2 Advent Calendar 2016の24日目の記事です。
この記事では、TLS 1.3 の4番目のハンドシェイクである 0RTT について説明します。
0RTTとは、アプリケーションが目的の通信を始めるまでに、下位の層でパケットのやりとりがないことを意味します。準備にかかる round trip time の回数が0回ということです。
TLS 1.3 では、0RTT が PSK ハンドシェイクの拡張として実現されています。TLS 1.3の最新のドラフトから0RTTの図を抜粋します:
Client Server
ClientHello
+ early_data
+ key_share*
+ psk_key_exchange_modes
+ pre_shared_key
(Application Data*) -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
{Finished}
<-------- [Application Data*]
(EndOfEarlyData)
{Finished} -------->
[Application Data] <-------> [Application Data]
他のハンドシェクと比べると分かりますが、波カッコ、角カッコに加えて、丸カッコが登場しています。丸カッコは、PSKをタネにして生成した共通の鍵で暗号化されていることを意味します。Client Hello を送ると同時に、アプリケーションがデータを暗号化して送っていることが分かるでしょう。
early_data 拡張が 0RTT を使っているという目標であり、EndOfEarlyDataというハンドシェイクメッセージが 0RTT によって送られるデータの終わりを示しています。
key_shareを用いて前方秘匿性のある(EC)DHEで鍵を交換する前に暗号化していますので、丸カッコの部分には前方秘匿性がありません。また、リプレイ攻撃も完全には防げません。
このようにセキュリティが弱いために、0RTTには通常とは別のAPIを提供せよとドラフトには書かれています。クライアント側に0RTT専用のAPIを用意するのは簡単そうですが、サーバ側はどうすべきなのか、現時点の僕には分かりません。僕が思い付くのは、サーバが0RTT を受け取るか否かの設定項目を設けるぐらいです。
PSKハンドシェイクで、サーバがPSKは正当だが受け入れられないと判断した場合は、フルハンドシェイクにフォールバックします。0RTTでも同様ですが、その場合サーバは、クライアントが0RTTで送って来たアプリケーションデータを読み捨てる必要があります。また、クライアントはサーバが pre_shared_key を返さない、つまり、フルハンドシェイクへのフォールバックを選んだことを検知すると、アプリケーションデータを再送する必要があります。
丸カッコ、波カッコ、角カッコが出揃ったので、ようやくキースケジュールの図を出せるときがやってきました:
0
|
v
PSK -> HKDF-Extract
|
v
Early Secret
|
+-----> Derive-Secret(.,
| "external psk binder key" |
| "resumption psk binder key",
| "")
| = binder_key
|
+-----> Derive-Secret(., "client early traffic secret",
| ClientHello)
| = client_early_traffic_secret
|
+-----> Derive-Secret(., "early exporter master secret",
| ClientHello)
| = early_exporter_secret
v
(EC)DHE -> HKDF-Extract
|
v
Handshake Secret
|
+-----> Derive-Secret(., "client handshake traffic secret",
| ClientHello...ServerHello)
| = client_handshake_traffic_secret
|
+-----> Derive-Secret(., "server handshake traffic secret",
| ClientHello...ServerHello)
| = server_handshake_traffic_secret
|
v
0 -> HKDF-Extract
|
v
Master Secret
|
+-----> Derive-Secret(., "client application traffic secret",
| ClientHello...Server Finished)
| = client_traffic_secret_0
|
+-----> Derive-Secret(., "server application traffic secret",
| ClientHello...Server Finished)
| = server_traffic_secret_0
|
+-----> Derive-Secret(., "exporter master secret",
| ClientHello...Server Finished)
| = exporter_secret
|
+-----> Derive-Secret(., "resumption master secret",
ClientHello...Client Finished)
= resumption_master_secret
- HKDF-Extract や Drive-Secret は単なる関数であり、ドラフトを読めば定義が分かります
- "0" は、cipher suite で決定したハッシュの出力の大きさ分の 0 の列です
- 左上の PSK は、前回のセッションで共有した resumption_master_secret のことです
- "(EC)DHE"は、(EC)DHEで共有した鍵のことです
鍵は以下のように使われます:
- 丸カッコ:client_early_traffic_secret
- 波カッコ:client_handshake_traffic_secret, erver_handshake_traffic_secret
- 角カッコ:client_traffic_secret_0, server_traffic_secret_0
これで、4つのハンドシェイクすべてが理解できました。