2019/9/10 に QUIC のバージョンネゴシエーションに関する draft が更新されました。
Compatible Version Negotiation for QUIC draft-schinazi-quic-version-negotiation-01
この更新は、期限切れ防止の為のもののようで、00 から内容は変わっていませんが、私自身もちゃんと目を通していなかったので、これを気にきっちり把握しておきたいと思います。
QUICのバージョン互換に関してのおさらい
本題に入る前に、QUIC の draft-22 に記載されている QUIC のバージョンの取り扱いに関しておさらいしましょう。
QUIC のバージョンは Version Negotiation Packet によってクライアント・サーバが相互にサポートしているバージョンのものが使われることが保証されます。
大まかな流れとしては以下のような感じです。
クライアントはハンドシェイク時の Initial Packet や 0-RTT Packet に利用したい QUIC のバージョンを含めてサーバへ送付する
1 を受け取ったサーバは、指定されたバージョンを受け入れられる場合はそのままハンドシェイクを続行する。
受け入れられない場合は、Version Negotiation Packet を返送する。
Version Negotiation Packet にはサーバが受け入れ可能なバージョンのリストが埋め込まれている。
クライアントは Version Negotiation Packet を受信したら一度コネクションを破棄し、Version Negotiation Packet に含まれている中でサポートしているバージョンを選択して再度ハンドシェイクを実行する
この方式では、Version Negotiation Packetを投げるフローに入ってしまうと、 1RTT 無駄にしてしまいます。
QUIC では折角 1-RTT/0-RTT ハンドシェイクで RTT を短縮しているのに、こんなところで 1RTT も無駄にするのは以ての外です。
と言う訳で、なるべく RTT を無駄にしないような Version Negotiation (以後 VN) の仕組みが今回の draft により提案されています。
Compatible Version Negotiation for QUIC
それでは、RTT を無駄にしない VN を具体的にどう実現しているのでしょうか。
実はこれはとても単純で
『クライアントもサーバに自分がサポートするバージョンのリストを送信する』
だけです。
それでは、具体的な流れを見てみましょう。
クライアントは、サーバがサポートする可能性が最も高いバージョン(使用できる最も古いバージョンが一般的には選ばれる)を使用して Initial Packet を送付する。
この Initial Packet にはクライアントがサポートする全てのバージョン情報が、トランスポートパラメータである supported_compatible_versions フィールドに優先度の低い順に含まれている。
サーバは 1 を受け取ったら、優先順位の高いバージョンから自身がサポートしているバージョンであるかを順に照合していき、利用可能なものがあれば以後そのバージョンで通信を行う。
また、選択したバージョンをトランスポートパラメータの negotiated_compatible_version フィールドに入れて返送する。
以上です(とてもシンプル!)。
上記のトランスポートパラメータ CompatibleVersions の定義は以下の通りです。
struct {
select (Handshake.msg_type) {
case client_hello:
QuicVersion supported_compatible_versions<4..2^8-4>;
case encrypted_extensions:
QuicVersion negotiated_compatible_version;
}
} CompatibleVersions;
supported_compatible_versions は、前述したようにサポートする QUIC のバージョンを優先度の低い順にリストとして保持します。
negotiated_compatible_version は、サーバが選択した通信に使用される QUIC のバージョンが保持されています。
supported_compatible_versions を使用しない場合は「QUICのバージョン互換に関してのおさらい」で解説した流れで QUIC のバージョンは選択されます。
また、supported_compatible_versions には Initial Packet に含まれるバージョンで利用する為に予約された 0x?a?a?a?a
の書式に従うバージョンも含めることができますが、これはサーバ側では無視されます。
ちなみに、QUIC のフレームは自己記述型ではないので、明示的な指定がない限りは異なるバージョン間には互換がないと見なされます(これによりクロスプロトコル攻撃への耐性も持ちます)。
「互換性のある」ことが明示されているバージョン間で、ハンドシェイク時の Encrypted Extensions のような拡張機能が片方でしか対応していなかった場合、このパラメータは無視されます。
最後にセキュリティについて触れます。
「QUICのバージョン互換に関してのおさらい」で解説した VN の方法では、ダウングレード攻撃を防ぐことができません。
今回の draft で紹介されている方法では、最終的にバージョンを合意するのには TLS のハンドシェイクが実施されている必要がある為、(クライアントの使用できる最も古いバージョンではありますが)暗号化が施され、ダウングレード攻撃への耐性を持つことができます。
Compatible Version Negotiation for QUIC に関する解説は以上です。
この draft のように、QUIC の VN に関してはまだこれから仕様が詰められていく段階なようですので、もうしばらく動向を注視した方が良さそうです。