先週、IIJ Technical WEEK 2021で山本 和彦さんが説明していた "Chrome Magic" について、知らなかったので自分用にメモ。
山本 和彦さんの発表資料はこちら。
www.iij.ad.jp
なお、QUICHEのコードではChaos Protectionと書かれているので、ここではChaos Protectionと呼ぶ。
前提: 硬直化(ossification)
ネットワークの中間装置が、パケットを観測し何かしらの処理をします。しかし、中間装置がパケットを読むときに、決め打ちで特定バイト目を取ってきて処理したりすると、将来のプロトコルが変更されたときに予期せぬ挙動をもたらすことがあります。
20017年頃、Googleの実験的プロトコルであったGoogle QUICではフラグの仕様を変えた際に、予期せず通信が阻害される場合があったと述べています。その他にも、TLS1.3の標準化においても、新しいバージョンのTLSの通信が阻害される事例が報告されました。
このようなことを、硬直化(ossification)と呼んでいます。
QUICのような最近のプロトコルでは、硬直化(ossification)を防ぐために工夫されています。たとえば、パケット暗号化を行い不用意に中間装置がパケットを処理できないようにしたり、Greaseという手法を用いて実装の予期せぬ挙動を検知するようにしています。
以前書いた記事もご参考に:
asnokaze.hatenablog.com
前提: QUICのInitialパケット
QUICはコネクションを確立する際に、クライアントからTLS Clienthelloメッセージを含む Initialパケットが送られます。このInitialパケットは仕様で定義された鍵を用いて暗号化されています。
この暗号化は、硬直化(ossification)を防ぐため行われていました。
もちろん、Initialパケットの暗号化は仕様で与えられていますので、だれでも復号することはできます(改ざんは出来ない)。冒頭紹介した発表で触れられていましたが、どうやら、SNIなどを取得するために、特定バイト列を読み込む中間装置がいるようです。
これでは、硬直化(ossification)の原因となりかねません。正しくパケットをパースするよう強制するために、導入されたのが Chaos Protectionです。
Chaos Protection
Chaos Protectionが行われた Initialパケットは次のような形になっています。ClientHelloのメッセージは、複数のCRYPTOフレームに分割されなおかつ順番もシャッフルされています。
一つのCRYPTOフレームは、ClientHelloのバイト列のうち指定されたOffsetからLength長のデータが格納されます。そのため、複数のCRYPTOフレームをどのような順番でInitialパケットに詰めても、それを正しく元のバイト列に直すことが出来ます。
CRYPTO Frame {
Type (i) = 0x06,
Offset (i),
Length (i),
Crypto Data (..),
}
このように正しくフレームをパースしないと、ClienthHelloのメッセージが読めなくなっています。
具体的に Chaos Protection で行われていることを、眺めていきます。
quic/core/quic_chaos_protector.cc - quiche - Git at Google
SplitCryptoFrame();
AddPingFrames();
SpreadPadding();
ReorderFrames();
return BuildPacket(header, buffer);
- SplitCryptoFrame: CRYPTOフレームをランダム個に適当に分割 (lengthもランダム)
- AddPingFrames: PINGフレームをランダム個追加
- SpreadPadding: 仕様で規定された長さまで複数*のPADDINGフレームで埋めます (*PADDINGフレームは1バイトですが、シャッフルのために長さを持つかのように扱ってる模様)
- ReorderFrames: フレームをシャッフルする (すべてのフレームを、ランダムなフレームと位置をスワップする)
Initialパケット以外
Initialパケット以降は当事者間で共有された鍵で暗号化されるため、中間装置はパケットのほとんどの領域を読むことは出来ません。
ですので、Chaos Protectionを行う必要はありません。