title: nginxのssl_protocolsとssl_ciphers
author: Amane Katagiri
summary: 裏で股を開くnginxちゃん
date: 2016-08-03T06:37:00+09:00
category: tech
license: CC BY 4.0
rating: sfw
ai_summary: NginxでSSL設定を行う際、serverブロックのみにssl_protocolsやssl_ciphersを記述すると、意図せず古いプロトコルでの接続を許可してしまう問題が発生しました。調査の結果、これらの設定をhttpブロックに記述することで、提示外の暗号スイートを受け入れる挙動が解消されることが判明。Wiiなどの古いブラウザを用いた検証を通じ、安全なSSLサーバー構築のための適切な設定箇所を解説しています。
ai_keywords: nginx, SSL/TLS, ssl_protocols, ssl_ciphers, リバースプロキシ, サーバー設定, TLS 1.2, Nintendo Wii, SSL Server Test, OpenSSL

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

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

[TOC]

# 前提
1. 先日、nginx(1.10.1)を導入して、ama.ne.jpなどにリバースプロキシを噛ませることにしました。設定にあたっては、各サイトの**server**ブロックに`ssl_protocols`や`ssl_ciphers`を加え、[つよいSSLサーバを作る](/post/strong-ssl/)と同じプロトコルや暗号スイートを指定しました。
2. 設定の結果、[SSL Server Test: ama.ne.jp](https://globalsign.ssllabs.com/analyze.html?d=ama.ne.jp&latest)の評価も実際の動作も変わらないようで、僕は大変満足げな表情です。

# 問題が発覚するまで
ひょんなことから、Wiiの[インターネットチャンネル](https://www.nintendo.co.jp/wii/features/internet/)でama.ne.jpを見てみることになりました。

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

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

* [インターネットチャンネル向けのウェブページを作りたいのですが…。 : Ｑ＆Ａ - Ｗｉｉ](https://web.archive.org/web/20171124213815/https://www.nintendo.co.jp/wii/q_and_a/093.html)
* [Template:ウェブブラウザにおけるTLS/SSLの対応状況の変化](https://ja.wikipedia.org/wiki/Template:%E3%82%A6%E3%82%A7%E3%83%96%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8BTLS/SSL%E3%81%AE%E5%AF%BE%E5%BF%9C%E7%8A%B6%E6%B3%81%E3%81%AE%E5%A4%89%E5%8C%96)

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

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

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

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

# 何が原因なのか
opensslを使って詳しい状況を調査します。

```text
$ 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を挟まずに直接接続したらどうなるかな？

```text
$ 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が使用可能としているプロトコルと暗号スイートのリストについても確認しておきます。

```text
$ 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_protocols](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols)や[ssl_ciphers](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers)に原因がありそうです。

`ssl_protocols`と`ssl_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_protocols`や`ssl_ciphers`が存在しないブロックでは、それらにデフォルト値が設定されることに注意します。

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

# その後

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

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

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

さらに、[SSL Server Test: ama.ne.jp](https://globalsign.ssllabs.com/analyze.html?d=ama.ne.jp&latest)の評価も、
Handshake Simulationのエラーが軒並み"Server closed connection"になり、すっきり気持ちのいい気分です。

before

![実施前のSSL Server Testの結果](/images/nginx-tls/test-before.png){width=662 height=216}

-----

after

![実施後のSSL Server Testの結果](/images/nginx-tls/test-after.png){width=662 height=216}

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