課題とスコープ
決済パートナーが、すべてのHTTPリクエストの送信者の証明と完全性を求めています。RFC 9421を使用して署名と検証を設計し、対象コンポーネント、リプレイ対策、鍵ローテーション、プロキシによる書き換えについて説明してください。
RFC 9421は、署名入力、対象となるHTTPコンポーネント、およびcreated、expires、nonceなどのパラメータを分離して扱います。この質問では、単にボディをハッシュ化するだけでなく、「誰が署名したのか」と「どのバイト列が署名されたのか」を相互運用可能な形で実現できるかが試されます。
面接官が評価するポイント
対象コンポーネントがリクエストのセマンティクスとバインドされているか、双方が署名ベースを同一に正規化しているか、時刻・ノンス・鍵IDによってリプレイが防止されているか、プロキシ・リトライ・圧縮・テナントが検証にどう影響するか、そして鍵の公開・失効・モニタリングが運用可能かどうかを評価します。
30秒の回答フレームワーク
「メソッド、ターゲットパス、オーソリティ、重要なビジネスフィールド、およびContent-Digestを対象に含め、アルゴリズム、パラメータ順序、クロック許容範囲を固定します。サーバーはSignature-Inputをパースし、同じベースを再構築して鍵を特定し、署名を検証して、created、expires、およびワンタイムノンスを確認した後にのみビジネスロジックへと進みます。バージョニングされた鍵IDにより、二重検証と失効をサポートします。プロキシは対象外のフィールドのみ書き換え可能とし、それ以外の場合は検証を失敗させます。」
ステップごとの詳細な回答
ステップ1: 証明すべき特性を定義する
署名は、鍵の保持者が選択されたHTTPコンポーネントに署名したことを証明し、改ざんの検知に役立ちます。これはTLS、認可、入力バリデーション、あるいはビジネス上の冪等性を置き換えるものではありません。プロトコルに認証、完全性、または否認防止のどれが必要かを明示します。
ステップ2: 対象コンポーネントを選択する
最低限、メソッド、ターゲットパス、オーソリティを対象に含めます。必要に応じてcontent-digest、冪等性キー、テナントID、またはクリティカルなビジネスフィールドを追加します。代替可能な単一フィールドのみを署名したり、正当なプロキシが書き換えるヘッダーを無計画に対象に含めたりしてはなりません。コンポーネントリストはプロトコルの一部としてバージョニングします。
ステップ3: 署名ベースを正規化する
双方がRFC 9421のコンポーネント識別子、順序付け、パラメータエンコーディング、および派生コンポーネントのルールに従う必要があります。サーバーは生のヘッダー文字列を単純結合して推測するのではなく、パースされたリクエストとSignature-Inputからベースを再構築すべきです。未知のまたは重複した重要なコンポーネントは拒否します。
ステップ4: リクエストコンテンツを保護する
ボディを持つリクエストについては、Content-Digestを計算して署名に含めます。検証前に受信バイト列をハッシュ化し、圧縮、トランスコーディング、またはJSONの再フォーマットによってコンテンツが知らぬ間に変更されないようにします。プロキシが解凍または再エンコードを行う場合は、署名がその変換の前に行われるか後に行われるかを定義します。
ステップ5: 時間情報とリプレイ防御を追加する
createdを必須とし、有用な場合はexpiresおよび一意のnonceを必須とします。タイムウィンドウとクロックスキューを検証し、使用済みノンスをテナントごとに短時間保存します。同一ビジネスリクエストのリトライは、署名の有効期限を無制限にするのではなく、冪等性キーに依存します。時間チェックに合格したからといって、書き込み処理を2回実行しても安全であるとは限りません。
ステップ6: 鍵の発見とローテーション
Signature-Input内の鍵IDによって、管理されたディレクトリまたはJWKS形式のエンドポイントから公開鍵を選択します。バージョニングされた結果をキャッシュします。ローテーション中は新しい鍵を公開し、一定の重複期間中は新旧両方の鍵を許可し、トラフィックが移行した後に古い鍵を失効させます。秘密鍵は署名側エンドポイントで保持し、鍵データや完全な署名ベースをログに出力してはなりません。
ステップ7: プロキシおよびリトライの境界を定義する
どのレイヤーがトレーシングフィールドを追加し、リクエストをリトライし、あるいはオーソリティを変更できるかをドキュメント化します。対象コンポーネントに対する不正な書き換えは検証に失敗しなければなりません。プロキシはワンタイムノンスを複製したり、署名を別のターゲットに転送したりしてはなりません。検証前に書き込みや負荷の高いビジネスロジックを実行してはなりません。
ステップ8: 障害を安全に運用する
鍵ID、署名バージョン、失敗分類、クロックスキュー、ノンスの衝突、欠落コンポーネント、プロキシ元をリクエストIDとともに記録します。攻撃者の探索を助ける鍵データや詳細なベースを返すことなく、期限切れ、未知の鍵、ダイジェスト不一致など、パートナーが対処可能な分類を提供します。
トレードオフと境界
非対称署名 vs 共有鍵
非対称鍵はマルチパーティ検証と独立した失効を簡素化しますが、より多くのインフラを必要とします。共有HMACは軽量ですが、すべての検証者がリクエストを偽造できるため、信頼境界が明確な場合にのみ使用してください。
カバレッジ vs プロキシ互換性
対象範囲を広げると完全性は強化されますが、ゲートウェイが正当にフィールドを変更した場合に失敗する可能性があります。検証を暗黙のうちに弱めるのではなく、プロキシ契約をプロトコルに含め、必要に応じて境界で再署名を行います。
狭いウィンドウ vs オフラインリトライ
短いウィンドウはリプレイリスクを低減しますが、時刻同期と迅速なリトライが必要です。オフラインクライアントは長寿命の署名を再利用するのではなく、新しい署名を要求すべきです。サーバーはパートナーごとに限定された許容範囲を適用できます。
障害訓練とシステム進化
プロキシがパスを変更する
テスト用プロキシでパスまたはオーソリティを書き換え、ビジネスロジックの前に検証が失敗することを確認します。許可されたトレーシングヘッダーの変更が署名に影響してはなりません。
古いリクエストがリプレイされる
同じ署名とノンスを2回送信し、2回目の試行が拒否されることを検証します。同じ冪等性キーで新しいノンスを使用し、ビジネス層の冪等性を検証します。
鍵のローテーション
新旧の鍵を同時に公開し、ポリシーに準拠した署名を検証します。失効後は古い署名が失敗し、古いキャッシュによって無期限に有効であり続けることがないようにします。
よくある間違いとフォローアップ
間違い 1: ボディのハッシュのみを署名する
署名を別のパスやメソッドにコピーできるかどうかを問いかけます。対象となるターゲットコンポーネントがリクエストの意味を拘束しなければなりません。
間違い 2: HTTPSだけで十分だと想定する
TLSを終端する内部プロキシの後で、元の送信者とコンテンツをどのように認証するかを問いかけます。
間違い 3: ノンスと時刻を無視する
キャプチャされた決済リクエストが有効期間内にリプレイ可能かどうかを問いかけます。時刻、ノンス、冪等性を組み合わせます。
間違い 4: 古い鍵を即座に削除する
処理中のインフライトリクエストとマルチリージョンのキャッシュがどのように重複するかを問いかけます。失効前に二重検証を行います。
間違い 5: 失敗時に署名ベースの詳細を返す
鍵データ、署名入力、または内部プロキシの詳細を公開することなく、パートナーがどのようにデバッグするかを問いかけます。
発展的なフォローアップと模範回答
なぜContent-Digestを署名に含めるのですか?
ダイジェストはボディのバイト列を表します。署名はそのダイジェストをメソッド、ターゲット、およびその他のコンポーネントにバインドするため、有効なダイジェストを別のリクエストに転用することができなくなります。
書き換え後にプロキシは再署名すべきですか?
書き換えがダウンストリームプロトコルの一部である場合、境界プロキシは自身のアイデンティティとして署名すべきです。そうでない場合は、元の署名が検証可能な状態を維持できるよう、対象コンポーネントを保持しなければなりません。
認証と認可はどう異なりますか?
検証は、鍵の保持者がリクエストに署名したことを証明します。認可では引き続きテナント、アカウント、金額、冪等性、ビジネス状態をチェックします。署名単体で実行を認可することはできません。