あどけない話

Internet technologies

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つのハンドシェイクすべてが理解できました。

TLS 1.3 開発日記 その6 Pre Shared Key

これは、http2 Advent Calendar 2016の13日目の記事です。

今日は、TLS 1.3 の第三番目のハンドシェイクである PSK (Pre Shared Key)について説明します。

みなさんは、Pre Shared Key という言葉から何をイメージしますか? 多くの方は、通信路の暗号化に使う鍵をあらかじめ共有することだと思うのではないでしょうか?

TLS 1.2 での Pre Shared Key は、RFC4279で定められています。なんとなんと、共有している鍵の主目的は、通信路の暗号化ではなく、通信相手の認証です。ここを間違っていると、全然話が理解できないので、間違っていた人は認識を新たにして下さい。

TLS 1.3 での PSK は、セッションの再開(resumption)と統合されました。ややこしいのですが、TLS 1.3 で PSK という場合、次の2つの意味があります。

  • External PSK: 手作業で共有した鍵を使って、お互いを認証すること
  • Resumption PSK: 前のセッションで共有した鍵を使って、サーバ認証(および前のセッションがクライアント認証をしていたらクライアント認証)を省略すること

この理解を間違うと、まったく意味不明なハンドシェイクなので、注意して下さいね。

Resumption PSKの場合、最初はフルハンドシェイクする必要があります。そのやりとりをドラフトから抜粋します。

ClientHello
 + key_share              -------->
                                                ServerHello
                                                + key_share
                                      {EncryptedExtensions}
                                      {CertificateRequest*}
                                             {Certificate*}
                                       {CertificateVerify*}
                                                 {Finished}
                          <--------     [Application Data*]
{Certificate*}
{CertificateVerify*}
{Finished}                -------->
                          <--------      [NewSessionTicket]
[Application Data]        <------->      [Application Data]

ここで、大切なのは NewSessionTicket です。Application Data用の暗号路が確立したら、いつでもサーバからクライアントへ送ることができます。

TLS 1.3 では、以下の2つも統合されています。

  • サーバが前のセッションの状態を保持する Session ID による resumption
  • セッション情報をサーバのみが復号化できる状態でクラアントに送って、サーバは状態を持たない Session ticket による resumption

NewSessionTicketには、十分な長さのデータが格納できますので、それが Session ID であっても、Session ticket であってもよい訳です。

PSK resumption の手続きもドラフトから抜粋します。

ClientHello
 + key_share*
 + psk_key_exchange_modes
 + pre_shared_key         -------->
                                                ServerHello
                                           + pre_shared_key
                                               + key_share*
                                      {EncryptedExtensions}
                                                 {Finished}
                          <--------     [Application Data*]
{Finished}                -------->
[Application Data]        <------->      [Application Data]

サーバが証明書を送ってないこと、つまりクライアントがサーバの認証を省略することがわかるでしょう。

PSKの情報を送る pre_shared_key 拡張は構造が複雑なので抜粋しませんが、クライアントは PSK のリストを送り、サーバが選んだ PSK が何番目かを返すと理解していれば十分です。クライアントが送る PSK のリストですが、ほとんど用途の場合その長さは1であると思われます。

Cipherスイートは、ClientHelloに入っていますので、前のセッションと異なる値を選んでもいいのですが、Hashが変わると Session ticket の検証に失敗します。

鍵交換は、psk_key_exchange_modes 拡張で制御します。以下の2つのモードがあります。

  • PSK only - 共有している鍵だけを種に使って暗号路の鍵を生成
  • PSK with (EC)DHE - 共有している鍵に加えて、新たに (EC)DHE で共有した鍵を種に使って暗号路の鍵を生成。こちらは前方秘匿性を持つ

ここまで読むと、TLS 1.3 開発日記 その2で述べた cipher スイートからサーバ認証と鍵交換が切り離された理由が納得できるのではないでしょうか?

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

TLS 1.3 開発日記 その4 フルハンドシェイク

これは、http2 Advent Calendar 2016の8日目の記事です。

今回はTLS 1.3のフルハンドシェイクについて書きます。

TLS 1.2のフルハンドシェイク

おさらいとして、RFC5246からTLS 1.2のフルハンドシェイクの図を少し変更して抜粋します。角カッコは暗号化されていることを意味します。

Client                                               Server

      ClientHello                  -------->
                                                      ServerHello
                                                      Certificate
                                                ServerKeyExchange
                                   <--------      ServerHelloDone
      ClientKeyExchange
      ChangeCipherSpec
      [Finished]                   -------->
                                                 ChangeCipherSpec
                                   <--------           [Finished]
      [Application Data]           <------->   [Application Data]

TLS 1.3 と比較する意味で、特筆すべき点を挙げます。

  • (EC)DHEの鍵交換は、サーバ側から始まります。ServerKeyExchangeにはサーバの(EC)DHE公開鍵の他に、証明書に対応する(RSAなどの)秘密鍵で署名した署名が入っています。これにより、クライアントはサーバを認証できます。
  • ServerHelloDone は、ServerHelloシリーズが終わる空の目印で、認証等の役割はありません。
  • 暗号化が始まる(正確には切り替わる)ことを告げる ChangeCipherSpec を送ります。定義としては、これは Handshake ではなく、独立な型を持つメッセージです。
  • クライアントからの Application Data は、Finished の後に送ることも可能です(1RTT)。しかし、サーバからの Application Data の送信には必ず 1.5RTT かかります。

なお、Finishedは、これまでのハンドシェイクに対して、共有した鍵を使って HMAC を計算し、共有鍵および改ざんがないことを確認します。

TLS1.3 のフルハンドシェイク

TLS 1.3のドラフトからTLS 1.3のフルハンドシェイクの図を少し変更して抜粋します。波カッコは初期の共有鍵で、角カッコは後期の共有鍵で暗号化されていることを示します。

       Client                                               Server

Key  ^ ClientHello
Exch | + supported_versions
     | + key_share
     v + signature_algorithms    -------->
                                                       ServerHello  ^ Key
                                                      + key_share   v Exch
                                             {EncryptedExtensions}  - Params
                                                     {Certificate}  ^
                                               {CertificateVerify}  | Auth
                                                        {Finished}  v
                                 <--------     [Application Data*]
Auth - {Finished}                -------->
       [Application Data]        <------->      [Application Data]

特筆すべき点は以下の通りです。

  • 1.3以降のバージョンを決定するために Client Hello に supported_version 拡張があります。
  • (EC)DHE の鍵交換は、key_share という拡張を使います。クライアントから始まることに注意して下さい。
  • クライアントがサーバの証明書を使ってサーバを認証する場合、signature_algorithms拡張の送信が必須となってます。サーバ認証の値が、暗号スイートに含まれないからですね。
  • Server Hello で返すほとんどの拡張は、暗号化されている Encrypted Extensions に入ります。たとえば、ALPN で何のプロトコルを選んだかは、暗号化される訳です。
  • CertificateVerify は、Certificate に対応する秘密鍵で生成した署名です。サーバを認証するのが目的です。TLS 1.2 とは違い、鍵交換とは明確に切り離されています。
  • Finishedは初期の共有鍵で暗号化されています。
  • サーバは Finished を返した直後から、Application Data を暗号化して返せます(0.5RTT)。HTTP の通信はクライアントから始まりますが、メールのプロトコルはサーバ側から始まることが多いので、TLS 1.2 と顕著な差が出ます。クライアントからの Application Data の送信は、このハンドシェイクでは1RTTです。

TLS 1.3 開発日記 その3 バージョン

これは、http2 Advent Calendar 2016の7日目の記事です。

今回はTLSのバージョンについて書きます。TLSのバージョンは、Client Hello と Server Hello を交換することで決めます。

Client Hello

TLS 1.3 の Client Hello は、TLS 1.2 と互換性を維持するために、構造が死守されています。

TLS 1.2 の Client Hello の定義はこう:

struct {
    ProtocolVersion client_version;
    Random random;
    SessionID session_id;
    CipherSuite cipher_suites<2..2^16-2>;
    CompressionMethod compression_methods<1..2^8-1>;
    select (extensions_present) {
      case false:
        struct {};
     case true:
        Extension extensions<0..2^16-1>;
    };
} ClientHello;

TLS 1.3 の Client Hello の定義はこう:

struct {
   ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
   Random random;
   opaque legacy_session_id<0..32>;
   CipherSuite cipher_suites<2..2^16-2>;
   opaque legacy_compression_methods<1..2^8-1>;
   Extension extensions<0..2^16-1>;
} ClientHello;

TLS 1.2 では extensions がない場合はまったくバイト列が生成されません。一方、TLS 1.3 では長さ0を表現する 0x0000 が生成されます。しかし、extensionsはほとんど必ず存在するので、その違いを気にする必要はありません。

TLS 1.3 では、session_id や compression_methodsは利用されないので、legacy という接頭辞が付いています。賢明な読者の方ならお気づきでしょうが、version にも legacy が付いています。これはどういうことでしょうか?

TLS1.2でのバージョンの決定

TLS 1.2までは、バージョンを以下のように決定していました。

  • クライアントは Client Hello の version にサポートしている最大のバージョンを入れる
  • サーバは、クライアントが教えてくれた最大のバージョンと、自分がサポートしているバージョンとを照らし合わせて利用するバージョンを決め、その値を Server Hello の server_version に入れる。

これでうまくいきそうですが、それは仕様上の話です。現実の世界では、Client Hello の version を最大値だと思わずに、「そんなバージョン知らない」と言ってコネクションを切ってしまうサーバがたくさん存在しています。その場合、クライアントはバージョンを下げて再び接続を試みる必要があり、応答時間が遅くなるという問題がありました。

TLS1.3でのバージョンの決定

TLS 1.3 では、legacy_version を TLS 1.2 の値に固定します。そして、supported versions という拡張にサポートしているバージョンのリストを入れます。

TLS 1.3クライアントとTLS 1.3サーバの場合:サーバは supported version の中にある TLS 1.3 を選び、Server Hello の version に入れて返します。

TLS 1.3クライアントとTLS 1.2サーバの場合:サーバは supported versionを知らないので無視します。そして、version にある TLS 1.2 を選んで返します。このサーバが、上記のダメなサーバでもうまく動きます。

Server Hello

念のため、Server Hello の構造も確認しておきましょう。

TLS 1.2 の Server Hello はこう:

struct {
    ProtocolVersion server_version;
    Random random;
    SessionID session_id;
    CipherSuite cipher_suite;
    CompressionMethod compression_method;
    select (extensions_present) {
      case false:
        struct {};
      case true:
        Extension extensions<0..2^16-1>;
    };
} ServerHello;

TLS 1.3 の Server Hello はこう:

struct {
    ProtocolVersion version;
    Random random;
    CipherSuite cipher_suite;
    Extension extensions<0..2^16-1>;
} ServerHello;

TLS 1.3 の Server Hello には、session_id がありません。つまり構造が違うのです!

レコード

TLSのメッセージには、Handshake、Alert、Appication Data などがあります。Client Hello や Server Hello は Handshake に属します。これらのデータを本文だと思うと、型や長さを示すヘッダが必要です。このヘッダ+本文の構造のことを TLS ではレコードと呼んでいます。

以下に TLS 1.3 の本文が(暗号化されてない)平文のためのレコードの構造を示します:

struct {
    ContentType type;
    ProtocolVersion legacy_record_version = 0x0301;    /* TLS v1.x */
    uint16 length;
    opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

驚くべきことに、レコードにも TLS のバージョンを示すフィールドがあります。この値と、Client/Server Hello のバージョンの値が食い違ったら、何が起きるでしょうか?

今から大切なことを言いますので、この記事ではこれだけ覚え下さい。

ある1つの値を2つの場所で指示しているなら、それはバグです

バグの例としては、UDPのヘッダに存在する長さフィールドが挙げられます。TCPヘッダには長さフィールドはありませんから、必要ないんです。

legacy_record_version の名前が示すように TLS 1.3 このフィールドを意味がないものとし、中間装置やサーバが最も受け入れてくれそうな、TLS 1.0 の値に固定します。(本文が暗号化されたレコードでは、このフィールドを削除しようという議論もあります。)

おわりに

今日は大切なことを説明しました。データ構造を設計する機会がある人は、心に刻んでおきましょう。

TLS 1.3 開発日記 その2 暗号スイート

これは、http2 Advent Calendar 2016の3日目の記事です。

今回は暗号スイートについて書きます。TLS 1.2 の暗号スイートは、たとえば以下のような感じでした。

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

これは次のような意味です。

  • 鍵交換は使い捨て楕円Diffie Hellman(ECDHE:EllipticCurve Diffie Hellman, Ephemeral)
  • サーバ認証はRSA
  • 通信の暗号化はAES 128のGCM(Galois/Counter Model)モード
  • ハッシュ関数がSHA256

TLS 1.1 以前だと、この暗号スイートのハッシュの意味が異なるんですが、面倒なので説明は割愛します。

TLS 1.3だと、暗号スイートが以下のようになります。

TLS_AES_128_GCM_SHA256

これは以下のような意味です。

  • 通信の暗号化はAES 128のGCMモード (TLS 1.2 のそれとは方法が若干異なります)
  • ハッシュ関数がSHA256 (HMAC や HKDF などに使われます)

暗号スイートは、TLS 1.2 と同様に、Client Hello でリストが提示され、Server Hello で選択されます。

さて、鍵交換とサーバ認証はどこにいってしまったのでしょうか?

TLS 1.3 の鍵交換

TLS 1.3 の鍵交換は、Client Helloの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;

この中に、P256なECDHEの公開鍵やX25519なECDHEの公開鍵が入るわけです。見ての通り、サーバはその中から1つを選び対応する公開鍵をServer Helloに1つだけ入れます。

TLS 1.3のサーバ認証

TLS 1.3 のサーバ認証は、Client Helloのsignature algorithms拡張で提示されます。

enum {
    /* RSASSA-PKCS1-v1_5 algorithms */
    rsa_pkcs1_sha1(0x0201),
    rsa_pkcs1_sha256(0x0401),
    rsa_pkcs1_sha384(0x0501),
    rsa_pkcs1_sha512(0x0601),

    /* ECDSA algorithms */
    ecdsa_secp256r1_sha256(0x0403),
    ecdsa_secp384r1_sha384(0x0503),
    ecdsa_secp521r1_sha512(0x0603),

    /* RSASSA-PSS algorithms */
    rsa_pss_sha256(0x0804),
    rsa_pss_sha384(0x0805),
    rsa_pss_sha512(0x0806),

    /* EdDSA algorithms */
    ed25519(0x0807),
    ed448(0x0808),

    /* Reserved Code Points */
    private_use(0xFE00..0xFFFF),
    (0xFFFF)
} SignatureScheme;

struct {
    SignatureScheme supported_signature_algorithms<2..2^16-2>;
} SignatureSchemeList;

そして、Server Certificate Verifyで何が選択されたかが表明されます。

struct {
    SignatureScheme algorithm;
    opaque signature<0..2^16-1>;
} CertificateVerify;

おまけ

どうして、鍵交換とサーバ認証が暗号スイートから切り離されたか疑問に思うことでしょう。その理由は、そのうち説明します。つまり日記はまだまだ続きます。

TLS 1.3 開発日記 その1 実装状況

これは、http2 Advent Calendar 2016の1日目の記事です。

現在、IETFTLS 1.3 の標準化が大詰めを迎えています。僕も TLS 1.3 の標準化に参加しており、仕様の分かりにくい部分を直したり、TLS 1.3 を Haskell で実装したりしています。この開発日記のシリーズでは、TLS 1.3の仕組みを説明していこうと思います。

そもそも、なぜ TLS 1.3 が必要なのかは、TLSの動向をお読み下さい。なお、次のTLSのバージョンを何にするかは、現在もめていて、1.3ではなくなる可能性もあることに注意して下さい。

現在の実装

現在利用できる TLS 1.3 の実装の一覧は、Implementationsにまとまっています。僕がクライアントとしてよく使っているのは、Firefox Nightly、Chrome Canary、および picotls です。

TLS 1.3 を気軽に試したいなら、Firefox Nightly や Chrome Canary をインストールし、Implementationsに書かれている設定をした後に、CouldFlare の TLS 1.3 テストサーバにアクセスしてみるといいでしょう。ページが表示されると、TLS 1.3 でアクセスできたことになります。

Implementationsには、Wiresharkも掲載されていますが、実際にはTLS 1.3に対応していません。tcpdump などでキャプチャしたパケットを表示できるようになるには、時間がかかるかもしれません。

Haskellの実装

僕は Haskell コミュニティで以下のような役割を持っています。

過去2年ぐらいの間、WarpをHTTP/2に対応させてきました。その成果は、Experience Report: Developing High Performance HTTP/2 Server in Haskellという論文にまとまっています。また、TLSに関しては、足りない機能を実装してきました。

僕の次の目標は、HaskellTLSライブラリにTLS 1.3の機能を組み込むことです。(それが終わったら、QUICを実装したいと小声で言っておきます。)

TLS 1.3の開発は、Haskellの暗号ライブラリである cryptonite を拡張することから始まりました。cryptoniteには、Haskellで書かれた楕円曲線暗号があります。これを使って、HTTP/2に必要な ECDHE を実装しマージしてもらっていました。

しかし、Haskellで書かれた楕円曲線暗号は遅いという問題があり、Cで書かれたP256やCで書かれたX25519をバックエンドとして使えるように、抽象化のレイヤを設け、その上に ECDHE を再実装しました。確か4回ぐらい再実装したと思います。現在、その成果はレビュー中です。

次に、TLSライブラリ自体を拡張しました。現時点は、TLS 1.3のサーバ側のみ実装できており、4つのハンドシェイクモードすべてに対応しています。Firefox Nightly、Google Chrome、および picotls と相互接続性を確認しています。また、この TLS ライブラリを使って、Web アプリケーションが実際に動くことも確認しています。

TLS 1.3の実装を通じて驚嘆するのは、仕様の美しさです。理解すればする程、よく考えらえていると感じます。この開発日記では、その美しさの片鱗でも説明できればよいなと思います。

では、また次の記事でお会いしましょう。