要約

  • TLS 1.3 の 0-RTT は、再開情報を持つクライアントが ClientHello と一緒にアプリケーションデータを送れるようにした。1、2 往復を省ける一方、接続をまたぐ非再生性は保証しない。
  • RFC 8470 は意味を TLS に推測させなかった。中継は Early-Data: 1 を保存し、個々の資源の影響を知るオリジンが、再生に耐えられない要求だけを 425 で拒める。
  • 利用者エージェントは自動的に再試行できるが、その再送を早期データにしてはならない。メソッドや対象ではなく、実行を許す暗号学的な時点を変える。HTTP/3 も QUIC 上で同じ境界を保った。

待ち時間を削ると新鮮さも一部失われた

通常の TLS では、HTTP 要求は双方のハンドシェイクを待つ。新しい ServerHello の乱数を含む状態からアプリケーション鍵が導かれ、その後に要求が流れる。この待ち時間は遠距離ほど大きい。

TLS 1.3 は以前接続した相手に再開用のチケットを渡せる。クライアントは次の接続で記憶した PSK を提示し、ClientHello と同時にデータを送る。サーバーの新しい発言を待たないため、HTTP は 1、2 往復を取り戻す。

しかし RFC 8446 は代価も明記した。0-RTT は暗号化されているが前方秘匿性がなく、別接続で再生されない保証もない。新しい ServerHello より先に送られたため、その接続固有のサーバー乱数に依存できないからだ。

これは暗号が無いという話ではない。攻撃者は認証された内容を自由に改変できず、同じ接続内で複製したり 1-RTT データへ変換したりもできない。問題は、同じ再開状態を受け入れる別接続へ早期フライト全体を提示できる点にある。

再生と再試行は別の主体が始める

再試行はクライアントが知っている。応答が消え、接続が切れ、あるいは利用者がもう一度実行する。再生はクライアントの知らないところでコピーされる。二つは区別すべきだが、分散システムでは連結する。

状態を完全には共有しない二つのゾーンを考える。一方が早期要求を受理して実行し、他方が 0-RTT を拒否して通常のハンドシェイクだけを完了する。前者の応答が隠されれば、クライアントは後者へ正当に再送する。攻撃者の再生と利用者の回復が、二重の効果を生む。

TLS 1.3 は一回限りのチケット、ClientHello 記録、鮮度窓、チケットを受け持つ一つのストレージゾーンを挙げる。直接再生を減らせても、地域間の一貫性や再起動時の記憶が必要である。さらに TLS は、そのバイト列が静的画像なのか、権利消費なのかを知らない。

「安全なメソッド」は資源の証言ではない

HTTP の GETHEAD は安全なメソッドとされ、PUTDELETE には冪等性がある。この分類はクライアントの初期判断に役立つが、資源の実装を監査した証拠ではない。

RFC 8470 は、名目上安全な要求にも副作用があり得ると警告する。一回限りの署名 URL、従量課金の計算、キャッシュ変更、監査イベントは GET でも重複し得る。一方、トランザクション識別子を持つ書き込みは重複を検知できる場合がある。

クライアントは何を早く送りたいか知る。TLS は早期かどうか知る。ゲートウェイは転送先を知る。二度実行した結果を知るのは普通オリジンだけである。TLS 1.3 が 0-RTT 用のアプリケーションプロファイルを要求し、ライブラリによる勝手な有効化と自動再送を禁じる理由はここにある。

全拒否、保留、個別拒否

サーバーは TLS で早期フライト全体を拒否できる。境界は明快だが、一つの HTTP 要求だけを選べないため、同じフライトの全要求が速度を失う。

二つ目は処理を待たせる方法である。ルーティングに必要な解析だけを行い、影響を起こす処理はハンドシェイク後に解放する。多重化された要求ごとに異なる方針を持てる。

三つ目が 425 Too Early である。再生され得るまま処理したくない一要求だけを利用者側へ返す。明示的に許可された資源は 0-RTT を使い続けられ、敏感な資源だけが新しい時間を要求する。

一ビットの履歴をオリジンまで運ぶ

アプリケーションがクライアントの TLS を終端するとは限らない。CDN、リバースプロキシ、ゲートウェイを経ると、オリジンの直前接続は完成済みでも、最初の接続は早期だった可能性がある。

Early-Data フィールドの有効値は 1 だけである。クライアント側ハンドシェイク前に転送する中継は追加し、受け取った中継は削除してはならない。自身または別インスタンスがすでに送った可能性がある場合も印を残す。

これは高速化の希望ではなく、上流で再生可能性が生じたという履歴である。下流で新しいハンドシェイクを待っても、前のホップのコピーは消えない。印の付いた要求を安全に処理できないオリジンは 425 を返す。

複数の値や不正な形も同じ一ビットの危険として扱う。構文の曖昧さで履歴を無効化させないためである。応答やトレーラーには置かれず、Connection でホップ単位にしてもならない。

再送で変えるのは宛先ではない

早期送信に対して 425 を受けた利用者エージェントは、自動再試行すべきである。ただし再試行は早期データであってはならない。ハンドシェイクを終え、通常のアプリケーションデータで送る。

URI、メソッド、本文、意図は同じでよい。425 はリダイレクトではない。正しいオリジンの正しい接続から返り得るため 421 とも違う。割当量を示す 429、稼働不能を示す 503、本文送信を調整する 100 Continue でもない。

早期データまたは Early-Data: 1 の証拠が無い普通の要求に 425 を使うべきではない。そのクライアントが特殊な再試行を理解する保証がないからだ。応答は既定ではキャッシュできず、対象資源の表現でもない。IANA HTTP ステータスコード登録簿 は 425 を Too Early として RFC 8470 に結び付ける。

中継が清められるのは自分が始めた履歴だけ

要求がすでに Early-Data: 1 を持っていたなら、中継は 425 を転送しなければならない。危険は現在の接続より前に始まっており、下流だけを再接続しても変わらない。

中継自身が未マークの要求を初めて早期データとして受けた場合は非対称である。クライアント側ハンドシェイクの完了を待ち、オリジンへ再試行してよい。始点を知る者が、要求をその始点の外へ移せるからである。

オリジンが RFC 8470 を理解すると分からないまま、ゲートウェイが早期要求を渡してはならない。待つか、自ら 425 を返す。エッジはアプリケーションの知らない損失を代理承認できない。

最後のバイトより行為の開始を見る

ヘッダーが早期で本文が後から届く要求もある。QUIC では、早期に分類されたストリームがハンドシェイク後にアプリケーションへ届くことさえある。到着時計は出自を消さない。

重要なのはサーバーがいつ作用を始めたかである。ヘッダー解析やルート選択は無害でも、書込み、外部呼出し、トークン消費、キュー投入は違う。同じチケットの再生を受け得る全インスタンスは「完了前に作用しない」という不変条件を共有しなければならない。

再起動直後のノードは以前の再生記録を持たない。記録窓と停止時間が重なる間は 0-RTT を拒否すべきである。局所的な忘却を要求の権限に変えてはならない。

HTTP/3 では SETTINGS の記憶も加わった

RFC 9114 は QUIC 0-RTT に RFC 8470 の緩和策を適用する。さらにクライアントは新しい SETTINGS を受ける前に、前セッションで記憶した値に従って早期要求を送る。

サーバーが現在値との互換性を確認できなければ 0-RTT を拒否する。受理後に、すでに送られたデータが違反するほど制限を下げることもできない。

SETTINGS の互換性は資源の再生許容とは別である。一方は HTTP/3 の共有状態を、他方は実行結果を守る。待たない要求には複数の明示的証拠が必要になった。

出典と証拠の限界

閉じた資料は TLS 1.3 の RFC 8446HTTP Early Data の RFC 8470HTTP/3 の RFC 9114、現在の IANA HTTP ステータスコード登録簿 である。仕様上の交換条件と義務は示すが、現在の普及率、全クライアントの対応、特定事件、実測の節約、アプリケーションの exactly-once は示さない。