前回の記事でSNI(Server Name Indication)拡張を有効にしたnginxのバイナリを作成したので遊んでみました。SNI拡張は、個々の仮想ホスト(VirtualHost)で独自のサーバ証明書を利用するために作られた TLSv1 の拡張仕様です。
TLSやSSLを使ったウェブサーバーでは一般的には仮想ホストは使えない、という事になっています(何事にも例外はありますが。)仮想ホストは HTTP のリクエストヘッダの Host フィールドの値を元に振り分けられますが,HTTPのリクエストを出すよりも手前の TLS/SSL のハンドシェイク時にサーバ証明書の検証が行われるので,クライアントが接続しようとしているホスト名とサーバ証明書のホスト名が異なってしまい,不正な証明書と表示されてしまいます。
SNIは接続しようとしているホスト名を、クライアントからサーバに伝えるための仕様です。(ホスト名はハンドシェイク前に伝えないと行けないので、平文で送られる事になりますが。。。)これ自体は素晴らしいのですが,Windows XP の IE がサポートしていないので今ひとつ広まっていません。
まずは SNI を使わないで仮想ホストを設定する場合の例です。
ssl_certificate server_cert.pem; ssl_certificate_key server_cer.key; server { port 443; ssl on; server_name host1.example.com; } server { port 443; ssl on; server_name host2.example.co.jp; }
サーバ証明書は1枚しか指定できないので,server_cert.pem には host1.example.com と host2.exacmple.co.jp の双方が記述されている必要があります。ホストが同一のドメインに存在する場合はワイルドカード証明書を使う方法もありますが,例のようにドメインが異なる場合は双方のホスト名をCNもしくはsubjectAltNameに記述します。この方法では仮想ホストが増える場合は証明書を発行し直す必要があります。
次に SNI を使う場合の例です。
server { port 443; ssl on; ssl_certificate host1.example.com.pem; ssl_certificate_key host1.example.com.key; server_name host1.example.com; } server { port 443; ssl on; server_name host2.example.co.jp; ssl_certificate host2.example.co.jp.pem; ssl_certificate_key host2.example.co.jp.key; }
仮想ホスト毎に証明書が指定する事ができるようになります。
実際に SNI をサポートしている Firefox などのブラウザでアクセスすると、それぞれ異なる証明書を持つ事が確認できます。
ただ、現在の nginx に固有の問題かもしれませんが,仮想ホスト毎にクライアント証明書による認証の有無を切り替えるのは出来ないみたいです。ssl_verify_client off;な設定の仮想ホストが1つでもあると,クライアント認証を行う仮想ホストに対してもクライアント証明書が送られません。
クライアント認証が必要ない仮想ホストに対してもssl_verify_client optional;を指定すると大丈夫なようですが。(Nginx の Wikiの説明ではoptional ではなく、askになっているので注意が必要です。)