要約

  • QUICのハンドシェイク完了は各エンドポイントのローカルな視点で決まり、双方で同時に起きるとは限らない。
  • サーバーのHANDSHAKE_DONE、またはクライアントで条件を満たす1-RTT ACKは、確認とHandshake鍵破棄を示す。
  • サービスの準備完了を判断するには、確認を0-RTTの結果とアプリケーションの結果に結び付けなければならない。

運用上の誤解は、ダッシュボードが緑になる瞬間に生まれる。HANDSHAKE_DONEを受け取ると、システムは接続を準備完了として表示する。しかしサーバーが早期データを拒否している可能性は残る。その場合、クライアントは該当するすべてのストリームをリセットし、そこに結び付いたアプリケーション状態もリセットしなければならない。信号が間違っているのではなく、緑色に与えた意味が広すぎるのである。

RFC 9001の完了条件はローカルに定義される。ローカルのTLSスタックがFinishedメッセージを送信し、相手のFinishedメッセージを検証したとき、そのローカルなハンドシェイクが完了する。この出来事は両端で同時に観測されるとは限らない。したがってログには、どのエンドポイントが、どの証拠によって状態を確認したのかを残す必要がある。

確認の扱いはサーバーとクライアントで異なる。サーバーではハンドシェイクが完了すると確認済みになり、完了時点でHANDSHAKE_DONEを送信しなければならない。クライアントではHANDSHAKE_DONEの受信が確認になる。また、1-RTT鍵で送ったパケットをカバーするACKから確認を推論することもできる。HANDSHAKE_DONEを送信できるのはサーバーだけだ。このフレームは、サーバーがクライアントのHandshakeパケットを受信して処理したことを知らせる接続状態の信号であり、アプリケーションの返答ではない。

確認の中心的な意味は鍵のライフサイクルにある。確認されると、エンドポイントはHandshake鍵を破棄しなければならない。Handshakeという暗号エポックが終了するわけだ。ただし、それによってアプリケーションプロトコルの交渉が終わったこと、リクエストがアプリケーションコードに届いたこと、バックエンドが利用可能であること、永続的な保存が成功したことまでは分からない。

Initial鍵の破棄は別の出来事である。クライアントは最初にHandshakeパケットを送信した時点でInitial鍵を破棄する。サーバーは最初にHandshakeパケットを正常処理した時点で破棄する。Initial鍵が不要になったことと、Handshakeが確認されたことを一つのイベントにまとめてはいけない。別々の証拠を一つにすると、接続の実際の進行が見えなくなる。

0-RTTの判断も独立している。サーバーはEncryptedExtensionsにearly_data拡張を含めれば0-RTTを受理し、含めなければ拒否する。拒否した場合、サーバーは0-RTTパケットを処理してはならず、クライアントはすべてのストリームと、それらに結び付いたアプリケーション状態をリセットしなければならない。後から届くHANDSHAKE_DONEが、拒否されたデータを受理済みの仕事に変えることはない。

リプレイへの対応はアプリケーションプロトコルの責任である。0-RTTのアプリケーションデータはリプレイによって複数回処理される可能性がある。クライアントはアプリケーションが明示的に求めない限り0-RTTを使うべきではなく、プロトコルは許容できる利用方法を定義しなければならない。確認済みのハンドシェイクが、リプレイ安全性、正確に一度の実行、永続的なコミット、早期リクエストの受理を証明することはない。

クライアント側の1-RTT ACKも同じ境界にある。クライアントが送った最小の1-RTTパケット番号をカバーするACKは、RFC 9001が定める確認条件を成立させる。それ以上に、アプリケーションがデータを消費したこと、保存したこと、認可したこと、業務を完了したことを示すものではない。これは暗号状態の受領証であって、業務結果の受領証ではない。