あどけない話

インターネットに関する技術的な話など

TLS 1.3 開発日記 その5 Hello Retry Request

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