『ハイパフォーマンスブラウザネットワーキング』メモその4

今回はTLS (SSL)のお話。ぶっちゃけ、最近この本を読み始めた動機は、このあたりの速度への影響をちゃんと知りたかったからなのです、ハイ。

4章 TLS

  • TLS (Transport Layer Security)の前身となるSSL (Secure Socket Layer)は、Netscapeによって開発が始められたプロトコル。TCPの直接の上位層で動作し、そのさらに上位のプロトコル (HTTP, email, etc)の動作に影響を与えることなくネットワーク通信時の安全性を提供する。
    • SSLが正しく使用されると、通信を傍受する第三者は通信のエンドポイント、暗号化の種類、そしてデータの送信頻度とおおよそのサイズを推測できるが、実際のデータの内容を読むことや、変更することはできない。
    • SSLがIETFによって標準化されたものが、TLS。
  • TLSは、上層で動作するアプリケーションにとって必要不可欠な暗号化認証データ整合性という3つのサービスを提供するよう設計されている。安全なWebアプリケーションは3つすべてのサービスを活用している。3つすべてを使う必要はないが、その場合は、そのリスクと意味合いを十分に認識しておくべきである。
    • 暗号化 … あるコンピュータから別のコンピュータへ何が送信されたかを見えなくする仕組み。
    • 認証 … 提供された証明書の正当性を検証する仕組み。
    • データ整合性 … メッセージの改竄や偽造を検出する仕組み。
  • 暗号化による安全なデータチャネルを確立するためには、ピア間で使用する暗号スイート、そして暗号化に使用される鍵について合意する必要があり、TLSプロトコルはこのやり取りを実行するためのハンドシェイクを明確に定義している。
  • このハンドシェイクでは、公開鍵暗号を使用している。これにより、ピア間がお互いの情報を事前に知ることなく、暗号化されていないチャネルを使って秘密鍵を受け渡すことができる。
  • TLSハンドシェイクの一部として、プロトコルはそれぞれのピアの本人性(identity)の証明もできる。この認証の仕組みがブラウザで使用されると、サーバが主張通りの相手(例えば、銀行)であり、名前やIPアドレスを偽装してなりすましを行っていないことを確認できる。この認証は、確立された信頼チェーンに基づく。さらにオプションとして、サーバがクライアントの本人性を認証することもできる
  • 暗号化と認証が実施されている場合、TLSプロトコルは独自のメッセージのフレーム化メカニズムを提供し、それぞれのメッセージをメッセージ認証コード(Message Authentication Code, MAC)で署名する。MACアルゴリズムは非可逆性を持つ暗号学的ハッシュ関数(事実上の)チェックサムであり、その鍵は接続する両者でネゴシエートされる。TLSレコードが送信される際には常にMAC値が生成されてメッセージに付加され、受信者は送られてきたMAC値を計算し確認することでメッセージの整合性と信頼性を裏付けることができる
  • TLSを使ってデータの交換を始める前に、クライアント-サーバ間に暗号化されたトンネルを確立しなければならない。クライアントとサーバはTLSプロトコルのバージョンについて合意し、暗号スイートを選択し、必要な場合は証明書を確認しなければならない。これらのステップにはそれぞれ新たなパケット往復が必要となり、TLS接続開始時にレイテンシを発生させる。以下、ニューユーク-ロンドン間の光ファイバ通信における片道28msの遅れを想定した例。
    1. TLSはTCP上で動作するので、まずパケット1往復によって3ウェイハンドシェイクを最初に完了させる。(0-56ms)
    2. TCP接続が行われると、クライアントは平文で動作仕様(TLSプロトコルのバージョン、サポートされている暗号スイートのリスト、TLSオプション、他)を送信する。(56-84ms)
    3. サーバはTLSプロトコルのバージョンを選択し、クライアントから提供されたリストから暗号スイートを決定し、自身の証明書を添付し、クライアントにレスポンスを返すオプションとして、サーバはクライアントの証明書と他のTLS拡張パラメータの要求を送信できる。(84-112ms)
    4. クライアントとサーバがTLSバージョンと暗号スイートに合意して、クライアントがサーバ証明書を受け入れた場合、クライアントはRSAもしくはDiffie-Hellman鍵交換プロセスを開始し、続くセッションのための共通鍵の生成に利用する(*RSAでは、サーバ証明書に含まれる公開鍵を使って、共通鍵を暗号化する)。(112-140ms)
    5. サーバはクライアントから送られてきた鍵交換パラメータを処理し、MACを検証することで、メッセージの整合性を確認する(公開鍵のペアの秘密鍵を使う)。そして、暗号化された「Finished」メッセージをクライアントに返す。(140-168ms)
    6. クライアントはネゴシエートされた共通鍵を使ってサーバから送信されたメッセージを復元し、メッセージのMACを検証する(自分が4で作成した鍵を使う)。これで問題がなければトンネル開通となり、アプリケーションデータを送信できるようになる。
  • 上記のように、TLS接続はTCPハンドシェイクに比べて最大2回の余計なパケット往復を必要とする。注意深く処理を行わなければ、TLS上でのアプリケーションデータ配信において、少なくとも数百ミリ秒程度のレイテンシが発生する。数秒に渡る可能性もある
  • 新規TLS接続は、「完全なハンドシェイク」のためにパケット2往復と、続くセッションのパラメータの検証および計算のためのCPUリソースが必要。しかし、この「完全なハンドシェイク」を毎回繰り返す必要はなく、「短縮ハンドシェイク」を使うと再訪ユーザのハンドシェイクを1往復に、「TLS False Start」を使うと新規ユーザのハンドシェイクを1往復に削減することができ、以前のセッションでネゴシエートされたパラメータを再利用して計算コストを削減できる。詳細は後述。
  • RSAハンドシェイクでは、まずクライアントがセッションの共通鍵として使用するための鍵を生成し、この共通鍵をサーバの公開鍵で暗号化し、サーバに送信する。サーバは秘密鍵を使ってこの鍵を復号し、鍵交換が終了する。この時点から、クライアントとサーバはネゴシエートされた共通鍵を使用してセッションを暗号化できる。
    • RSAハンドシェイクでは、常に同じ公開鍵/秘密鍵のペアがサーバの認証と共通鍵の暗号化に使用される。したがって、攻撃者がサーバの秘密鍵を取得しデータ通信を盗聴した場合、攻撃者はセッション全体を復号できる。
  • Diffie-Hellman鍵交換を使うと、クライアントとサーバはハンドシェイクで通信経路上で鍵そのものを直接やり取りすることなく共通鍵のネゴシエートできる。やりとりするのは、同じ鍵を計算によって作り出すためのパラメータ。ただし、クライアントとサーバのそれぞれが勝手に決めてかつ送出していない値がないと、鍵を計算できないようになっている。以下の解説がわかりやすい。
  • 認証は、TLS接続の確立に必要不可欠。暗号化されたトンネルを通して攻撃者を含む任意のピアと通信を行うことができるので、通信相手が信頼できる相手であると確信できない限り、暗号化が全く意味をなさない可能性がある
  • ピアの本人性は、以下のように確認する。
    • AliceとBobはそれぞれ自分の公開鍵と秘密鍵を生成する。
    • AliceとBobはそれぞれの秘密鍵を他人から隠す。
    • Aliceは自分の公開鍵をBobに送り、Bobも公開鍵をAliceに送る。
    • AliceはBob宛に新しいメッセージを生成し、自分の秘密鍵で署名する。
    • BobはAliceの公開鍵を使って提供されたメッセージの署名を検証する。
  • つまり、公開鍵暗号方式によって、送信者の公開鍵を使って、メッセージが正しい秘密鍵によって署名されたことを検証できる。しかし、送信者を承認するかどうかの決定は、まだ信頼に基づいている(=このメッセージは確かにBobから届いたものである。そしてそもそも、Bobは信頼できる相手である)。
  • Bobの友人であるCharlieは、面識のないAliceにメッセージを送って受け取ってもらうために、自分自身の公開鍵をBobの秘密鍵で署名してもらい、この署名(=Bobの秘密鍵で暗号化されたCharlieの公開鍵)を自分のメッセージに添付してAliceに送信する。このとき、Aliceはまず、Charlieの公開鍵に付与されているBobの署名を、自分が信頼しているBobの公開鍵を使って検証する。これで、AliceはCharlieが(Bobが自分の秘密鍵を使うほどに)信用に足る人物であることを確認し、さらにCharlieの公開鍵を使って、送られてきたメッセージがその信用に足るCharlieからのものであることを確認することができる。これが信頼チェーン(Chain of Trust)である。さらに、Bobに信頼されているCharlieの秘密鍵で署名されていれば、Daveもまた信頼できる、というふうに続く。
  • サーバの送ってくる証明書には、以下のような情報が含まれている。
    • 発行組織名
    • 有効期限
    • サーバの公開鍵
  • 自分がブラウザで通信するとき、誰を信頼すればよいのか?Bobにあたるものは何なのか?
    • 手動で指定された証明書
    • 認証局( CA, Certiicate Authority)
    • ブラウザとOS
  • 最も一般的な検証方法は、認証局(CA)を使うこと。ブラウザは信頼する認証局(ルート認証局)を指定し、その認証局は自らが署名するそれぞれのサイトを検証する責任を負う
    • これらの証明書が悪用されたり信用が失われていたりしないかの監査と検証も認証局が行う。
    • 認証局の証明書を持ったサイトの安全性が破られた場合、その証明書を無効とするのも認証局の責任。
    • チェーン全体の「信頼の起点」はルート認証局である。各ブラウザは信頼された認証局(ルート認証局)のリストを標準搭載している。
  • IPやTCPと同様に、TLSセッション内で交換されるデータは明確に規定されたプロトコルに従ってフレームに格納される。通常のアプリケーションデータ転送フレームワークは以下の通り。
    • TLSレコードプロトコルがアプリケーションデータを受信する。
    • 受信データがブロックに分割される。レコードあたり最大2^14バイト(16KB)
    • アプリケーションデータが圧縮される(オプション)。
    • MACまたはHMACが追加される。
    • ネゴシエートされた暗号化方式でデータが暗号化される。
  • 上記のステップが完了すると、暗号化されたデータは転送のためにTCP層に渡される。受信側ピアでは、同様の流れが逆向きに適用される。取り決められた暗号化方式でデータを解読し、MACを検証し、データを抽出してアプリケーションへ渡す。
  • レコードプロトコルの注意点は以下。
    • 最大のTLSレコードサイズは16KB。
    • それぞれのレコードは5バイトのヘッダに、最大32バイトのMAC、そしてブロック暗号利用時はパディングが付与される。
    • 暗号を解読してレコードを検証するためには、レコード全体が必要となる。
  • 可能であれば、アプリケーションにとって正しいレコードサイズを選択することが重要な最適化となる。小さなレコードは、レコードのフレーム化による大きなオーバーヘッドが発生する。一方、大きなレコードは、TLSに処理されてアプリケーションに渡される前に、TCPによって再構成されなければならない。
  • 暗号化された通信路の確立と維持は、両側のピアに計算コストを発生させる。具体的にはまず、TLSハンドシェイクに使われる公開鍵(非対称鍵)暗号の計算コストがある。公開鍵暗号は共通鍵暗号と比較してより大きな計算コストがかかり、Web黎明期には専用のハードウェアが必要であったが、現在ではソフトウェアで実現可能であり、またそれが占有するCPU時間も僅かなものとなっている。決して高コストではない。
  • TLS接続のセットアップにかかるレイテンシは、新規でも再開の場合でも、重要な最適化エリアである。TLSセッションはTCP上で動作するため、TCP最適化の手法が適用できる。TCP接続の再利用は暗号化されていないトラフィックにおいて重要な最適化であるが、TLS上で動作するアプリケーションに関しても同様に重要な最適化である。
  • Early Terminationは、クライアント-サーバ間の往復のレイテンシを最小化するためにサーバをユーザの近くに配置するというシンプルな技術。一番単純な方法は、ユーザに海や大陸の横断を強制してサービス提供元のサーバに直接させる代わりに、そのサーバが提供するデータやサービスを世界中のサーバにレプリケートする、もしくはキャッシュを作成すること(←多くのCDNがすでに行っているサービス)。クライアントはすぐ近くに位置するローカルプロキシとの間でTLS接続を行うことができる。このローカルプロキシがサービス提供元サーバと永続的かつ安全な接続を確立し、すべてのリクエストに対して代理でサービス提供元サーバと通信を行う。つまり、TCPとTLSハンドシェイクの往復はサービス提供元サーバと直接TLS接続を行うよりもはるかに迅速に行われ、接続確立にかかるレイテンシも大幅に削減される。
  • そもそもデータを送信しないことほど速いことはありえない。可能な限り送信するビットを減らすべき。TLSセッションキャッシュステートレスセッション再開は、再訪問者のパケット1往復を完全に除去し、計算コストを低減する。
  • TLSセッションキャッシュが依存するセッションIDは、SSL2.0で初めて導入され、ほとんどのクライアントとサーバでサポートされているが、ほとんどのサーバにおいて、デフォルトではONになっていない
  • セッション再開は、以前の訪問時にネゴシエートされていたセッションパラメータを再利用することで、ハンドシェイク時のパケット往復の削減と、ハンドシェイク時の計算コストの削減を実現する。しかし、初回訪問者や、以前のセッションの有効期限が切れている場合には意味がない。TLS False Startというオプションのプロトコル拡張を使用すると、ハンドシェイクが部分的に完了した時点でデータ送信側からアプリケーションデータを送信できるようになり、初回訪問者と再訪問者両方でパケット1往復のハンドシェイクが可能となる。
    • これはTLSハンドシェイクプロトコルを変更するわけではなく、アプリケーションデータを送信できる時点が変更されるだけ。クライアントがClientKeyExchangeレコードを送信すると、クライアントは暗号化のための鍵を既に知っているので、アプリケーションデータを送信できる。残りのハンドシェイクは、改竄されていないことを確認するために実施され、データ送信と並行して行うことができる。よって、False Startを使用すると、完全なハンドシェイクの場合でも、短縮ハンドシェイクの場合でも、パケット1往復でハンドシェイクを行うことができる。
    • すべてのモダンブラウザはTLS False Startをサポートしているが、サーバ側で特定の条件が整っている場合にのみ有効となる。
  • TLS上のアプリケーションデータは、すべてTLSレコードプトロコルで転送される。レコードの最大サイズは16KBで、選択された暗号化方式によって、ヘッダとMAC、そしてオプションのパディングの合計20-40バイトのオーバーヘッドが発生する。IPとTCPのオーバーヘッドも発生する。レコードが1つのTCPパケットに収まる場合、IPのヘッダに20バイト、そしてオプション無しのTCPのヘッダに20バイト。その結果、1レコードにつき60-100バイトのオーバーヘッドが発生する可能性がある。一般的なMTUサイズである1500バイトでは、このパケット構造のために最小で6%のオーバヘッドが発生する。
  • レコードが小さいほどフレームのオーバーヘッドが大きくなるが、レコードのサイズをただ最大値(16KB)まで大きくすることが必ずしも良い考えというわけではない。レコードが複数のTCPパケットにまたがる場合、TLSは暗号を復元する前にすべてのTCPパケットの到達を待たなければならない。パケットロスや再構成、もしくは輻輳制御のために送信速度が調整されると、各パケットフラグメントはTLSレコードの復元までバッファリングされる必要があり、レイテンシが発生する。
  • 結局、ブラウザによって使用されるWebアプリケーションにおいてのベストプラクティスは、レコードサイズをTCP接続の状態に応じて動的に調整することである。
    • 新規接続かつTCP輻輳ウィンドウが小さい場合、もしくは接続がしばらくの間アイドル状態だった場合、各TCPパケットはTLSレコードを1つだけ送信すべきで、TLSレコードはTCPに割り当てられた最大MSSを占有すべき。これにより、バッファリングにかかる不要なレイテンシを削除し、より早くドキュメントの最初のバイトの読み込みを始められる。
    • 輻輳ウィンドウが十分に大きく、サイズの大きなストリームを転送する場合、TLSレコードのサイズは、フレーム化処理を削減しCPUの無駄遣いを避けるため、複数のTCPパケットにまたがっても構わない。
  • TLSの機能に、レコードプロトコル内で転送するデータの可逆圧縮のビルトインサポートがあるが、以下の理由から、サーバでのTLS圧縮は無効にすべき
    • CRIME攻撃により、セッションハイジャックが可能
    • トランスポートレベルのTLS圧縮はコンテンツタイプを判別しないため、画像や動画など既に圧縮されているデータをさらに圧縮しようとする(←CPUの無駄遣い)
  • ブラウザが信頼チェーンの検証を行う際、サイトの証明書からその親をたどって、信頼されたルート認証局にたどり着くまでのチェーンの横断が必要。したがって、最初に行うべきサーバの最適化は、ハンドシェイク時にすべての中間証明書を確実に含めること(=確実にクライアントに送りつけること)。忘れた場合でも多くのブラウザは動作するが、信頼チェーンの検証を一旦中断して中間証明書を取得し、その中間証明書を検証してから残りの検証を継続する、という動作になってしまう。このプロセスにはおそらく新たなDNSルックアップ、新規TCP接続、そしてHTTP GETリクエストが必要となり、ハンドシェイクのために数百ミリ秒のレイテンシが発生する
    • 証明書には通常、その親証明書のURLが含まれているので、それを使ってブラウザは中間証明書を取りにいくことは可能。
    • 必要のないチェーンは含めないようにすること。サーバ証明書はTLSハンドシェイク中に送信され、またこのハンドシェイクは、新規TCP接続、つまりスロースタートアルゴリズムの早い段階で送受信される可能性が高いものである。証明書チェーンがTCPの初期輻輳ウィンドウより大きい場合、ハンドシェイクに意図しないパケット往復を追加してしまうことになる。
    • 上記に対応するためには、初期輻輳ウィンドウを広げることの他、できる限り証明書のサイズを小さくする方法を検討する。具体的には、中間認証局の数を最小化する。理想的には、サーバからクライアントに送信する送信チェーンは、自分のサイトの証明書と、認証局の中間証明書の2つであるべき。ルート証明書はブラウザに存在するため、送信不要。

まだちょっとだけ続きがありましたが、とりあえずここまで。うー、長かった。。。