要約

  • OCSP ステープルは、特定の CertID について、認可された応答者が一定の時刻関係のもとで署名した主張である。署名は主張者の権限を示すが、その後の失効をすべて知っていたことや、このハンドシェイクのために生成したことは示さない。
  • producedAt、thisUpdate、nextUpdate は別の時計である。取得時刻、HTTP キャッシュ年齢、サーバーへの組み込み、クライアント時計、最大許容年齢も別に残さなければ、再利用可能な窓が見えない。
  • TLS は状態情報を運ぶだけで、接続を許可するかはクライアントの判断である。CertID、署名者の認可、状態と時刻、証明書列の位置、拡張の交渉、Must-Staple、ソフトフェイルかハードフェイルか、最終判断を一続きの証拠にする必要がある。

同じ good が二つの現実をまたいだ

応答者は09時55分に good を署名し、サーバーは09時57分に取得した。失効は10時07分に発生した。四分後にクライアントへ渡ったバイト列は、取得時のものと同じだった。

署名は通り、thisUpdate は未来ではなく、nextUpdate も過ぎていない。だからといって、その回答が10時07分の出来事を含むことにはならない。

ここで「偽造」と呼べば真正性と鮮度を混同する。「失効確認済み」と呼べば、応答者、キャッシュ、TLS 実装、クライアント方針に分かれた権限を一つの判定に見せかける。問うべきは、誰が、どの証明書について、いつまでの知識を、どの失敗方針のクライアントに提示したかである。

CertID が対象を固定する

OCSP はサイト全体の健康状態を尋ねる仕組みではない。CertID は発行者名のハッシュ、発行者鍵のハッシュ、証明書のシリアル番号、ハッシュ方式で問いを特定する。

別のシリアル番号に対する正しい署名は、弱い証拠ではなく無関係な回答だ。リーフ証明書への応答も、中間証明書を自動的には覆わない。証明書指紋、発行者、シリアル番号、CertID、証明書列の何番目に応答が付いたかを記録する必要がある。

RFC 6960 の good は意図的に狭い。最低限、そのシリアル番号で有効期間中の証明書が失効済みとして記録されていないことを意味する。発行された事実まで保証せず、thisUpdate より後の変化を即時に取り込む約束でもない。

署名が示すのは発言権である

確定的な OCSP 応答は、証明書発行者か、その発行者から OCSP 署名を明示的に認可された応答者が署名しなければならない。暗号署名だけでなく、発行者との認可関係も検証対象である。

これで分かるのは「この鍵がこの証明書について状態を述べる権限を持つか」だ。「握手時点の最新失効情報がこの鍵まで届いていたか」ではない。

認可されていない鍵の正しい署名は権限を欠く。認可された鍵の正しい署名でも、情報の時点は古いことがある。応答者証明書、OCSP 署名用途、発行者との関係、署名結果、元応答のハッシュを別々に残すべき理由である。

三つの時刻を「期限」にまとめない

producedAt は署名時刻、thisUpdate は状態が正しいと最後に分かった時刻、nextUpdate は新しい情報が利用可能になる最終時刻を示す。三つは同義ではない。

RFC 6960 は事前生成を認める。大量処理向けの RFC 9919 はキャッシュ利用を前提に nextUpdate を必須とし、クライアント時刻が thisUpdate と nextUpdate の間にあることを求める。

実運用には、失効登録、応答者への反映、HTTP Date/Age、取得、再検証、資格情報への組み込み、握手、クライアント時計が加わる。時計の巻き戻しに備え、単調増加の観測時刻も必要だ。

許容する時計ずれは管理された幅であって、鮮度の上乗せではない。未来すぎる thisUpdate、過ぎた nextUpdate、ローカルの最大年齢を超えた応答を別の理由として報告しなければならない。

キャッシュは例外ではなく設計である

ステープリングは、クライアントが接続ごとに応答者へ問い合わせる遅延とプライバシー漏えいを避け、応答者障害をハンドシェイクから切り離す。再利用は意図された動作だ。

RFC 9919 は Date、Last-Modified、Expires、ETag、Cache-Control を使った再利用と再検証を扱う。キャッシュ期限は OCSP 自身の更新境界より前でなければならず、古いプロキシ応答には no-cache で再取得する余地がある。

したがって「署名時刻上、受け入れ可能か」と「サーバーが約束した周期で更新したか」は別問題だ。サーバー側には取得元、HTTP ヘッダー、応答ハッシュ、取得・再検証・組み込み時刻、次の更新期限を残す必要がある。

起動時に正しい応答を読み込み、その後の更新処理が止まったなら、クライアントが拒否し始める前から運用は壊れている。

共有ステープルはクライアント nonce ではない

OCSP nonce は要求と応答を結び、古いコピーではなく新しい乱数に対応した回答を得るために使える。RFC 8954 は応答者が nonce を省略する場合も認め、短い有効期間を再利用リスクの軽減策とする。

多数のクライアントに配る同じステープルは、通常、接続ごとに異なる nonce を持てない。その鮮度の根拠は、署名された時間区間、ローカル最大年齢、継続的な更新である。新しい握手で運ばれたことを、新しく生成された証明として記録してはならない。

TLS の存在確認は受け入れ判定ではない

TLS 1.2 以前ではクライアントが status_request を送り、サーバーは CertificateStatus で応答できる。TLS 1.3 では証明書エントリーに状態情報を関連付ける。IANA の値 5 は拡張の識別を定めるが、実際の交渉や検証までは証明しない。

BoringSSL は取得したステープルを、整形式とは保証されない生データとして扱う。OpenSSL も要求、サーバーへの設定、クライアントでの取得を別の API に分ける。コールバックが動いたという指標は、妥当な応答が届いた証拠ではない。

欠落にも複数の理由がある。クライアントが要求しなかった、証明書交換のない再開セッションだった、サーバーが応答しなかった、証明書に TLS Feature がない、クライアントがソフトフェイルした。ClientHello、完全/再開ハンドシェイク、応答数、証明書列位置、各検証結果、最終理由を保存しなければ区別できない。

Must-Staple は欠落を制御する

Must-Staple と呼ばれる TLS Feature は、必要な状態情報を黙って省略するダウングレードを防ぐ。対象証明書を使うサーバーはクライアントの要求を満たす必要があり、満たさなければクライアントは接続を拒否できる。

ただし、すべてのクライアントが同じように強制するとは限らない。また、応答が存在すれば内容が正しいという意味でもない。CertID 不一致、無認可の署名者、悪い署名、期限外、unknown、revoked はそれぞれ独立して失敗する。

「必須なのに無い」と「有るが無効」の双方を試験する必要がある。単純なステープル有無の比率では、どちらも統治できない。

証明書列とセッション再開を数える

TLS 1.3 より前は通常リーフ用の一応答であり、TLS 1.3 は複数の証明書エントリーに応答を関連付けられる。OpenSSL も古い TLS の単一応答と TLS 1.3 の応答スタックを区別し、空の位置を許す。

これは中間証明書を常に全てステープルすべきという普遍規則ではない。観測側が個数と位置を失ってはならない、という要求である。

さらに、証明書を交換しない再開セッションでは OpenSSL の状態コールバックは呼ばれない。以前の認証状態に基づく再開を、新しい証明書状態確認として数えるのは誤りだ。

ライブラリの機能より、動いている更新を証明する

OpenSSL は CertID 検索、状態抽出、時計ずれと最大年齢を含む時間検査、署名と応答者権限の検証を分離している。一つの関数が最終判定を出すのではなく、アプリケーションが組み合わせ、失敗時の動作を決める。

nextUpdate がない応答では、最大年齢を設定しなければ非常に古い回答が受け入れられ得る。上限は設定ファイルだけでなく、実行時の判定理由として見える必要がある。

GnuTLS は OCSP を証明書検証に統合し、Must-Staple で状態がなければ専用の失敗を返す。サーバーは定期的に応答を更新し、使用中の読み取り専用資格情報を新しい世代へ切り替えなければならない場合がある。

BoringSSL はハンドシェイク中のオンデマンド取得ではなく、事前に取得・設定・更新する方法を勧める。応答者停止を接続遅延から切り離せる一方、更新スケジューラと資格情報交換が安全境界になる。

各プロセスがどの応答ハッシュを配っているか、いつ組み込んだか、更新が最後に成功したか、拒否まで何分か、どの接続が受け入れたかを実行中の事実として照合する必要がある。

境界を壊す試験

別シリアルへの正しい署名、認可のない応答者、未来の thisUpdate、過ぎた nextUpdate、最大年齢のない古い応答、破損した生データを投入する。失効後にまだ期限内の good を再利用し、偽造扱いせず受け入れ窓を測る。

必須ステープルを省略し、ローカル方針が求める中間証明書だけ欠落させる。TLS 1.3 再開時に新しい検査として記録されないことを確かめる。更新処理を止め、期限前に警報が出るかを見る。時計を戻しても単調年齢が増えるか確認する。

最後に応答者を停止する。既知の有効なキャッシュがある間は同期問い合わせで握手を詰まらせず、受け入れ可能な証拠がなくなった時点で、明示された方針どおりに失敗させる。

出典