RFC 3546 で、
TLS (Transport Layer Security つまり SSL) の拡張が規定された。
その中の一つが、﹁Server Name Indication﹂と呼ばれる拡張であり、
クライアントがサーバに対して、
サーバのホスト名を伝えることができるようになった
(規定されたのは 2003年6月なのであるが、まだあまり普及していない)。
なぜクライアントがサーバへ、
当のサーバの名前を伝えてやる必要があるかというと、
サーバが複数のホスト名を持つ場合があるからだ。
例えば WWW サーバは、
http リクエスト中の﹁Host:﹂フィールドを見てレスポンスを切り替える。
この機能はバーチャルドメインと呼ばれ、
一つのIPアドレスで複数のホスト名のサービスを提供する方法として、
広く使われている。
ところが (従来の) https の場合、この方法が使えない。
WWW サーバは SSL 通信を開始するにあたって、
*最初に*サーバ証明書を
クライアントへ送る必要があるからだ。
http リクエスト中の﹁Host:﹂フィールドに、
別のホスト名が書いてあったとしても後の祭。
﹁リクエストしたホスト名と、
サーバから送られてきた証明書に記載されたホスト名が一致しない﹂という旨の
警告が WWWブラウザに表示されてしまう。
もう少し詳しく説明すると、
クライアント (WWWブラウザ) とサーバは、
SSL 通信を始めるにあたって、
次のようなハンドシェークを行なう。
クライアント | サーバ | |||
---|---|---|---|---|
ClientHello | → | 乱数, セッションID, 暗号/圧縮方式 | ||
← | ServerHello | 乱数, セッションID, 暗号/圧縮方式決定 | ||
← | Certificate | サーバ証明書 | ||
← | ServerKeyExchange | 共通鍵の交換 | ||
( | ← | CertificateRequest | クライアント認証を要求する時のみ) | |
← | ServerHelloDone | ServerHello の終了を通知 | ||
(ClientCertificate | → | クライアント認証を要求された時のみ) | ||
ClientKeyExchange | → | 共通鍵の交換 | ||
ChangeCipherSpec | → | 次のデータから暗号化することを通知 | ||
Finished | → | 以上のハンドシェークのハッシュ値(暗文) | ||
← | ChangeCipherSpec | 次のデータから暗号化することを通知 | ||
← | Finished | 以上のハンドシェークのハッシュ値(暗文) |
このハンドシェークの後、
クライアントが暗号化された http リクエストを送信し、
それを受けてサーバが暗号化されたレスポンスを返す。
https サーバがバーチャルドメイン機能を持つには、
https サーバがサーバ証明書を送信する (上のハンドシェーク図の3行目) より前に、
クライアントがリクエストしたいホスト名を通知する必要がある。
上図から明らかなように、
ホスト名の通知は一番最初の﹁ClientHello﹂で行なわれなければならず、
そのための拡張が、
﹁Server Name Indication﹂というわけである。
もちろんこの時点では、まだ鍵の交換は行なわれていないので、
ホスト名は平文で送られる。
前置きが長くなってしまったが、
この Server Name Indication (SNI) を stone でサポートしてみた
(stone.c Revision 2.3.1.11 以降)。
ただし stone が利用している
OpenSSL で SNI がサポートされるのは
0.9.9 以降である (追記: 0.9.8f 以降でもサポートされた) ので、
OpenSSL 0.9.9 以降のライブラリを使って stone を make する必要がある 。
二台の http サーバ senri.gcd.org と asao.gcd.org があるとき、
次のように stone を実行する:
stone -z sni \ -z servername=senri.gcd.org \ -z cert=senri.gcd.org-cert.pem \ -z key=senri.gcd.org-key.pem \ senri.gcd.org:http 443/ssl -- \ -z servername=asao.gcd.org \ -z cert=asao.gcd.org-cert.pem \ -z key=asao.gcd.org-key.pem \ asao.gcd.org:http 443/ssl転送元ポート指定﹁443/ssl﹂が二度現われていることに注意。 最初の﹁senri.gcd.org:http 443/ssl﹂は、 ﹁-z servername=senri.gcd.org﹂と指定しているように、 クライアントが通知するサーバのホスト名が senri.gcd.org の場合の指定である。 サーバ (つまり stone) は、 ﹁-z cert=ファイル名﹂と﹁-z key=ファイル名﹂で指定されるサーバ証明書を返し、 クライアントからの通信を、 SSL 復号を行なった上で senri.gcd.org:http へ中継する。 二番目の﹁asao.gcd.org:http 443/ssl﹂についても同様に、 クライアントが asao.gcd.org を通知すれば、 stone は asao.gcd.org のサーバ証明書を返すとともに、 クライアントからの通信を、 SSL 復号を行なった上で asao.gcd.org:http へ中継する。 この例では http サーバは二台のみであるが、 同様に何台でも指定できる。 また、もちろん物理的に異なるサーバを用意する必要があるわけではなく、 一台の http サーバで複数のポートを開き、 各ポートで別々のホスト名のサービスを提供してもよい。 OpenSSL 0.9.9 の s_client コマンド (SSL クライアント) でアクセスしてみると、 senri.gcd.org を通知すれば (-servername senri.gcd.org オプション)、
% openssl s_client -connect localhost:443 -servername senri.gcd.org -CApath /usr/local/ssl/certs CONNECTED(00000003) depth=2 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org verify return:1 depth=1 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org verify return:1 depth=0 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=senri.gcd.org/emailAddress=sengoku@gcd.org verify return:1 Server did acknowledge servername extension.サーバ (stone) は﹁CN=senri.gcd.org﹂の証明書を返し、 同じ 443番ポートへのアクセスでも 通知するサーバ名を asao.gcd.org へ変えるだけで、
% openssl s_client -connect localhost:443 -servername asao.gcd.org -CApath /usr/local/ssl/certs CONNECTED(00000003) depth=2 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org verify return:1 depth=1 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org verify return:1 depth=0 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=asao.gcd.org/emailAddress=sengoku@gcd.org verify return:1 Server did acknowledge servername extension.サーバが返す証明書が﹁CN=asao.gcd.org﹂に変わるし、 stone が中継する先も asao.gcd.org:http へ変わる。 したがって一つのIPアドレスに 複数のホスト名を持たせるバーチャルドメイン機能を、 任意の SSL 通信で実現できる。 なお、上記 stone 実行方法 (オプション指定) は、やや煩雑なので、
stone -z sni \ -z certpat=%n-cert.pem \ -z keypat=%n-key.pem \ -z servername=senri.gcd.org \ senri.gcd.org:http 443/ssl -- \ -z servername=asao.gcd.org \ asao.gcd.org:http 443/sslなどと、証明書ファイルの指定をまとめることもできる。 ﹁-z certpat=%n-cert.pem﹂オプションによって証明書のファイルのパターンを 指定する。 ﹁%n﹂はサーバのホスト名で置き換えられる。 すなわち、 ﹁-z servername=senri.gcd.org﹂を指定した場合は、 ﹁-z cert=senri.gcd.org-cert.pem﹂を指定したのと同じ結果になる。 ﹁-z keypat=%n-key.pem﹂についても同様。 現時点で SNI をサポートしている WWWブラウザは、 私の知っている範囲だと Firefox 2.0 等と IE7 だけであるが、 今後は開発される WWWブラウザの大半が SNI をサポートすることになるだろう。 そうなれば https サーバも、 バーチャルドメインで運用することが一般的となるはずである。
Name Based SSLについてもうちょっと腰を入れて調べる。
IE7ではRFC3546 のServer Name Indicationの対応がされている。
Apacheの方では、RFC3546は、mod_gnutlsを入れれば対応は可能なようです。
mod_gnutlsのサイトはこちら。
が、2005年から時が止まったまま・・・。むう。
でびぞー徒然日記から引用
OpenSSL 0.9.9 の安定版がリリースされれば (追記: SNI をサポートした 0.9.8f が 10/11 にリリースされた)、 apache などの WWW サーバにおいても SNI サポートが普通になると思われるが、 それまでは stone で SSL 暗号化を行なうようにすれば、 手軽に SNI を利用できる。 サーバに OpenSSL 0.9.9 をインストールしてしまうと、 OpenSSL を利用する全てのソフトウェアが影響を受けてしまうが、 例えば以下のように stone.c をコンパイル (Linux でのコンパイル例) して、 stone だけ 0.9.9 をリンクするようにすれば、 影響を stone だけに限定できる。
% cc -Wall -DCPP='"/usr/bin/cpp -traditional"' \ -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_POP -DUSE_SSL \ -I /usr/local/ssl-0.9.9/include -L /usr/local/ssl-0.9.9/lib \ -o stone stone.c -lpthread -ldl -lssl -lcrypto
以上は stone が SSL サーバとして、 サーバホスト名通知を受付ける場合であるが、 もちろん stone を SSL クライアントとして実行し、 サーバホスト名を通知することもできる。
% stone -q sni -q verbose -q verify -q CApath=/usr/local/ssl/certs -q servername=asao.gcd.org localhost:443/ssl 10080 Jun 23 08:43:46.116894 3084876480 start (2.3c) [18500] Jun 23 08:43:46.185448 3084876480 stone 3: 127.0.0.1:443/ssl <- 0.0.0.0:10080 Jun 23 08:43:48.610513 3084876480 3 TCP 6: [depth2=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org] Jun 23 08:43:48.610707 3084876480 3 TCP 6: [depth1=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org] Jun 23 08:43:48.610928 3084876480 3 TCP 6: [depth0=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=asao.gcd.org/emailAddress=sengoku@gcd.org] Jun 23 08:43:48.624724 3084876480 [SSL cipher=AES256-SHA]
「-q servername=asao.gcd.org」オプションで、 通知するサーバホスト名を指定している。 この実行例の場合「asao.gcd.org」を通知しているので、 サーバからは「CN=asao.gcd.org」の証明書が返されている。 この実行例では、中継先が「localhost:443」であるので 「-q servername=」オプションを指定する必要があるが、 もし中継先 (サーバ) ホスト名が通知すべきサーバホスト名に一致するのであれば、 「-q servername=」オプションは省略できる。