これは、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つしかないからです。(ええ、増やすことはもちろんできますよ!)