NginxでHTTPS:ゼロから始めてSSLの評価をA+にするまで Part 2 – 設定、Ciphersuite、パフォーマンス

今日のインターネットの世界では、一般的な静的Webサイトも含め、全てのWebサイトに、強固で安全なHTTPSのセットアップが必要となります。この記事は、Nginxセキュリティをどのようにセットアップするのかに関するシリーズのパート2です。

パート1は、Webサーバに有効な署名証明書をセットアップする話で終了しました。しかしこれには、最適な設定とは言い難い、デフォルトのNginxの設定を使用していました。

この記事を読み終えれば、SSL Labsのレポートで、A+の評価を獲得できる安全なHTTPSの設定ができます。それだけでなく、追加でいくつかの微調整も行い、パフォーマンスそしてUXも向上させていきます。

A+ score on juliansimioni.com

ここに掲載した記述やコードの抜粋の他にも、すぐに使えるNginx向けのSSL設定ファイル、ほぼこのままで使用できる事例サイトの設定ファイル、そして、記事を書くに当たってリサーチした結果、実際に私が使用した全てのリストをGithubに掲載しましたので、自由に使ってください。

SSLv3の無効化

Nginxのデフォルトの設定では、SSLv3が有効になっています1。このSSLv3は、POODLEの攻撃に対する脆弱性を持っていることが、2014年10月に確認されています。IE6は、初期設定のままで新しいプロトコルをサポートしていない唯一のブラウザで、TLSv1を使用するように設定されています。そのため、SSLv3をサポートする理由がありません。

SSLv3が有効である場合、SSL LabsはサーバのSSL評価がCとなるように制限をかけるようになっているので、まずはこの設定変更を行いましょう。

# support only known-secure cryptographic protocols
# SSLv3 is broken by POODLE as of October 2014
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

全ての証明書チェーンを送信

信頼されるべきサーバ証明書(例えば、あなたのWebサイトのもの)がどれなのかを判断するには、ブラウザは認証局から発行してもらうルート証明書を使用します。しかし、そこには大抵、中間証明書が存在するので、サーバ証明書が有効であるかどうかを確認するには、ブラウザはこの中間証明書のことを知らなければなりません。

もちろん、ブラウザが中間証明書を探し出し、ダウンロードすることは可能ですが、Webサイトに接続するプロセスが遅くなります。全てのプロセスが更に複雑化することで攻撃者にとっての抜け道を更に増やすことになってしまいます。

An Incomplete Certificate chain
不完全な証明書チェーン

ですから、ユーザが最初に接続した際に中間証明書が送信されるようにNginxを設定しておけば良いのです。実際、こうしておかなければ、SSLはBの評価しか得られません。

認証局は恐らく、中間証明書がダウンロードできるリンクを送ってくるでしょうから、それを受け取ったら、サーバの安全な場所に保存し、以下のような要領でNginxにそのことを伝えます2

# send intermediate certificate during new sessions
ssl_trusted_certificate /etc/nginx/ssl/startssl/sub.class1.server.ca.pem;

Ciphersuiteの設定

SSL/TLSプロトコルは、これら自身が暗号を提供するわけではありません。その代わり、サーバとクライアント間での合意を許可し、数ある暗号スキームの1つを使用するチャネルを通じて、通信が開始されるようにしています。

サーバとクライアント間でSSL/TLSを使うには、4つのことに合意しなければなりません。鍵交換アルゴリズム(サーバとクライアント間で安全に鍵暗号を共有する方法)、認証(意図する送信者/受信者だけが確実にコミュニケーションする)、暗号化アルゴリズム(メッセージが読み取れないように暗号化する)、そしてメッセージダイジェストアルゴリズム(メッセージが盗まれていたり、壊されていたりしていないか確認する)の4つです。

それぞれのアルゴリズムには、多くの異なるアルゴリズムが存在し、機能やパフォーマンス、暗号強度、ブラウザサポートは多様です。多くのアルゴリズムには、実際に使用するには不適切な弱点があります。最新バージョンのブラウザを使用すれば、個人のユーザを保護するには十分です。しかし、残念なことに、多くの旧ブラウザのデフォルト設定では、安全ではありません。

Ciphersuiteを設定する目的は、セキュリティシステムに不正にアクセスされたり、大したことはないにしてもパフォーマンスに影響を及ぼしたりすることなく、可能な限り多くのブラウザ間での互換性を維持することです。

これは、OpenSSLがNginxの設定で認識されるように、設定の文字列を入れることで可能になります。これを簡単に行うために、関連する設定を以下に挙げておきます。

# make the server choose the best cipher instead of the browser
# Perfect Forward Secrecy(PFS) is frequently compromised without this
ssl_prefer_server_ciphers on;

# support only believed secure ciphersuites using the following priority:
# 1.) prefer PFS enabled ciphers
# 2.) prefer AES128 over AES256 for speed (AES128 has completely adequate security for now)
# 3.) Support DES3 for IE8 support
#
# disable the following ciphersuites completely
# 1.) null ciphers
# 2.) ciphers with low security
# 3.) fixed ECDH cipher (does not allow for PFS)
# 4.) known vulnerable cypers (MD5, RC4, etc)
# 5.) little-used ciphers (Camellia, Seed)
ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';

ここで、この根本的理由を説明します。

サーバにciphersuiteを選択させる

多くのブラウザ、中でも古いものは、ciphersuiteの選択がお粗末です。サーバは、ブラウザとサーバの両方がサポートされているciphersuiteをリストから選択するように最初に指示を出さなくてはなりません。

ゼロもしくは低セキュリティのcipersuiteの無効化

不思議なことに、設定が正しくないと、SSL/TLSが暗号化されずに送信されてしまうことがあります。でも安心してください。これは簡単に無効化することができます。OpenSSLには、低セキュリティとして知られる独自の内部cipersuiteリストがあります。手始めにこれらを無効化してみましょう。

安全ではないアルゴリズムの無効化

いくつかのアルゴリズムでは脆弱性、またはその疑いのあるものが確認されています。これらは、必要に応じて無効化もしくは制限をかけることができます。特に以下に挙げるアルゴリズムは無効化すべきでしょう。

MD5: 安全とは言えないが、一般的に使用されている

MD5のハッシュアルゴリズムは一般的に使用されていますが、1996年から脆弱性が確認されています。これは、MD5が導入されるようになってから、わずか4年後のことでした。今日では、MD5は衝突に対しては弱く、特にGPUで解読できるということで有名です。今後使用し続けることは、安全とは言えません。

RC4: アルゴリズムの旧シンボル、最近評判を落としている

RC4での暗号化もまた、一般的に使用されています。しかも最近まで、使用が広く推奨されていたアルゴリズムです。しかし、他でもないあのEdward Snowdenが、「NSAはRC4の暗号を解読できる技術を持っている可能性がある」ということを示唆したため、その危険性が公になりました。

RC4に関する理論的な脆弱性を示すリサーチ結果と結び付けてみても、RC4の実環境で攻撃を受ける可能性があるということは、到底無視できません。Microsoftは、RC4を無効化することを推奨するセキュリティアドバイザリを公表し、IETFはクライアントやサーバは決してRC4を使用しないようにとの覚書の草案をまとめました。

SHA1: 急激に攻撃の可能性が高まっている

パート1では、SHA1ではなくSHA256を使って、証明書リクエストを生成しました。パート1の時と同じ理由で、ハッシュアルゴリズムにSHA1を使っているciphersuiteも無効化しなければなりません。現時点においては、SHA1に対する攻撃が行われたという事例は報告されていませんが、恐らくそれもそう先のことではないでしょう。

ほとんど使われていない暗号鍵を無効化する

ほとんど使われていない暗号鍵は一般的ではありません。無効化することで状況がシンプルになり、攻撃3で狙われる部分が減ります。

可能なかぎりperfect forward secrecyをサポートする

Perfect forward secrecy(PFS)を使うことで、特定のセッションにカスタマイズされた暗号鍵を使えるので、安全に接続することができます。このことは安全性に対する素晴らしい利点となります。たとえサーバの秘密鍵が漏れてしまったとしても、過去にサーバに送られたメッセージがデコードされることはないのです。

また、サーバが利用しているセッション鍵が攻撃者に破られてしまったとしても、攻撃者はその鍵を1回のセッションにしか利用することができません。攻撃者にとっては、サーバの通信を攻撃するコストが増える割に、その結果として得られるものが減ることになります。

最近あった好例としてはハートブリードがあります。PFSを使っていれば、攻撃者が攻撃できるのは個々のセッションだけです。サーバの秘密鍵を更新する必要はありますが、秘密鍵が漏洩したとしても、ユーザデータの大部分に対するセキュリティは守られます。

IE8は明らかに例外ですが、最近使われているブラウザはPFSを使った鍵交換アルゴリズムをサポートしています。

PFSをサポートするciphersuiteの安全性を維持するためには、設定の変更をもう1カ所行う必要があります。Nginxはデフォルトで、PFS暗号化に1024ビットのRSA鍵を生成しますが、これはオーバーライドできます。以下のように設定を変更し、2048ビットの鍵を生成するためにopensslを必ず使ってください(この処理には数分かかる可能性があります)。

# Use 2048 bit Diffie-Hellman RSA key parameters
# (otherwise Nginx defaults to 1024 bit, lowering the strength of encryption # when using PFS)
# Generated by OpenSSL with the following command:
# openssl dhparam -outform pem -out /etc/nginx/ssl/dhparam2048.pem 2048
ssl_dhparam /etc/nginx/ssl/dhparam2048.pem;

適切なところでパフォーマンスを最適化する

アルゴリズムの脆弱性が度々発見されることはありますが、実は最近のブラウザは極めてパワフルなセキュリティツールの総合的なパッケージソフトをサポートしています。現在一般的に使われているブラウザの多くで、AESやSHA2を含めて、NISTのSuite B 暗号化によって定義された4つ全てのアルゴリズムがサポートされているのです。

セキュリティ専門家の多くは、現在サポートされている最長の鍵を使っても、セキュリティに対して目に見える効果はなく、ただパフォーマンスが低下するだけである4と考えています。

このことを考慮した共通の設定では、最も安全性の高いバリアントがサポートできますが、鍵の長さとしては妥当なものが求められます。例えば、上記の設定はECDHE-ECDSA-AES256-SHA384とECDHE-ECDSA-AES128-SHA256のciphersuiteを両方サポートしていますが、より短い鍵のバリアントの方が好まれるでしょう。それに両方とも攻撃されたことはなく、最高の安全性を提供しています。つまり、ユーザの大多数にとっては安全でかつ妥当なパフォーマンスを得られる方法がデフォルトとなっていますが、ユーザが望めば、最も安全性の高いciphersuiteを選択することもできるのです。

HSTSを有効化する

セキュリティに関する多くのコンセプトは、数学的に証明されたレベルのセキュリティが得られる暗号アルゴリズムのように、明確かつ詳細な手順を、正確に実装することにあります。HSTSは、そのようなものではありません。

HSTSを有効化することは、今後一切サーバへのプレーンテキストのリクエストをしないように、ブラウザに伝えるだけのことです。

理論上、上記については、全てのリソースに対して有効なHTTPS接続を必須とするように適切に設定されたサーバに対しては、何の利点もありません。しかし、実際には、HSTSは簡単に起こり得る膨大な数の設定エラーからサーバを守ります。

実装するのも簡単です。サーバに必要なのは、それぞれのHTTPリクエストと一緒に有効なHSTSヘッダを送ることだけで、残りの処理はブラウザがやってくれます。

# enable HSTS including subdomains
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains;'

これを実行することでブラウザは、サーバや全てのサブドメインに対して1年間、プレーンのHTTPリクエストを送らなくなります。もしサブドメインの中にHTTPSを受けられないものがある場合、includeSubdomainsは含めなくても全く問題ありません。

HSTSを有効化することは、結局、ヘッダの有効期限が切れるまではサーバがHTTPSに対して正確に応答することをブラウザに対して保証していることであると留意しておきましょう。つまり、これはHTTPSの公開初日に有効化すべきものではないということです。

問題なくHTTPSのセットアップができた時点で、HSTSプリロードにサイトを登録して、よく使われている最新バージョンのブラウザに対して、自分のサーバがHTTPSリクエストのみを受け付けることを知らせましょう。これは効果的です。あなたのサーバに対して最近のブラウザがHTTPリクエストを送信することは、決してなくなります。

HSTSのセキュリティ面での利点は絶大で、SSL LabsはこれをSSLでA+の評価を得るための最終条件にしています。

パフォーマンスの向上

最後に、パフォーマンスを向上させるために必要となる設定変更をいくつかご紹介します。私が把握している限り、これらの変更はセキュリティ上何の害も及ぼしません。実際のところ、セキュリティの向上につながるものも含まれています。

OCSP Staplingの設定

HTTPSを使ってサーバと通信を始める前に、ブラウザ側はまず、サーバが使っている証明書がまだ有効かどうかを確認しなければなりません。何らかのアップグレードや攻撃に対する対応等の結果、証明書が無効になっていることもあるからです。証明書の状況を確認しておくことは大切なことです。

サーバに何も設定していない場合、そのサーバに接続しようとする全てのブラウザは、取得した証明書が無効になっていないかをまずOCSPサーバに確認しなければならないため、その分時間がとられることになります。OCSP Staplingが設定されていれば、前もってこの確認作業をサーバ側で行っておくことができます。OCSPレスポンスはサーバ側の認証局によって署名されているので、サーバが直接ブラウザに返してきたものであっても信頼することができます。

この設定を加えることで、OCSPサーバへのトラフィックを削減することもできます(これは素晴らしいことです)。またOCSPサーバは攻撃を受けて、サービスが動かなくなったり、リクエストが拒否されたりするかもしれません。この設定変更は、そういった事象によって起こる予期しない中断からサーバを守ることにもつながります。

# allow Nginx to send OCSP results during the connection process
ssl_stapling on; 

SSLセッションキャッシュのサポート

HTTPSに移行する際に直面する最も大きな課題は、パフォーマンスに関するものです。サーバにとって許容範囲を超える稼働とならないか、ユーザにとってページを読み込む速度がもっと遅くなるのではないか、といった懸念です。データサイズに関して言えば、安全なセッションを確立するために必要なオーバーヘッドの大きさは今や問題ではありません

とはいえHTTPSを使って最初に接続を確立するプロセスでは、HTTPを使った通信に比べると、クライアントとサーバの間でより多くのラウンドトリップが発生することになります。つまり、今なおページの読み込み時間に対して顕著な影響を与えていることが明らかです。

このことを考慮すると、ほんの数秒の間だけでもSSLセッションのキャッシュを保存することは道理にかなっています。そうすればユーザは、たった一度だけセッションを確立すればいいことになります。Nginxではこの機能がほぼ正しく設定されているので、難しい設定を加える必要はありません。セッションキャッシュのタイムリミットに関する設定だけ、次のように変更しましょう。

# Cache SSL Sessions for up to 10 minutes
# This improves performance by avoiding the costly session negotiation process where possible
ssl_session_cache builtin:1000 shared:SSL:10m; 

参考記事及び謝辞

このガイドが皆さんにとって分かりやすく、十分に役立つものであることを願っています。でも、全てをカバーできているわけではありません。セキュリティとは複雑で、最新の情報がすぐに変わってしまう、挑戦しがいのあるものです。この事実を念頭においた上で、私がこれまでに見つけた中から特に分かりやすい参考記事をいくつかここに挙げておきましょう。より多くの情報を皆さんが得られることを期待しています。そして、これらの素晴らしい記事を書かれた作者の皆さんに感謝します。

「【翻訳】NginxでHTTPS : ゼロから始めてSSLの評価をA+にするまで(Part 1)」へのリンクはこちら


  1. Nginx側もブログでPOODLEに関する記事を書いており、Nginxを使う場合は常にSSLv3を無効にするよう推奨しているので、恐らく早々にデフォルトを変更すると思われます。 

  2. Eric Millの今すぐ無料でHTTPSに切り替えようをはじめとする多くのチュートリアルでは、ルート証明書と中間証明書、そしてサーバの証明書を1つのファイルにまとめて結びつけることによって、同じような動作を実行する方法を紹介しています。この方法でも良いのですが、私自身はファイルを別々に分けておくほうが分かりやすくて好きです。どちらの方法でも、お好きな方法を選んでください。 

  3. ここに挙げたciphersuiteを、より一般的に使っている国もあります。主にこうした特定の国々に向けて主にトラフィックを送る場合には、有効にしておくことも必要かもしれません。 

  4. 同様の理由により、私は4096ビット鍵や、4096ビットで生成されるディフィー・ヘルマン鍵共有のパラメータ、そして同じようなビット数変更に対応する証明書を使うことが正しいとは思いません。より大きなビットで対応すればQualys SSL Reportのスコアはもちろん上がりますが、パフォーマンスにコストがかかることにもなるでしょう。