nginx の SSL設定をしくって Facebook アプリ登録でエラーとなった件

現在開発中のアプリを 「Facebook for Developers」でアプリ登録をした際に、SSL (本当はTLSですが、便宜上SSLと表記します)に関するエラーで少々ハマってしまったので、その内容を記録しておこうと思います。なお、SSL をご存知の方にとっては、至極当たり前の結末なので、あくまでも初心者向けということでご容赦ください。

エラー内容

現在開発中のアプリでは、Facebook の「ログイン」サービスを利用しますが、その際に「Facebook for Developers」から自分のアプリを登録する必要があります。

developers.facebook.com

この「Facebook for Developers」では、アプリの様々な設定を行いますが、ログインサービスの利用を一般にオープンにするためには「プライバシーポリシーのURL」というフィールドを設定しなければなりません。早速プライバシーポリシーのページを用意し、このフィールドにURLを登録しようとしたところ、次のエラーが出てしまいました。

Facebook プラットフォームに準拠するものにするには、有効なプライバシーポリソーURLを入力してください。リクエストエラー: SSL Error: Can't validate SSL Certificate. Either it is self-signed (which will cause browser warnings) or it is invalis.

エラーメッセージの特に英語の部分をみると、どうも「SSL証明書がオレオレじゃないの?」とか「じゃなかったら無効な証明書だよ」と言っています。何か SSL の設定が悪いようです。

原因調査

このアプリはウェブアプリで、Webサーバは nginx を使用しています。

SSL証明書は「Let's Encript」で発行したもので、決して「オレオレ証明書」ではありません。また、ChromeFirefox などのブラウザで動かしても、これまでSSLに関連した警告は特に表示されませんでした。*1

SSL サイト安全性チェック

まずは、SSLに着目して調査したところ、SSLのサイトにも安全性が高いもの、低いものがあることがわかりました。そして、それをチェックしてくれる 「Qualys SSL LABS SSL Server Test」というサービスがあるとのこと、ありがたや。

www.ssllabs.com

早速チェックしてみたら「B」評価でした。

f:id:ngzm:20180306140839p:plain

よく見ると「Key Exchange」の部分が黄色表示で問題があります。あと、もうひとつ "This server's certificate chain is incomplete. Grade capped to B." というメッセージがあります。

証明書チェインが不完全?

で、この "This server's certificate chain is incomplete. Grade capped to B." メッセージについて調べると、 This server's certificate chain is incomplete. ... | Qualys Community というコミュニティサイトがヒット、その中に次の投稿がありました。

When user connects to web server, web server sends certificate chain: leaf server certificate and intermediate certificate. What happens leaf certificate is signed by intermediate, intermediate is signed by already in browser stored root certificate, so certification validation is complete and because certificate chain is trusted your leaf server certificate is trusted. When modern browsers receive intermediate certificate they store it in browser cache. Now lets say your server only sends leaf certificate and does not send intermediate certificate (most probably in your case). What will happen? It depends if user has already visited some other web site and retrieved intermediate from that site and store it in browser cache. If it did then browser will use that intermediate certificate from browser cache to validate the chain of trust. But if end user did not visit any other web site using the same intermediate certificate then user will get fatal error in browser like ssllabs displays "incomplete chain".

要するに、SSL 証明書には「leaf server certificate(サーバ証明書)」と「intermediate certificate(中間証明書)」があって、もしブラウザなどクライアントで、中間証明書がキャッシュ含めて入手できないとルート証明書までのチェインが手繰れなくてエラーになります的なことが書いてあります。

ということで、”This server's certificate chain is incomplete. Grade capped to B." というメッセージは、「証明書チェインが不完全ですよ」ということで、もっと意訳すると「中間証明書が無くないすか?」ということではないかと想定されます。

nginx SSL証明書の設定誤りを発見!

これまでの調査を元に、自サイトの nginx の SSL 設定部分を再確認しましたところ、むちゃやばい間違いを発見しました。

ssl_certificate /etc/letsencrypt/live/[my-site-url]/cert.pem;
ssl_certificate_key /etc/letsencrypt/[my-site-url]/privkey.pem;

Let's Encript で SSL 証明書を発行すると、/etc/letsencrypt/live 以下に 4つのファイルができます。

  1. cert.pem
  2. chain.pem
  3. fullchain.pem
  4. privkey.pem

"cert.pem" はサーバ証明書が入ったもの、"chain.pem" は中間証明書、"fullchain.pem" はサーバ証明書と中間証明書が両方入ったもの、"privkey.pem" は証明書と対の秘密鍵です。

nginx の場合、"ssl_certificate" にサーバ証明書、"ssl_certificate_key" に秘密鍵を指定するのですが、中間証明書を設定できるフィールドが用意されていないので、このような場合は "ssl_certificate" にサーバ証明書と中間証明書が両方入った "fullchain.pem" を設定しなければなりません。*2

一方、私のサイトでは、サーバ証明書だけが入った "cert.pem" が設定されており、これでは中間証明書が入手できずにエラーとなってしまうことが判明しました。

対策する

ここまでくると、対策は簡単です。"ssl_certificate" を "fullchain.pem" に変更して、Webサーバを再起動します。

ssl_certificate /etc/letsencrypt/live/[my-site-url]/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/[my-site-url]/privkey.pem;

これで、もういちど「Facebook for Developers」の「プライバシーポリシーのURL」を設定してみます.... と、無事に設定することができました!!
Facebook で発生したエラーは、中間証明書が入手できなかったことが原因であるということで決まりです。

なお、この状態で「Qualys SSL LABS SSL Server Test」で SSLの安全性評価を再実行しました。

f:id:ngzm:20180306152936p:plain

相変わらず「B」評価ですが、”This server's certificate chain is incomplete. Grade capped to B." のメッセージは消えていることが確認できます。

ちなみに「Qualys SSL LABS SSL Server Test」の最高評価は「A+」らしいです。このままだと「Key Exchange」の部分とか問題が残ってますし、せっかくなので、最高評価の A+ を勝ち取るまで nginx と SSL をセットアップしてみたいと思います。

2018年3月7日追記。

最高評価の A+ を勝ち取るまで nginx と SSL をセットアップした内容をブログに書きましたのでリンクしておきます。

ngzm.hateblo.jp

まとめ

  • SSL にも安全性の問題があるので、単にサイトを SSL 化にしただけで安心してはいけない。
  • SSL のサイトを構築した場合は、「Qualys SSL LABS SSL Server Test」などで安全性をチェックしよう。
  • nginx にSSL証明書を設定する場合は、必ず中間証明書とサーバ証明書の両方入っているものを使用すること!間違えたらやばいところ。

以上です。

*1:これは自分が使用しているブラウザのキャッシュに何かのタイミングで必要な中間証明書が残っていたためにたまたま問題が無かっただけということですが...

*2:Apache の場合、2.4.8より前のバージョンでは、サーバ証明書と中間証明書、秘密鍵のそれぞれのフィールドが用意されていたようですが、2.4.8以降は、中間証明書のフィールドが無くなったようなので、nginx と同様 "fullchain.pem" を使用する必要があります。