この記事ではOS X Yosemite 10.10と、元々インストールされているOpenSSL 0.9.8を利用しています。
$ openssl version
OpenSSL 0.9.8zg 14 July 2015
-
help.hipchat.com
にリクエストを投げたら何故か*.uservoice.com
の証明書が返ってくる(2015-12-05現在)
$ openssl s_client -connect help.hipchat.com:443 -showcerts < /dev/null 2>&1 | grep subject
subject=/OU=GT20300774/OU=See www.rapidssl.com/resources/cps (c)15/OU=Domain Control Validated - RapidSSL(R)/CN=*.uservoice.com
SNIという、一つのIPアドレスが複数のSSL証明書を扱える技術があるのですが、サーバ側でSNIを使っている場合、SSLハンドシェイク時にドメイン名を指定しないと正しいSSL証明書が返ってこない事があります。
比較的新しいサービスだとSNIを使っている事が多いです。例えば、HipChatのヘルプページ(help.hipchat.com)ですが、これはSNIを使っています。なので、openssl
コマンドを使って証明書を取得しようとすると上記のようなことが起こります。
サイトのURLを表すコモンネーム(CN)の値が*.uservoice.com
となっています。なぜこんなことが起きるのでしょうか?それは、一つのIPアドレスが複数のSSL証明書を持っているからです。ドメイン名は単なるIPアドレスを単に見やすくしたものです。アクセス時は番号に変換されてしまいます。なので、サーバはリクエスト一つ一つが、どのドメイン名を意図して送られたものなのかわからないのです。当然、どの証明書を返してよいか分からないわけです。そこで、SSLハンドシェイク時に明示的に文字列としてドメイン名を指定しなければ、デフォルトとして設定されているSSL証明書が返ってきます。
openssl
コマンドで明示的にドメイン名を指定するには下記のオプションを使います。
オプション | 内容 |
---|---|
-servername | 欲しいSSL証明書のドメイン名を明示的に指定する |
- 明示的にサーバ名を指定すると正しい証明書が返ってくる。
$ openssl s_client -connect help.hipchat.com:443 -servername help.hipchat.com -showcerts < /dev/null 2>&1 | grep subject
subject=/C=US/L=San Francisco/ST=California/O=Atlassian, Inc./OU=HipChat/CN=help.hipchat.com
私はとあるAPIを叩いていたのですが、どうしても通信がうまく行きませんでした。それは、APIのエンドポイントでSNIが使われていることと、私が使っているAPIクライアントがJava1.6製だったのが原因でした。Java1.6のHTTPSクライアントは、SSLハンドシェイク時にドメイン名を指定する機能がありません。なので、上記のhelp.hipchat.com
のような例では証明書が不正だと判断され通信が失敗します(この場合、何も指定しなくてもhelp.hipchat.comのCNを持った証明書が返ってくるよう、サーバ側で設定されていれば通信はできるのですが……)。