代表的な面接トピック

ネットワーク面接:TLS 1.3 ハンドシェイクの仕組みと 0-RTT が安全な条件

一般難しい
Offer.cc 編集チーム公開日 更新日

質問

グローバル API が TLS 1.3 を使用しています。フルハンドシェイクと再開ハンドシェイクの流れを順を追って説明し、各段階で何が暗号化されるか、証明書がどのようにサーバーを認証するかを説明した上で、GET /rates と POST /transfers に 0-RTT を使用すべきかどうかを判断してください。

質問と適用範囲

グローバル API が TLS 1.3 を使用しています。フルハンドシェイクと再開(resumed)ハンドシェイクの流れを順を追って説明してください。各段階で何が暗号化されるか、証明書がどのようにサーバーを認証するか、鍵がどのように生成されるか、そして GET /ratesPOST /transfers が 0-RTT を使用すべきかどうかを説明してください。

明確なベースラインを設定します。クライアントは TCP 経由で HTTPS サービスに接続し、サーバーは X.509 証明書で認証し、フルハンドシェイクはエフェメラル ECDHE を使用し、再開接続は前回の接続で発行されたセッショントラフィックチケットを使用し、API エントリポイントはロードバランサーまたは CDN とします。HTTP/3 は TLS 1.3 を QUIC 接続確立に統合していますが、このベースラインではメッセージの順序とセキュリティ特性を具体化するために TCP 上の TLS レコードを使用します。

この質問は、バックエンド、クライアント、インフラストラクチャ、SRE、セキュリティ、および一般的なソフトウェアエンジニアリングの面接に適しています。面接官は単に ClientHello から Finished までの暗記を求めているわけではありません。候補者は、アイデンティティがこのハンドシェイクにどのようにバインドされるか、どの鍵がどの段階を保護するか、そしてネットワークの往復を 1 回削減することがなぜアプリケーション側で処理すべきリプレイ問題を生じさせるのかを説明する必要があります。

面接官が評価しているポイント

第 1 に、鍵交換、認証、バルク暗号化を明確に区別して説明できるか。証明書内の公開鍵は、通常ハンドシェイク全体のトランスクリプトに対する CertificateVerify 署名を検証するために使用されます。後続のすべてのビジネストラフィックを暗号化するわけではありません。対称トラフィック鍵は、ECDHE、PSK、および HKDF ベースの鍵スケジュールから生成されます。

第 2 に、暗号化の境界を正確に示せるか。最初の ClientHelloServerHello は、ネゴシエーションに必要な情報を公開します。ServerHello を処理した後、双方のピアはハンドシェイクのトラフィック鍵を導出できます。これらの鍵は EncryptedExtensionsCertificateCertificateVerify、および Finished を保護します。その後、独立したアプリケーショントラフィック鍵がアプリケーションデータを保護します。

第 3 に、セッション再開と 0-RTT の違いを理解しているか。セッション再開は、1-RTT ハンドシェイクを完了させつつ認証を短縮できます。0-RTT では、クライアントは最初のフライト(最初の送信)でアーリーデータ(early data)を送信します。どちらも PSK を使用できますが、現在の ServerHello から導出される鮮度(freshness)を欠いているのはアーリーデータのみです。

第 4 に、プロトコルのリスクをビジネスセマンティクスに落とし込めるか。TLS 1.3 はアーリーデータに対してより弱い特性しか提供しません。前方秘匿性(Forward Secrecy)がなく、接続をまたいだ非リプレイの保証もありません。これを有効にするかどうかは、HTTP メソッドだけで決定することはできません。課金、セッション作成、カウンターのインクリメント、報酬の発行、ワンタイムトークンの消費などによって判断が変わります。

第 5 に、可観測性のあるロールアウトとデバッグの計画を提案できるか。優れた回答では、フルハンドシェイク、1-RTT 再開、0-RTT を区別し、チケット、ALPN、HelloRetryRequest、アーリーデータの受け入れまたは拒否、HTTP 425 Too Early を確認し、エッジ TLS 終端からオリジンへの個別のセキュリティチャネルを検証します。

最初に確認すべき明確化の質問

  • どのトランスポートが使用されているか? TCP 上の HTTPS と QUIC/HTTP/3 はどちらも TLS 1.3 暗号技術を使用しますが、メッセージの伝送方法や接続確立方法が異なります。ベースラインでは TCP を使用します。
  • 初回接続か、それとも再開接続か? 初回接続には使用可能な PSK がなく、証明書認証が必要です。再開接続は以前のチケットを提示できますが、必ずしも 0-RTT を送信するとは限りません。
  • サーバーはクライアント証明書を要求するか? ほとんどの Web API はサーバーのみを認証します。mTLS の場合は、クライアントの CertificateCertificateVerify が追加されます。
  • TLS はどこで終端されるか? CDN またはロードバランサーで終端される場合、クライアントはそのエッジエンドポイントを認証します。エッジからオリジンへのトラフィックは、独自の暗号化とアイデンティティの決定を持つ独立した接続です。
  • 操作のリプレイセマンティクスは何か? GET /rates は本当に読み取り専用か?ワンタイムトークンを消費したり、課金対象のカウンターをインクリメントしたり、高負荷な処理をトリガーしたりしないか?POST /transfers 上の冪等性キー(Idempotency Key)だけでは、アーリーデータのリスクを自動的にすべて排除できるわけではありません。
  • 双方が HTTP アーリーデータを正しく実装しているか? TLS 層がアーリーデータを拒否した場合や、サーバーが 425 を返した場合、クライアントは確立された接続上で安全にリトライする必要があります。

30秒の回答フレームワーク

「フル TLS 1.3 ハンドシェイクでは、クライアントはバージョン、暗号スイート、ECDHE 鍵シェアを含む ClientHello を送信します。サーバーは ServerHello でパラメータを選択し、自身の鍵シェアを返します。双方は ECDHE の結果とトランスクリプトを HKDF に渡してハンドシェイク鍵を導出するため、ServerHello 以降の拡張、証明書、署名、Finished は暗号化されます。クライアントはチェーン、ホスト名、CertificateVerifyFinished を検証し、自身の Finished を送信します。後続のデータは個別のアプリケーショントラフィック鍵によって保護されます。

再開は以前のチケットに関連付けられた PSK を使用し、完全な証明書認証を回避できます。0-RTT も有効になっている場合、クライアントは ClientHello と共にアーリーデータを送信します。このデータは PSK のみに依存し、前方秘匿性がなく、接続をまたいでリプレイされる可能性があります。私は、明示的にリトライセーフで副作用のない読み取りのみを許可します。送金処理は、一貫したゲートウェイポリシー、425 Too Early、通常のリトライサポートを備えた上で、ハンドシェイクの完了を待つ必要があります。」

ステップバイステップの詳細解説

ステップ 1: ClientHello がネゴシエーション素材を提供

クライアントは ClientHello から開始します。通常、ナンス、サポートされている TLS バージョン、TLS 1.3 暗号スイート、署名アルゴリズム、サポートされている鍵交換グループ、1 つ以上の key_share の値、SNI や ALPN などの拡張が含まれます。TLS 1.3 暗号スイートは、主に AEAD アルゴリズムと HKDF で使用されるハッシュを選択します。他の拡張により、証明書の署名アルゴリズムと鍵交換グループがネゴシエートされます。

このメッセージは、通常の TLS 1.3 ではまだハンドシェイクのトラフィック鍵によって保護されていません。また、ピア間で ECH が正常に使用されない限り、通常 SNI も平文で見えます。最初の 1 バイトからすべてのフィールドが暗号化されていると主張するのは、ピアがまだこの接続用の共有トラフィック鍵を導出していないという事実を無視しています。

クライアントの鍵シェアは、エフェメラル ECDHE 公開値です。対応する秘密値はローカルに保持されます。公開値自体は共有シークレットではありません。各ピアは、自身の秘密値と相手ピアの公開値を組み合わせて、同一の ECDHE シークレットを計算します。

ステップ 2: ServerHello がパラメータを選択し、新しい鍵交換を完了

サーバーは TLS バージョン、暗号スイート、受け入れ可能な鍵シェアを選択し、ServerHello を送信します。ECDHE を使用する場合、エフェメラル公開値も提供します。ClientHelloServerHello によって暗号パラメータが決定され、双方は ECDHE シークレットを TLS 1.3 の HKDF 鍵スケジュールに入力できるようになります。

クライアントがサーバーの受け入れるグループの鍵シェアを提供しなかった場合、サーバーは HelloRetryRequest を送信し、選択したグループで別の ClientHello を要求できます。これにはもう 1 回のラウンドトリップが必要となり、当初のアーリーデータの試行は通常の成功パスとしてそのまま続行することはできません。本番環境で断続的に余分な RTT が発生する場合、ネットワークのジッターだと機械的に判断するのではなく、HRR を疑って調査すべきです。

ServerHello の後、双方はクライアントとサーバーのハンドシェイクトラフィックシークレットを導出できます。プロトコルは、これらの鍵で後続のハンドシェイクメッセージを保護します。パッシブな盗聴者は証明書や以降のほとんどの拡張を読み取ることができませんが、レコードサイズ、タイミング、および ECH で隠されていない初期情報は観測可能なままです。

ステップ 3: サーバーパラメータ、証明書、トランスクリプト署名によるアイデンティティの確立

サーバーは次に、暗号化された EncryptedExtensions(ALPN などの拡張の選択結果を含む)を送信します。クライアント認証を行いたい場合は CertificateRequest を送信します。一般的な一方向の HTTPS ハンドシェイクでは、次にサーバーの CertificateCertificateVerify、および Finished が送信されます。

これらのメッセージには明確に異なる役割があります。

メッセージ主な目的よくある誤解
Certificateサーバーの証明書チェーンと関連する拡張を提供証明書だけでハンドシェイクが信頼されるようになる
CertificateVerify現在のハンドシェイクのトランスクリプトに証明書の秘密鍵で署名証明書の公開鍵がすべてのビジネスデータを暗号化する
Finishedハンドシェイクから導出された鍵でトランスクリプトの完全性を認証し、鍵の確認を提供証明書を検証するだけである

クライアントは、PKI ポリシーに従ってチェーン、有効期間、ホスト名、許可された署名アルゴリズムを検証し、トランスクリプトに対する CertificateVerify 署名を検証します。これにより、証明書の秘密鍵の所持が、この ClientHelloServerHello、およびネゴシエートされたパラメータセットにバインドされます。攻撃者は無関係なハンドシェイクから署名を移植することはできません。

ステップ 4: Finished がハンドシェイクを完了し、アプリケーション鍵を分離

サーバーの Finished は、トランスクリプトとハンドシェイクシークレットから導出される検証値です。チェックが成功すると、クライアントに対して、観測されたネゴシエーションが改ざんされておらず、ピアが対応するハンドシェイク鍵素材を所持していることが証明されます。証明書認証や Finished の検証を行わずにデータを受け入れると、TLS のアイデンティティおよび完全性の保証が破棄されてしまいます。

次に、クライアントは自身の Finished を送信します。サーバーが mTLS を要求した場合、クライアントはまず自身の証明書と CertificateVerify を送信します。ピアは、ハンドシェイク鍵を再利用し続けるのではなく、アプリケーショントラフィックシークレットでアプリケーションデータを保護します。TLS 1.3 では、後で KeyUpdate を使用して新しいアプリケーショントラフィック鍵に移行することもできます。

前方秘匿性はエフェメラル (EC)DHE によって実現されます。ピアがエフェメラル秘密値と不要になったトラフィックシークレットを破棄していれば、将来サーバーの長期的な証明書秘密鍵が漏洩したとしても、過去に完了したフルハンドシェイクのキャプチャ済みアプリケーショントラフィックは復号できません。証明書鍵は認証を行うものであり、前方秘匿性の唯一の源ではありません。

ステップ 5: セッションチケットによる後続接続の PSK 再開

フルハンドシェイクの後、サーバーは NewSessionTicket を送信できます。クライアントはチケットと関連する再開シークレットを保存します。以降の接続で、クライアントは pre_shared_key 内でチケットの識別情報を提示し、バインダーを使用して対応する PSK の所持を証明します。サーバーがそれを受け入れた場合、PSK が再開接続を認証し、通常は証明書チェーンと CertificateVerify を再送信する必要がなくなります。

セッション再開にあたって、新しい ECDHE を諦める必要はありません。TLS 1.3 は PSK 単独、および PSK と ECDHE の組み合わせをサポートしています。本番環境の設計では、セッション再開の認証および計算上のメリットを維持しながら、通常の 1-RTT アプリケーショントラフィックに新しい Diffie-Hellman 素材を組み込めるため、通常 PSK+DHE が重視されます。PSK 単独は特性が異なるため、回答では選択したモードを明記する必要があります。

チケットは永続的なログイン認証情報ではありません。有効期限、バインドされた暗号パラメータ、サーバー鍵のローテーション、およびクラスター間の共有ポリシーが存在します。マルチリージョンサービスがチケットを一貫して復号または検証できない場合、フルハンドシェイクにフォールバックするかセッション再開を拒否することが適切な互換性分岐となります。セッション再開率を高めたいからといって、チケット暗号化鍵の有効期限を無制限に設定することは正当化されません。

ステップ 6: 0-RTT は最初のフライトにアーリーデータを配置

チケットがアーリーデータを許可している場合、クライアントは再開接続の最初のフライトで ClientHello とアプリケーションデータを送信できます。アーリーデータは、PSK から導出されたクライアントアーリートフィックシークレットで暗号化されます。送信時点で、クライアントはこの接続の ServerHello をまだ受信していません。

これによりレイテンシは削減されますが、2 つの重大な弱点が生じます。

  1. アーリーデータは今回の接続の ECDHE 交換を行わずに PSK から導出されるため、前方秘匿性がありません。
  2. 今回のサーバーナンスや ServerHello に依存しないため、プロトコルとして同じ暗号文が別の接続でリプレイされないことを保証できません。

サーバーは TLS アーリーデータのバッチを受け入れるか拒否するかを選択できます。サーバーが拒否した場合、クライアントは通常のハンドシェイク完了後に、実行が必要なリクエストを再送信する必要があります。アプリケーションは、サーバーがリクエストを処理した可能性があるもののクライアントが結果を受信できなかったという不確実性にすでに直面しています。0-RTT はさらに、攻撃者が接続をまたいで意図的に重複を作成できる余地を与えます。

ステップ 7: ビジネスの副作用に基づいて適用可否を判断

追加情報がない場合、RFC 8470 はクライアントが安全な(safe)HTTP メソッドをアーリーデータで使用することを許可し、安全でないメソッドや不明なメソッドを禁止しています。ただし、HTTP メソッドは最初のフィルターに過ぎません。リソースの実際の挙動が真のリスクを決定します。

リクエストここでの判断理由
GET /rates明示的なレビュー後にのみ検討読み取り専用であり、再実行可能で、ワンタイムトークン、課金、または許容できない計算の副作用がないこと
POST /transfers0-RTT は絶対に使用しないリプレイにより、送金の重複、監査イベントの重複、または異なる時間での認可が発生する可能性があるため
POST /login通常は拒否セッションを作成したり、チャレンジを消費したり、リスクカウンターを変更したりするため
GET /download?token=once単に GET であるという理由だけで許可しないワンタイムトークンやアクセス集計により、リプレイが重大な影響を及ぼすため

冪等性キーが存在するからといって、それだけで送金処理に 0-RTT を有効化してよい理由にはなりません。冪等性の記録はすべての処理ノード間で一貫しており、正しいトランザクション境界でアトミックにコミットされ、監査、通知、制限枠、不正検知などの副作用をカバーしている必要があります。また、盗まれたリクエストをリプレイされると、キーが固定されたり消費されたりするリスクもあります。高リスクな書き込み操作に対しては、ハンドシェイクの完了を待つのが依然としてより安全なポリシーです。

ステップ 8: ゲートウェイ、オリジン、クライアントのポリシーを一貫させる

HTTP は Early-Data: 1425 Too Early を定義しています。ゲートウェイが以前のホップでアーリーデータとして到着した可能性のあるリクエストを転送する場合、リスクシグナルを維持します。リクエストを安全に処理できないオリジンは 425 を返します。クライアントはハンドシェイク完了後に接続上でリトライし、そのリトライでは再びアーリーデータを使用してはなりません。

すべてのエッジインスタンスが同じリクエストクラスを一貫して処理する必要があります。あるインスタンスが即座に実行し、別のインスタンスがハンドシェイク完了を待つか拒否した場合、攻撃者はインスタンス間の差異を悪用して処理を重複させることができます。対策には以下が含まれます。

  • デフォルトで 0-RTT を無効にし、明示的に登録された読み取り専用リソースのみを許可する。
  • チケットの有効期間、アーリーデータのサイズ、受け入れウィンドウを制限する。
  • 共有または一貫してパーティショニングされたアンチリプレイ状態を使用する(ただし、これはリスクを低減するものであり、アプリケーションセマンティクスを完全に代替するものではないと認識すること)。
  • 高負荷時にはアーリーデータを一括して拒否し、リプレイによって高コストな処理が増幅されないようにする。
  • CDN、リバースプロキシ、オリジンがすべて Early-Data425 を正しく理解していることを確認する。

模範回答の例

「サーバー証明書認証を伴う TCP 上のフル ECDHE ハンドシェイクをベースラインとして説明します。

クライアントは、サポートされているバージョン、TLS 1.3 暗号スイート、署名アルゴリズム、鍵交換グループ、エフェメラル鍵シェア、および必要に応じて SNI と ALPN 拡張を含む ClientHello を送信します。サーバーは ServerHello でパラメータを選択し、自身の鍵シェアを返します。双方は ECDHE の結果とトランスクリプトを HKDF に渡してハンドシェイクトラフィック鍵を導出します。鍵交換フェーズの後、EncryptedExtensions、サーバー証明書、CertificateVerifyFinished が暗号化されます。

証明書はサーバーアイデンティティの信頼チェーンを提供します。CertificateVerify はこのハンドシェイクのトランスクリプトに証明書の秘密鍵で署名し、アイデンティティをこのネゴシエーションにバインドします。Finished はトランスクリプトの完全性を認証し、ハンドシェイク鍵の所持を確認します。クライアントがチェーン、ホスト名、署名、Finished を検証した後、自身の Finished を送信し、ピアは独立したアプリケーショントラフィック鍵に切り替えます。証明書の公開鍵はアイデンティティを検証するものであり、すべてのビジネスデータを暗号化するわけではありません。前方秘匿性は主にエフェメラル ECDHE とシークレットの消去によってもたらされます。

後続の接続は、NewSessionTicket に関連付けられた PSK を使用して再開できます。通常の PSK+DHE 1-RTT ハンドシェイクを使用することも、オプションで 0-RTT を使用することもできます。0-RTT を使用すると、クライアントは ClientHello と一緒にアーリーデータを送信しますが、それらのバイトは PSK 派生鍵のみで保護され、前方秘匿性がなく、接続をまたいでリプレイされる可能性があります。

したがって、POST /transfers を 0-RTT で送信することは絶対に避けます。冪等性キーを使用している場合でも、送金、監査、制限枠、通知の各処理がすべて重複に対して安全であることを証明する必要があります。GET /rates は、純粋な読み取りであり、安全にリトライ可能で、ワンタイムトークンや許容できない副作用がない場合にのみ許可リストに追加します。ゲートウェイとオリジンは Early-Data: 1 を伝播させ、安全でない場合は 425 Too Early を返し、クライアントはハンドシェイク完了後にリトライします。ロールアウト中は、フルハンドシェイク、1-RTT 再開、0-RTT を個別に測定し、チケットの受け入れ、HRR、拒否パス、ノード間のポリシーを検証します。」

よくある間違い

  • 証明書の公開鍵がその後のすべてのトラフィックを暗号化すると答える → TLS 1.3 のビジネストラフィックは対称トラフィック鍵を使用する → 証明書の署名検証と ECDHE/PSK および HKDF を明確に区別する。
  • ClientHello 以降のすべてが暗号化されると答える → 初期ネゴシエーションはハンドシェイク鍵の導出前に行われる → 後続のハンドシェイクメッセージの暗号化境界を ServerHello の後に置く。
  • 証明書チェーンのみを検証し、トランスクリプトを省略する → アイデンティティはこのネゴシエーションにバインドされる必要がある → CertificateVerifyFinished の個別の役割を説明する。
  • セッション再開と 0-RTT を同一視する → 再開された接続でも 1-RTT ハンドシェイクを待つことができる → まず PSK 再開を説明し、アーリーデータはオプションとして説明する。
  • 0-RTT を無償の RTT 削減として扱う → アーリーデータには前方秘匿性がなく、リプレイされる可能性がある → リスクをアプリケーションの副作用に対応づける。
  • すべての GET を自動的に許可する → GET でもトークンを消費したり、利用料金を請求したり、コストの高い処理をトリガーしたりする可能性がある → 実際のリソースセマンティクスに従って許可リストを管理する。
  • 冪等性キーがあれば送金リプレイが解決されると思い込む → クラスターの一貫性や周辺の副作用が重複する可能性がある → 高リスクな書き込みについてはハンドシェイクの完了を待つ。
  • CDN のみを設定し、オリジンを無視する → TLS 終端と HTTP 転送は 2 つのセキュリティ境界をまたぐ → エッジ、ゲートウェイ、オリジン、クライアント、および 425 の動作を整合させる。

フォローアップ質問

フォローアップ 1: なぜ TLS 1.3 は TLS 1.2 よりも高速なのか?

一般的な TLS 1.3 のフルハンドシェイクでは、古い設計から不要なメッセージや廃止されたアルゴリズムを削除することで、1 回のネットワーク往復(1-RTT)でパラメータのネゴシエーションとサーバー認証を完了できます。また、より統一された鍵スケジュールと再開メカニズムも貢献しています。ただし、リクエスト全体のレイテンシには DNS、TCP、サーバー処理、アプリケーションデータも含まれます。「TLS 1.3 は 1-RTT である」ということは、リクエスト全体が常に 1 RTT しかかからないという意味ではありません。

フォローアップ 2: 証明書の秘密鍵が漏洩した場合、過去のトラフィックはどうなるか?

これらのフルハンドシェイクがエフェメラル ECDHE を使用し、実装側でエフェメラル秘密値や不要になったトラフィックシークレットを破棄していれば、長期的な証明書鍵のみが漏洩しても、過去にキャプチャされたアプリケーショントラフィックを復号することはできません。これが前方秘匿性の価値です。チケット暗号化鍵、PSK、セッションシークレット、またはエンドポイントのトラフィックシークレットの漏洩は影響が異なり、チケットの有効期間、ログ、ローテーションに照らして評価する必要があります。

フォローアップ 3: アンチリプレイストアがあれば、なぜすべてのリクエストが 0-RTT に対して安全にならないのか?

グローバルなマルチリージョンシステムにおいて、レイテンシを増加させることなく、すべてのチケットとアーリーデータの受け入れを強整合性かつ 1 回限りの使用にすることは容易ではありません。ネットワーク分断、ノードの状態、チケットのローテーション、攻撃者による並行リプレイなどが境界を作り出します。プロトコルレベルの制御によってリプレイの有効期間や成功回数を制限することはできますが、アプリケーション側は依然として重複リクエストが発生し得ることを想定し、アーリーデータを適切な操作のみに制限する必要があります。

フォローアップ 4: 本番環境でフルハンドシェイク、再開、0-RTT のどれが使用されたかをどのように検証するか?

認可されたテスト環境で、openssl s_client -connect api.example:443 -servername api.example -tls1_3 -alpn h2 を使用してバージョン、証明書、ALPN を確認します。セッションを保存して再利用し、セッションが再開されるか、アーリーデータが受け入れられるかを観察します。サーバーのテレメトリには、機密情報を含めずにハンドシェイクモードと拒否理由を記録する必要があります。パケットキャプチャの復号は、クライアントがエクスポートしたセッションキーを使用して制御された環境でのみ行い、事後に確実にクリーンアップします。

フォローアップ 5: 425 Too Early は障害(outage)を意味するか?

必ずしもそうではありません。これはサーバーがリプレイリスクを拒否したことを意味し、アーリーデータの通常の制御パスです。クライアントはハンドシェイク完了後にリトライすべきであり、そのリトライではアーリーデータを使用してはなりません。ユーザー側で失敗が多発している場合は、クライアントのリトライ実装、Early-Data の伝播、サーバーインスタンス間の一貫性、不適切なエンドポイントが誤って 0-RTT 許可リストに追加されていないかを確認してください。

公開情報ソース

関連する質問