Qualys SSL Server Test で A+評価&満点を取得する (nginx)

Qualys SSL Server Test: https://www.ssllabs.com/ssltest/

![ssltest.png](https://qiita-image-store.s3.amazonaws.com/0/15361/ff2266b5-2b14-348d-3f24-09da61f6b632.png)

# 追記

- 2018-12-05 OpenSSL v1.1.1 および TLS v1.3 用の対応を追記。
- 2019-04-04 `TLS_AES_128_GCM_SHA256` 無効化について追記。
- 2019-08-31 CBC 暗号スイートの無効化について追記。
- 2022-06-08 nginx における TLS v1.3 の暗号スイートの設定方法を追記。

# 注意

A+ 評価はいいとしても、満点を取ろうとすると、対応ブラウザが少なくなったり、サーバ負荷が高くなったりします。
実運用では A もしくは A+ 評価を目指すまでに留め、満点は趣味でやってみるくらいでいいかと思います。

# 証明書取得時の注意

もし満点を達成しようとするのであれば、証明書の鍵長を 4096bit 以上にする必要があります。 
鍵長が 4096bit 以上でなければ、 Key Exchange が 100 になりません。

参考: [Let’s EncryptでSSL/TLS証明書のRSA鍵長を4096ビットにする方法 | Webセキュリティの小部屋](https://web.archive.org/web/20220628161301/https://www.websec-room.com/2017/05/04/2809)

# SSL 関係の設定 (アルファベット順)

```
# HSTS を有効にする。
# これを有効にすると、クライアントは、初回接続以降は「必ず」 https で接続してくるようになる。
# 有効にしないと A が評価の上限になり、有効にすることで A+ 評価になる。
# 「必ず」 https 接続になっていいかどうかはサービスによるので、設定には注意すること。
add_header Strict-Transport-Security 'max-age=31536000;' always;

# HSTS Preload に登録する場合は、サブドメインも全て https に対応させ、
# ヘッダに includeSubDomains と preload も含めるようにする。
# @see https://hstspreload.org/
#add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;

# OCSP Stapling に対応するため、ネームサーバを設定する必要がある。
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# 証明書に対応する公開鍵を指定。
# 自分自身の証明書～中間証明書～ルート証明書の一つ前 までの順に結合したファイルを指定する。
# COMODO の PositiveSSL ならば、送付される証明書に含まれる
# {your domain}.crt
# COMODORSADomainValidationSecureServerCA.crt
# COMODORSAAddTrustCA.crt
# の 3 つを、この順に結合する。
# 例)
# cat {your domain}.crt \
#   COMODORSADomainValidationSecureServerCA.crt \
#   COMODORSAAddTrustCA.crt \
#   > server.crt
#
# Let's Encrypt なら fullchain.pem を指定する。
ssl_certificate /path/to/fullchain.pem;

# 証明書に対応する秘密鍵を指定。
ssl_certificate_key /path/to/privkey.pem;

# RSA は WEAK と判定される。
ssl_ciphers 'HIGH !aNULL !eNULL !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED !RSA';

# Cipher Strength を 100 にするためには AES128 など 128bit 以下の暗号を除外する必要がある。
#ssl_ciphers 'HIGH !aNULL !eNULL !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED !RSA !AES128';

# OpenSSL v1.1.1 以上では !ARIA128 も追加で除外する必要がある。
# OpenSSL v1.1.1 以上で、かつ TLSv1.3 を有効にした場合、 /etc/ssl/openssl.cnf にも修正が必要 (後述) 。
#ssl_ciphers 'HIGH !aNULL !eNULL !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED !RSA !AES128 !ARIA128';

# nginx v1.19.4 以降であれば、下の設定で TLSv1.3 用の暗号スイートも設定できるので、 openssl.cnf の修正は不要。
#ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384;

# 2019-08-31 時点、 CBC 暗号スイートが WEAK と判定される。 (ただしスコアに影響はでない)
# CBC を除外するには !SHA1 !SHA256 !SHA384 を追加する。
# なお GCM や CHACHA20 暗号スイートの中にも SHA* という名前を持つものがあるが、これらは除外されない。
# 参考: https://security.stackexchange.com/a/207812
# CBC が WEAK となった経緯: https://security.stackexchange.com/questions/210072/why-does-ssl-labs-now-consider-cbc-suites-weak
#ssl_ciphers 'HIGH !aNULL !eNULL !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED !RSA !AES128 !ARIA128 !SHA1 !SHA256 !SHA384';

# DHE 暗号方式のためのパラメータファイルを指定する。
# このファイルは `openssl dhparam 2048 -out dhparam.pem` コマンドで生成できる。
# 2048bit 以上が推奨されている。
# A 評価にするためには 2048bit 以上で作成する必要がある。
# Key Exchange を 100 にするためには 4096bit 以上で作成する必要がある。
ssl_dhparam /path/to/dhparam.pem;

# Key Exchange を 100 にするためには secp384r1 を指定することが必要。
# 指定しなくても A (A+) 評価は可能。
#ssl_ecdh_curve secp384r1;

# どの暗号化スイートを使うか、サーバ側が決定するようにする。
# クライアントが脆弱な暗号化スイートを使おうとすることを防ぐ。
ssl_prefer_server_ciphers on;

# ssl_protocols を絞ると、対応するブラウザが減る。
# 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
# A 評価にするなら TLSv1 以上に。(ほとんどの携帯電話が非対応に)
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

# Protocol Support を 100 点にするなら TLSv1.2 のみに。 (IE10 以下や Android 4 以下などが非対応に)
# OpenSSL v1.1.1 以上であれば、 TLSv1.3 を追加しても良い。
#ssl_protocols TLSv1.2;

# Session resumption (caching) を有効にする。
ssl_session_cache shared:SSL:10m;

# OCSP Staplingを有効にする。
# OpenSSL のバージョンが古すぎると、 "ssl_stapling" ignored というエラーが出る。
# OpenSSL のバージョンをアップデートすること。
# 例えば CentOS 5 だとアップデートしきっても古いままなので、有効にはできない。
ssl_stapling on;
ssl_stapling_verify on;

# OCSP Stapling で使用する証明書。
# 中間証明書～ルート証明書の順に結合したファイルを指定する。
# COMODO の PositiveSSL ならば、送付される証明書に含まれる
# COMODORSADomainValidationSecureServerCA.crt
# COMODORSAAddTrustCA.crt
# AddTrustExternalCARoot.crt
# の 3 つを、この順に結合する。
# 例)
# cat COMODORSADomainValidationSecureServerCA.crt \
#   COMODORSAAddTrustCA.crt \
#   AddTrustExternalCARoot.crt \
#   > trusted.crt
#
# Let's Encrypt なら chain.pem を指定する。
ssl_trusted_certificate /path/to/chain.pem;
```

# OpenSSL >= v1.1.1 かつ TLS v1.3 を有効にした状態で満点を取得する場合

OpenSSL v1.1.1 で TLS v1.3 を有効にすると

- `TLS_AES_256_GCM_SHA384`
- `TLS_CHACHA20_POLY1305_SHA256`
- `TLS_AES_128_GCM_SHA256`

という暗号スイートが TLS v1.3 用に追加されます。
このうち `TLS_AES_128_GCM_SHA256` は鍵長が 128bit であるため、 Cipher Strength のスコアが落ちます。

`TLS_AES_128_GCM_SHA256` を無効化すればいいのですが、 nginx からでは TLS v1.3 用の暗号スイートを設定できません。
※ TLS v1.3 で使用される暗号スイートは、それ以外の暗号スイートと全く別に管理されるため、 `ssl_ciphers` で設定できません。
※ 2018-12-05 nginx v1.15.7 時点。
※ nginx v1.9.14 以降は設定できるようになりました。↑にその設定方法を記述してあります。 openssl.cnf の修正は不要です。

TLS v1.3 用の暗号スイートは `openssl.cnf` (通常 `/etc/ssl/openssl.cnf` にあるはず) を修正することで設定できます。

`openssl.cnf` を開き、最上部に以下の通り追記します。

```
openssl_conf = default_conf

[default_conf]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
ciphersuites = TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384
```

`ciphersuites` に TLS v1.3 で使用する暗号スイートを列挙します。
修正後、 nginx を再起動 (reload ではなく) することで適用されます。

参考: [#1529 (Could not configure TLS1.3 ciphers in OpenSSL 1.1.1 pre4) – nginx](https://trac.nginx.org/nginx/ticket/1529)

なおスコアには関係しませんが、 AES より ChaCha のほうがパフォーマンスが良いらしいので (未検証) 、 ChaCha を先に指定しています。

参考: [#1445 (OpenSSL - ChaCha prioritized - Nginx enhancement) – nginx](https://trac.nginx.org/nginx/ticket/1445)
参考: [Use ChaCha only if prioritized by clnt by tmshort · Pull Request #4436 · openssl/openssl](https://github.com/openssl/openssl/pull/4436)
参考: [Do the ChaCha: better mobile performance with cryptography](https://blog.cloudflare.com/do-the-chacha-better-mobile-performance-with-cryptography/#performance)

## `TLS_AES_128_GCM_SHA256` 無効化について

`TLS_AES_128_GCM_SHA256` を無効化すると、 Qualys SSL Server Test で以下のようなエラーメッセージが表示されます。(2019-04-04 時点)

> This server does not support the mandatory cipher suite TLS_AES_128_GCM_SHA256 for TLS 1.3. See [RFC 8446](https://tools.ietf.org/html/rfc8446#section-9.1) for details.

これは、 TLS v1.3 の仕様上 `TLS_AES_128_GCM_SHA256` が必須であるためです。
しかし、 `TLS_AES_128_GCM_SHA256` が有効だと Cipher Strength のスコアは落ちます。
このあたりは調整中らしいです。(See https://github.com/ssllabs/ssllabs-scan/issues/636#issuecomment-461727819)

※ 2022-06-08 時点において、上記エラーメッセージは表示されなくなっていました。

仕様を正しく実装することのほうが重要かと思いますので、満点を目指すのはやはり趣味・実験・自己満足に収めたほうがいいかと思います。