これは、http2 Advent Calendar 2016の12日目の記事です。
今日は、第2番目のハンドシェイクである HRR (Hello Retry Request)について説明します。HRR とは、サーバがクライアントに Hello を再要求し、フルハンドシェイクをやり直すハンドシェイクです。
以下に仕様のドラフトからHRRの図を抜粋します:
Client Server
ClientHello
+ key_share -------->
<-------- HelloRetryRequest
+ key_share
ClientHello
+ key_share -------->
ServerHello
+ key_share
{EncryptedExtensions}
{CertificateRequest*}
{Certificate*}
{CertificateVerify*}
{Finished}
<-------- [Application Data*]
{Certificate*}
{CertificateVerify*}
{Finished} -------->
[Application Data] <-------> [Application Data]
サーバは、クライアントが提示した key_share 拡張を拒否する場合に、HRR を送ります。key_share 拡張の定義はこうです。
struct {
NamedGroup group;
opaque key_exchange<1..2^16-1>;
} KeyShareEntry;
struct {
select (Handshake.msg_type) {
case client_hello: KeyShareEntry client_shares<0..2^16-1>;
case hello_retry_request: NamedGroup selected_group;
case server_hello: KeyShareEntry server_share;
};
} KeyShare;
ハンドシェイクメッセージによって構造が違うのが分かります。こんなことは、TLS 1.2 にはありませんでした。TLS 1.2 の拡張を処理するパーサを TLS 1.3 対応にするには、メッセージの型の情報も渡さないといけなくなりました。
この2つの図から分かることは:
- クライアントは、自分のサポートしている(EC)DHEの鍵交換の鍵組みを生成し、それらの公開鍵をリストとして key_share 拡張で送ります。
- たとえば、P256のECDHEの公開鍵とX25519のECDHEの公開鍵を送ります。
- サーバは、そのすべてが気に入らない場合は HRR を返し、もう一度 Client Hello を送るように要求します。
この意味分かりますか? 僕はまったく理解できませんでした。自分が知っている鍵交換の公開鍵をすべて提示したのに、拒否されたのです。クライアントは何ができるというのでしょうか?
答えは、picotlsの作者である kazuho さんに教えていただきました。Chrome CanaryがHRRを使うというのです。その動作は以下の通りです。
- Canary は X25519 の ECDHE の公開鍵を key_share で送ります。
- サーバが X25519 をサポートしていなければ、HRR が返ってきます
- Canary は、P256 の ECDHE の公開鍵を key_share で送ります。
つまり、最初に全部を送るのではなく、帯域を節約するだけに1つだけ送るのです。一度に全部送ると選択権はサーバにありますが、1つずつ送ればクライアント側で優先順位を付けることになります。
最初はまったく分からなかった HRR ですが、Haskell tls のクライアント側も HRR ベースにしようかと検討中です。なぜなら、もともとの TLS 1.2 のコードでは、鍵交換の秘密鍵を保存しておく場所が1つしかないからです。(ええ、増やすことはもちろんできますよ!)