Tuesday, July 30, 2019

HTTP のプライオリティが大きく変わろうとしている話(その他 IETF 105 雑感)

先週、モントリオールで開催された IETF 105 に参加してきました。

いろんなことがあったのですが、個人的に一番大きかったのは、HTTP/3 からプライオリティ(優先度制御)まわりの仕様を落とすことが決定したこと。

HTTP/3 は、トランスポートプロトコルである QUIC の上で動作する、次世代の HTTP プロトコルです。その設計は、QUIC ワーキングググループが、HTTP ワーキンググループから委託され、HTTP/2 の機能を移植する、という形式を取っています。

ところが、5月にロンドンで開催された QUIC ワーキンググループの中間会議で、一部参加者から HTTP/3 の優先度制御に対する不満が表明されたのです注1。それを受けて、QUIC ワーキンググループでは、HTTP/3 の優先度制御にあった HTTP/2 のそれとの差異を少なくする作業を進める一方、HTTP ワーキンググループでは、IETF 105 において、プライオリティをどうするか、議論することになっていました。

HTTP/2 の優先度制御を十分にサポートしていないサーバが多いことは事実です。まともに実装したサーバを動かしている大規模事業者はFで始まる2社くらいではないでしょうか。一方で、実装しようと思えばできるものを「嫌だから変えたい」と言って変えようとするのは、信頼感のある話ではありません。HTTP/2 の仕様策定に関わった当事者が主張するのであれば、なおさらです。

変えるのであれば、皆が納得できるようなものにすべきです。

という考えを踏まえ、今月初頭、僕は Cloudflare 社の Lucas Pardue 氏とともに、HTTP ヘッダベースの優先度制御手法を提案しました(提案仕様, 発表資料)。具体的には、以下のような特徴をもつ提案です。

  • HTTP/2 よりも単純な、各リクエストが独立した優先度を保持する方式
  • たった8種類の優先度
  • サーバとクライアントによる優先度の協調制御
  • HTTP ヘッダを利用することで、優先度制御を HTTP のバージョン非依存にするとともに、将来の拡張性を確保

HTTP/2 方式の堅持を主張するプレーヤーが減ったことで、焦点は、QUIC と HTTP/3 への影響を最小限に抑えつつ、新しい優先度制御手法に如何に軟着陸するか、という点に移りました。

結果、QUIC と HTTP の両ワーキンググループで HTTP/3 から優先度制御に関する規定を取り除くことが決定しました注2QUIC WG 議事録, HTTP WG 議事録)。HTTP/2 についても、現行の優先度制御を廃止するか、「優先度制御しない」ことを表明する機能を追加するかが議論され、後者が採択されました。また、新しい優先度制御手法を議論するサイドミーティングも開かれ、我々の提案した手法を中心に議論が行われました。

今後は、HTTP ワーキンググループにおいて組織された、新しい優先度制御手法を議論する小グループにおいて、迅速な合意が得られるかが焦点になります。

その他、僕の仕事に関係しているところでいうと、共著者の一人を務めている Encrypted SNI は徐々に進展し、また、maprg(測定と解析に関するリサーチグループ)では、Fastly で開発を進めている quicly を使った、衛星ネットワークでの QUICの パフォーマンス試験や、エンドポイントが露出するパケットあたり2ビットの情報を使って、プライバシー問題を抑えつつネットワーク事業者が輻輳の分析を行えるようにする手法の検証報告がありました。

感想:つかれた。

注1: 4月に開催された HTTP Workshop でも話があったのですが、僕は欠席したので雰囲気を知りません。
注2: 優先度を伝達するプロトコルと、その関連規定を取り除くのであって、サーバ独自の優先度制御など、プロトコルによらない手段を否定するものではありません。

Monday, July 22, 2019

pthread_once が嫌いすぎて再実装した話

pthread_once が嫌いです。なぜ嫌いかって言うと、こんな感じで、ファイルレベルのグローバル変数やグローバル関数が出現し、また、値を使う場所と初期化コードの位置が離れがちで可読性が下がるから。

static volatile BIO_METHODS *biom = NULL;

static void init_biom(void)
{
    biom = BIO_meth_new(BIO_TYPE_FD, "h2o_socket");
    BIO_meth_set_write(biom, write_bio);
    BIO_meth_set_read(biom, read_bio);
    BIO_meth_set_puts(biom, puts_bio);
    BIO_meth_set_ctrl(biom, ctrl_bio);
}

static void setup_connection(...)
{
    (いろいろ省略)

    // BIOを初期化
    static pthread_once_t init_biom_once = PTHREAD_ONCE_INIT;
    pthread_once(&init_biom_once, init_biom);
    BIO *bio = BIO_new(biom);
    ...
}


一方、pthread_onceを使う煩雑さを避けようとすると、自前でダブルチェックロックを書くことになるのですが、ダブルチェックロックをちゃんと書くのは難しい(参考:LCK10-J. ダブルチェックロック手法を誤用しない)し、実際間違えるし、毎回、間違えないように書こうとするのはストレスなんです。

というわけで、一念発起して、マクロを使って自分が本当にほしかった「once」を実装しました。

こんな感じで使います。

static void setup_connection(...)
{
    (いろいろ省略)

    // BIOを初期化
    static volatile BIO_METHODS *biom = NULL;
    H2O_MULTITHREAD_ONCE({
        biom = BIO_meth_new(BIO_TYPE_FD, "h2o_socket");
        BIO_meth_set_write(biom, write_bio);
        BIO_meth_set_read(biom, read_bio);
        BIO_meth_set_puts(biom, puts_bio);
        BIO_meth_set_ctrl(biom, ctrl_bio);
    });
    BIO *bio = BIO_new(biom);
    ...
}

グローバル変数やコールバック関数はなくなったし、初期化コードと利用コードが隣同士になって可読性が上がりました。

実際のコードは https://github.com/h2o/h2o/pull/2086 にありますので、ご参照ください。これで正しくダブルチェックロック実装できてるはず。

最後に一言。Cのマクロにブロック渡すのは超便利。

Wednesday, June 12, 2019

#doh_study で Encrypted SNI について話しました

6月11日に開催された #doh_study で登壇する機会をいただきました。

発表では、Encrypt SNI の設計と最近の変更を説明するとともに、背景としてインターネット上のプロトコルに求められる要件がどう変わってきているかを説明しました。



暗号化とプライバシー保護は通信プロトコルの前提条件となりつつありますが、それにともない、企業内の機器やペアレンタルコントロール等において通信内容をどのように管理していくのか。インターネットをより安全で便利なものにするために、ステークホルダーをまたいだ協力が求められています。

参考: Moving control to the endpoints: Motivations, challenges, and the path forward | APNIC Blog

Saturday, September 8, 2018

次世代プロトコル(QUIC etc.)のセキュリティとプライバシー @ #builderscon

9月6日より開催中の builderscon 2018 において、登壇の機会をいただき、インターネットのトランスポート層プロトコルについてセキュリティやプライバシーに関わる設計がどのように進めてられているか、TLS と QUIC を中心に発表しました。

QUIC のハンドシェイクプロトコルとパケット番号暗号化、TLS の Encrypted SNI 拡張は、いずれも僕が提案した機能あるいは方式が採用される予定のものなので、背景にある動機や意義を含め、整理して発表する機会をもらえたことをありがたく感じています。

聴講いただいた方々、また、スライドをご覧になる方々と、次世代プロトコルの暗号応用の手法のみならず意義を含め共有し、理解と議論を深めることができれば、これに勝る喜びはありません。



PS. QUIC のハンドシェイクプロトコルと Encrypted SNI 拡張については、以下のブログ記事もあわせてご覧いただけます。

QUICハンドシェイクの再設計、もしくはTLSレイヤの終焉
TLS の SNI 暗号化に関する Internet Draft を共同提出しました

Tuesday, July 3, 2018

TLS の SNI 暗号化に関する Internet Draft を共同提出しました

Eric Rescorla (RTFM), Nick Sullivan (Cloudflare), Christopher Wood (Apple) の各氏とともに、SNI を暗号化する TLS 拡張を提案する Internet Draft を提出しました。


アナウンスのメールにあるとおり、すでに NSS / Firefox と picotls / H2O で実装作業が開始されており、今月開催される IETF 102 で相互運用試験を行うとともに、標準化にむけた議論を深める予定です。

スノーデン事件以降、広範囲におよぶトラフィックモニタリングによるプライバシー侵害の懸念が明らかになるとともに、できるだけ多くのインターネット上の通信プロトコルを暗号化することが求められるようになってきました (参考: RFC 7258 - Pervasive Monitoring Is an Attack)。

その結果として、ウェブサイトの HTTPS 化が進み、DNS over TLS (RFC 7858)と DNS over HTTPS の標準化により DNS プロトコルの暗号化にも目処が立ち、残る課題は、クライアントがサーバと TLS ハンドシェイクを開始する際に平文で流れるサーバ名注1をどう暗号化するか、という点となっていました。

今回、共同提案した方式は、私が昨年提案した方式を簡略化したもので、ひとことでいうと「木を森の中に隠す」アプローチです。

具体的には、CDN のように、多数のサーバ名を同一の IP アドレスから配信しているという前提注2において、その IP アドレスに属する全てのサーバ名について、同一の ECDH 鍵を DNS を用いて配信し、クライアントはその鍵を利用して TLS ハンドシェイク内のサーバ名を暗号化する、という形になります。

クライアントはサーバのアドレス解決と同時に ECDH 鍵も DNS に問い合わせすることになりますが、HTTP/2 を前提とする DNS over HTTPS では複数の DNS 問い合わせを並列に実行可能なので、接続確立までのレイテンシは従来と変わらないと考えられています。

Firefox が DNS over HTTPS に対応するのに続き、Android が DNS over TLS をサポートするという流れにある今、あとは SNI 暗号化が実装されれば、通信内容を第三者に傍受されたとしても、どのサーバと通信しているのかが漏洩する可能性はとても小さくなります注2

インターネットを利用する人々のプライバシー保護の観点からは前進である一方、海賊版サイトのブロッキングにおいて、ISP レベルでの対応が難しくなることは事実ですが、こちらについては、立法や司法の判断に基づいて配信を停止するという、ユーザのプライバシーを侵害しない形式での手法を検討していただきたいと考える次第です。


注1: TLS では、ひとつの IP アドレスで動作するサーバが、クライアントが指定するサーバ名にあわせて異なるサーバ証明書を配信し、それに基づいて暗号方式を確立します
注2: サーバ名ごとに異なる IP アドレスを使っているケースでは、IP アドレスからどのサーバにアクセスしているか判明するため、サーバ名を暗号化する意味がありません

Thursday, June 14, 2018

Git で全ブランチから検索

ググったけどmacOSでぱっと動くのがなかったのでメモがてら書く。

全ローカルブランチから検索
% git grep keyword $(git branch | colrm 1 2)

リモート含む全ブランチから検索
% git grep keyword $(git branch -a | colrm 1 2)

Tuesday, June 12, 2018

QUICハンドシェイクの再設計、もしくはTLSレイヤの終焉

先週スウェーデンのKistaで開催された第5回QUIC Interimで、ハンドシェイクプロトコルの再設計案の採用が決まりました。

提案者として、その背景にある考え方を整理したいと思います。

▪️提案内容

詳しくはDesign Docを見てもらえばいいとして、ざっくりいうと、
  • TLSスタックをふたつに分割し
  • パケットはQUICがレイアウトしたバイト列をTLSスタックが提供するAPIを使って暗号化注1して生成
  • ハンドシェイクメッセージについては、平文のメッセージをTLSスタックとQUICスタックとの間で交換し、QUICスタック側で上記手法によるパケット化暗号化を行う
というものです。

これにより、たとえばサーバがハンドシェイク時に送出するパケットの構造は以下のようにかわります。

図1. 従来方式

図2. 新方式

赤は難読化(つまり正当なパケットと攻撃との区別がつかない)、黄は未認証の暗号化(通信は暗号化されているが、誰と話しているかはわからない)、青は認証済の暗号化を示しています。従来は5層のレイヤで2重の暗号化をしていたのが、4層のレイヤで1重の暗号化を行うようににかわることがわかります。

なぜ、このような変更を採用することになったのでしょうか。

▪️「レイヤ化」アプローチの限界

伝統的なTLSスタックにおいて、TLS接続はTCP/IP接続の拡張として表現されます。たとえば、JavaのSSLSocketはSocketを拡張したクラスです。

書き込みや読み込み命令はSSLSocketに対して同期命令として発行され、SSLSocketの中で適宜Socket I/Oに変換されます。

この手法の問題は、ソケットの管理が困難になりがちなことです。

たとえば、イベントドリブンなプログラムにおいては、同期I/Oではなく非同期I/Oが求められます。このことは、TLSスタックに非同期I/O用のモードを別途設けるか、あるいは、TLSスタックに対し、アプリケーション側からソケットのようなAPIをもつバッファを提供してやる必要がでてくることを意味します。

また、ヘッドオブラインブロッキングの影響を制御するために、暗号化するブロックサイズを制御し、ブロック単位で送信しようにも、そのような制御を可能にするインターフェイスが存在しないという問題があります注2

これらの点に鑑み、H2Oでは、レイヤ化ではなく、入力バッファと出力バッファを引数としてハンドシェイクや暗号化関数を呼び出すというコーデックスタイルのAPIをもつ独自のTLSスタック「picotls」を開発し、TLS 1.3むけに採用してきました。

▪️「トランスポート層による暗号化」の必要性

TCP上でTLSを使う上での問題点のひとつが、第三者がいつでも接続をリセットできるという点です。このような攻撃が可能なのは、TCP自体が認証付き暗号によって保護されておらず、リセットを引き起こすようなパケットを誰でも生成できてしまうからです。

このようなトランスポート層への攻撃に対処するため、QUICのこれまでのドラフトでは、QUICの上で動くTLSスタックから鍵を「エクスポート」し、この鍵を使ってパケットを暗号化してきました。

この点を指して、picotlsの共同開発者であるChristian Huitema氏は「TLSの利用形態はレイヤからコンポーネントへ変化している」と指摘してきました。プログラミング上の都合のみならず、プロトコル設計上の都合からも、従来のTLSをレイヤとして利用するアプローチが限界に来ていたことがわかります。

利用形態の変化はともかく、これまでのドラフトのアプローチは以下の各点の問題を抱えるものでした。
  • 暗号化をTLSとQUICという2つのソフトウェアスタックで行うという二重暗号化
  • 鍵交換後もハンドシェイク完了まで攻撃に対し脆弱
  • 鍵の使用開始タイミングが不明確

これらの問題を解決しようとする動きとしては、先行してIETF 101におけるEric Rescorla氏によるDTLSへの移行提案がありましたが、DTLSのパケットフォーマットがQUICが必要する機能を提供できない点や、再送制御がハンドシェイク前と後と異なってしまう点などが問題視されていました。

それを踏まえ、DTLSという、QUICとは異なるプロトコルを組み合わせるくらいなら、いっそTLSスタックを分割して、QUICのパケットフォーマットをそのまま使おう、というのが、今回の提案の骨子だったわけです。

従来のドラフトと比較した利点としては、
  • 二重暗号化がなくなる
  • TLS 1.3と同じタイミングで暗号鍵を変更するため、ハンドシェイク中の攻撃耐性が向上する注3
  • 鍵管理をTLSスタックに依存できるようになるので、QUICスタックの品質向上に寄与する
  • パケットフォーマットは引き続きQUICワーキンググループで設計管理、拡張される
といった点があげられることになります。

また、この提案が受け入れられた背景として、TLSスタックの従来のAPIに限界があることが共通理解となっていた、という点もあげられるでしょう。

▪️まとめ

QUICではTLSをハンドシェイクと暗号化という2つの機能に分割して使用することになりました。このことは品質向上につながります。

TLSから見ると、TCP上での使用を前提としたTLS 1.3、UDP上での使用を前提に異なるパケットフォーマットを採用したDTLS 1.3に加え、第3のパケットフォーマットをもつプロトコルが誕生することを意味します。

今後策定されるプロトコルにおいて暗号化が必須であることを考えると、TLSのハンドシェイクメッセージを使いつつ、プロトコル毎に異なるパケットフォーマットを定義する流れが一般化するかもしれません。

長期的にみると、TLSの解体と再構成につながるかもしれませんね。


注1: TLSスタックが共通鍵を提供しQUICスタック側で暗号化を行ってもよい
注2: TLSスタックの下にあるソケットAPIはあくまでバイト列をやりとりするためのものであって、レコードの切れ目がどこにあるかを表現する前提になっていない、ということ
注3: 図2において、赤で示される、パケット注入攻撃が可能なタイミングが短くなっていることが確認できます