先週スウェーデンの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において、赤で示される、パケット注入攻撃が可能なタイミングが短くなっていることが確認できます