SNI(Server Name Indication)の仕組みの話

1台のWEBサーバーにバーチャルホストを設定して複数のWEBサイトを運営することはよくあることです。バーチャルホストとは1つのIPアドレスを複数のドメインで共有する技術のことです。ここでバーチャルホストの仕組みをおさらいしておきます。

WEBサーバーではxxx.comとyyy.comのドメインを運営しているものとします。

①ブラウザがxxx.comにアクセスをします。HTTP1.1以降の仕様ではHTTPヘッダにHostフィールドが必須となっているためブラウザはHostフィールドにドメイン(ここではxxx.com)を設定してWEBサーバーにリクエストを送ります。

②WEBサーバーはリクエストを受け取りHostフィールドを取得します。Hostフィールドに設定されたドメインを参照することにより、どのドメインへのアクセスかがわかります。

③バーチャルホストの設定ファイルをもとにドメインのマッチングを行い、HTMLファイルの取得(もしくは生成)をします。

④WEBサーバーは該当するWEBサイトのHTMLファイルをブラウザに返却します。

今まではこれで問題なかったのですが近年は常時TLS化が進んだ影響でこの仕組みではドメインが判断できなくなりました。どういうことかというと、TLSによりHTTPリクエストが暗号化されているためWEBサーバーのほうでHostフィールドが判断不能になりました。

そこでこの問題を解決するために導入された仕組みがSNI(Server Name Indication)です。どうやるかというと、暗号化されたHTTPリクエストとは別に「Hostフィールドに該当する情報」をブラウザからWEBサーバーに送るようにしています。このやりとりはTLSのハンドシェイク時に行われます。

SNIはブラウザとWEBサーバーが対応していれば良いためインターネットを利用する人が特に意識をする必要はありません。常時TLS化が一般的になってきた現在ではほとんどのブラウザがSNIに対応済みです。WEBサーバー側はTLSのハンドシェイクにOpenSSLを利用している場合、0.9.8以降で対応しているようです。

ちなみにですが、Hostフィールドに該当する情報をTLSのハンドシェイクの中のどこでやりとりしているのか、どんな感じで設定しているのかを確認しようと思い、Wiresharkを使ってキャプチャしてみました。以下がキャプチャした結果です。通信相手のサーバー情報は伏せています。

どうやらブラウザからは「Client Hello」の中にある「Server Name Indication extension」という部分にドメイン情報を入れてWEBサーバーに送っているようです(赤枠で囲ったところ)。暗号化はされていないのでWEBサーバー側でバーチャルホストの判断はできそうですね。。
※TLSのハンドシェイクについてはこちらの記事の前半にも書いているので見てみてください。

Wiresharkに秘密鍵を登録しても解読できないのにSSLKEYLOGFILEを使えば解読できる理由

秘密鍵をWiresharkに登録してもTLSの通信を復号できなかったのに、SSLKEYLOGFILEを使えば復号できる理由を考えてみました。Wiresharkを使ってTLS通信を復号しようとした記事は以下です。

秘密鍵をWiresharkに登録してもTLS通信を解読できない?
ubuntuでFirefoxのTLS通信をキャプチャする。

TLSのネゴシエーションの中で「鍵交換」に関するやり取りに注目して必要な部分を抜き出したものが以下のシーケンス図です。
※TLSのネゴシエーションのすべてではありません、省略しているものもあります。

図の中にある各やり取りの説明をします。

Client Hello
クライアントからサーバーに対してWEBブラウザが対応しているいくつかの暗号スイートを提示します。

Server Hello
クライアントから提示された暗号スイートの中からクライアント/サーバーの双方で使える一番強い暗号スイートをサーバーが選び、クライアントに通知します。ここで決められた暗号スイートに従い、以降の通信を行います。

Server Certificate
サーバー証明書をクライアントに通知します。サーバー証明書には公開鍵が含まれています。

Server Key Exchange
必要に応じて一時的な鍵をクライアントに通知します。Server Key Exchange は省略される場合があります。

Client Key Exchange
共通鍵を生成する鍵の元を先ほど受け取った公開鍵で暗号化してサーバーに通知します。

Change Cipher Spec(クライアント→サーバー)
鍵の元から共通鍵の生成ができたことをサーバーに通知します。

Change Cipher Spec(サーバー→クライアント)
鍵の元から共通鍵の生成ができたことをクライアントに通知します。

Application Data
やり取りしたいデータを共通鍵で暗号化して双方で通信を行います。データの復号にも共通鍵を使います。

[広告]

それでは、本題に入ります。

鍵交換方式がRSAでは解読できるのにECDHEだと解読できないのはなぜか?
SSLKEYLOGFILEを使った場合、鍵交換方式がECDHEでも解読できるのはなぜか?

まずRSAの鍵交換の方式から見ていきます。鍵交換にRSAを使った場合はクライアント/サーバーの双方で同じ鍵の元を共有します。鍵の元はClient Key Exchangeでクライアントからサーバーに送られています。そのためWiresharkで鍵の元のデータを盗み見することができれば共通鍵を得ることができます。鍵の元からクライアントやサーバーがやっているのと同じアルゴリズムで共通鍵を作るのです。

次にECDHEの方式です。ECDHEは Ephemeral Eliptic Curve Diffie-Helman の略です。ECDHEの最後の「E」がEphemeral(一時的)を意味します。ECDHEの場合は鍵の元をクライアントとサーバーの双方で生成し、鍵の元を使って計算した値をクライアントとサーバーで送り合います。つまり、クライアントとサーバーは別々に違う鍵の元を持ち、それを使った計算結果を相互に交換するので鍵の元自体はネットワークに流れません。計算結果は Server Key Exchange と Client Key Exchange で交換してします。(厳密にはもっと複雑なのかもしれませんが交換しあっているということです)。鍵の元を交換せず計算結果だけを交換しあったところで同じ共通鍵を作れるのか?という疑問もありますができるようです(前提としてクライアントとサーバーで共有しておく数値があらかじめある)。そのへんのアルゴリズムは難しいので書くことはできないのですが、大事なところは、通信を盗み見したところで鍵の元はわからない、ということです。盗聴をして「前提としてクライアントとサーバーで共有していた数値」および「計算結果」を取得し、アルゴリズムがわかったとしても、鍵の元を逆算で導き出すのは至難の業(ほとんど不可能)というわけです。

と言うことで、1点目の「鍵交換方式がRSAでは解読できるのにECDHEだと解読できないのはなぜか?」については、ECDHEだとWiresharkでパケットを盗み見しても共通鍵を導き出せないから、というのが理由になります。今は鍵交換にRSAを使おうとするサーバーはほとんどないです。古いクライアント端末ではRSAしか対応していない場合があり、そのような端末でも利用できるようにするためサーバー側もRSAを使用している、というケースはあるでしょう。一応、RSAは鍵の元をずっと使い続けるわけではなく、随時再作成はしているようです(一時的な鍵として使用している)。なお、ECDHEのような鍵交換方式はPFS(Perfect Forward Secrecy)と呼ばれていて、PFSは秘密鍵がわかってもそれだけでは暗号を解読できない性質のものを指しています。

では、2点目のSSLKEYLOGFILEを使った場合を考えてみます。秘密鍵をWiresharkに登録してECDHEの鍵交換のやりとりを盗み見しようとしたのと違って、2点目の場合はWEBブラウザが「クライアントの鍵の元」と「サーバーからの計算結果」を知っています。ですのでブラウザは共通鍵を生成できます。つまり、SSLKEYLOGFILEを環境変数として設定することでブラウザが共通鍵そのものをファイルに書き出してくれます。ファイルの出力先はSSLKEYLOGFILEで指定した場所になります。そのためWiresharkはそのファイルを取り込むことで共通鍵を手に入れることができ、それをもとにTLS通信を復号することができるのです。ただブラウザに共通鍵を出力する機能があれば良いのですが、そういう機能がない場合はこの手は使えません。ChromeとFirefoxでは対応しているみたいですね。