This site is an archive of ama.ne.jp.

nginxのssl_protocolsとssl_ciphers

nginxを導入、設定し、運用するにあたって少し問題が起こったので、それについて書きます。

やったことだけを知りたい人向けに書くと、「ssl_protocolsssl_ciphershttpブロックに書こう」という記事です。

前提

  1. 先日、nginx(1.10.1)を導入して、ama.ne.jpなどにリバースプロキシを噛ませることにしました。設定にあたっては、各サイトのserverブロックにssl_protocolsssl_ciphersを加え、つよいSSLサーバを作ると同じプロトコルや暗号スイートを指定しました。
  2. 設定の結果、SSL Server Test: ama.ne.jpの評価も実際の動作も変わらないようで、僕は大変満足げな表情です。

問題が発覚するまで

ひょんなことから、Wiiのインターネットチャンネルでama.ne.jpを見てみることになりました。

インターネットチャンネルのUAはOpera/9.30 (Nintendo Wii; U; ; 2047-7; ja)です。

見て分かる通り、Opera 9.30ベースのブラウザが搭載されているようです。Opera 9は、TLS 1.2には非対応とのこと。

つよいSSLサーバを作るではama.ne.jpをTLS 1.2にのみ対応させて良い評価を狙いました。ですからきっとサーバは無慈悲にも接続を中断して、ブラウザは涙で画面を濡らすことでしょう。

というわけで、インターネットチャンネルにhttps://ama.ne.jpと入力し、アクセスしてみると……

なぜか表示されるんですね。

あれ? どうやってアクセスしてるんだ……? 無理矢理? でも無理矢理ってなんだ?

何が原因なのか

opensslを使って詳しい状況を調査します。

$ openssl s_client -connect ama.ne.jp:443 -tls1
...
SSL-Session:
Protocol  : TLSv1
Cipher    : ECDHE-RSA-AES256-SHA
...
GET / HTTP/1.1
Host: ama.ne.jp

HTTP/1.1 200 OK
...

<!DOCTYPE html>
<html>
...
</html>

ムムム。ちゃんと接続できて、しかも上流からレスポンスも返ってきている……。

じゃあ、nginxを挟まずに直接接続したらどうなるかな?

$ openssl s_client -connect ama.ne.jp:8083 -tls1
...
SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:s3_pkt.c:1472:SSL alert number 70
SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:656:
...
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated

おっ、接続が切られた。どうやらnginxに問題があるようです。

ここで、nginxが使用可能としているプロトコルと暗号スイートのリストについても確認しておきます。

$ nmap --script ssl-enum-ciphers -p 443 ama.ne.jp
Starting Nmap 7.12 ( https://nmap.org ) at 2016-08-03 04:12 JST
Nmap scan report for ama.ne.jp (157.7.73.93)
Host is up (0.0079s latency).
rDNS record for 157.7.73.93: hakurei.ama.ne.jp
PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers:
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       Key exchange parameters of lower strength than certificate key
|_  least strength: A

Nmap done: 1 IP address (1 host up) scanned in 3.72 seconds

確かにTLS 1.2のみが提示されていますね。

というわけで今回の問題は、自分が提示していない暗号スイートを受け入れるnginxのチャラい挙動に原因があるようです。

nginxの設定を見直す

おそらくnginxの設定に、とりわけssl_protocolsssl_ciphersに原因がありそうです。

ssl_protocolsssl_ciphersはどちらもserverおよびhttpブロックに書くことができますが、変更前はserverブロックのみに個別の値を設定していました。

個別とは名ばかりで実際には全て同じ値だったことと、serverブロックの上位であるhttpブロックに設定がないのが不自然だったことから、これらの設定を全てhttpブロックに移したところ正常に動作しました。

いくつかパターンを変えて調べてみたところ、以下のことが分かりました。

  1. http.ssl_protocolsの(http.ssl_ciphers∩ server.ssl_ciphers)がサーバが提示する暗号スイートである。
  2. http.ssl_protocolsのhttp.ssl_ciphersが「実際に使用できる暗号スイート」である。実際に使用できる暗号スイートには、サーバが提示していない暗号スイートが含まれている場合がある。
  3. server.ssl_protocolsは、実際に使用できる暗号スイートにも、サーバが提示する暗号スイートにも影響を与えない。

なお、ssl_protocolsssl_ciphersが存在しないブロックでは、それらにデフォルト値が設定されることに注意します。

これらの挙動によれば、http.ssl_ciphersよりもserver.ssl_ciphersを厳しくしている今回のような場合に、サーバが提示する暗号スイートよりも実際に使用できる暗号スイートが多くなってしまうことが分かります。

その後

設定後に再びインターネットチャンネルでama.ne.jpにアクセスしようとしたところ、「ページが見つからない」ということになりました。絶対404エラーではないと思うんだけど。それでいいのか、インターネットチャンネル。

また、opensslでもきちんと接続できなくなっていることを確認しました。

$ openssl s_client -connect ama.ne.jp:443 -tls1
CONNECTED(00000003)
write:errno=104
...
No ALPN negotiated

さらに、SSL Server Test: ama.ne.jpの評価も、
Handshake Simulationのエラーが軒並み"Server closed connection"になり、すっきり気持ちのいい気分です。

before

実施前のSSL Server Testの結果


after

実施後のSSL Server Testの結果

よかったよかった。おやすみなさい。