NginxでTLS 1.3が使えなくてとても悩んだ話(設定の書き方)

新しく建てたFreeBSDのホストにOpenSSL 1.1.1aをインストールしてNginxのウェブサーバを構築したが、過去と同じやり方で設定したところ、TLS1.2では通信できるがTLS 1.3は使えないという変なことになってほぼ1日悩まされた。
FreeBSDに限らないかと思うし、数少ない誰かの役には立つかもしれないので記録として残す。

NginxはFreeBSDのportsでビルドしている。
% nginx -V
nginx version: nginx/1.15.7
built with OpenSSL 1.1.1a 20 Nov 2018
TLS SNI support enabled
configure arguments: --prefix=/usr/local/etc/nginx --with-cc-opt='-I /usr/local/include' --with-ld-opt='-L /usr/local/lib' --conf-path=/usr/local/etc/nginx/nginx.conf --sbin-path=/usr/local/sbin/nginx --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --user=www --group=www --modules-path=/usr/local/libexec/nginx --with-file-aio --http-client-body-temp-path=/var/tmp/nginx/client_body_temp --http-fastcgi-temp-path=/var/tmp/nginx/fastcgi_temp --http-proxy-temp-path=/var/tmp/nginx/proxy_temp --http-scgi-temp-path=/var/tmp/nginx/scgi_temp --http-uwsgi-temp-path=/var/tmp/nginx/uwsgi_temp --http-log-path=/var/log/nginx/access.log --with-http_v2_module --with-http_addition_module --with-http_auth_request_module --with-http_gzip_static_module --with-pcre --with-http_ssl_module --with-http_stub_status_module --without-mail_imap_module --without-mail_pop3_module --without-mail_smtp_module --with-threads --add-dynamic-module=/usr/ports/www/nginx-devel/work/ngx_devel_kit-0.3.0 --add-dynamic-module=/usr/ports/www/nginx-devel/work/nginx-ct-93e9884 --add-dynamic-module=/usr/ports/www/nginx-devel/work/headers-more-nginx-module-55fbdab

上の赤字の部分がOpenSSL 1.1.1aと表示されているのでTLS 1.3対応のNginxになっている筈。

%  ldd /usr/local/sbin/nginx
/usr/local/sbin/nginx:
        libthr.so.3 => /lib/libthr.so.3 (0x8008f4000)
        libcrypt.so.5 => /lib/libcrypt.so.5 (0x800b1c000)
        libpcre.so.1 => /usr/local/lib/libpcre.so.1 (0x800d3b000)
        libssl.so.11 => /usr/local/lib/libssl.so.11 (0x800fd9000)
        libcrypto.so.11 => /usr/local/lib/libcrypto.so.11 (0x801400000)
        libz.so.6 => /lib/libz.so.6 (0x8018e2000)
        libc.so.7 => /lib/libc.so.7 (0x801afa000)
 

libsslは/usr/local/lib/にあるものなのでベースシステム(/usr/lib)のものではports/pkgでインストールしたものと考えて良さそう。

ここまでは問題なさそう。(実は最初は異常だったので再ビルドしたけど)

% openssl version
OpenSSL 1.1.1a  20 Nov 2018

opensslコマンドもFreeBSDに元から入っている1.0.2oではなくportsで入れたOpenSSL 1.1.1aが使われる状態。

% openssl s_client -tls1_2 -connect www.example.com:443
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = www.example.com
verify return:1

中略

---
SSL handshake has read 3029 bytes and written 311 bytes
Verification: OK
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: 5713536EC630D4D728CF6814DDFD95B1F494F7B5228B9F57091DA75E608EADA4
    Session-ID-ctx: 
    Master-Key: AF9F907000380186D47DFB5745615852C2034FA464A03EA2F09827C78865BBE8A509745EEA1C2B723296C33E9D23068A
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1543832199
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: yes
---
closed

TLS 1.2では正常に接続できるので証明書が悪いとか基本的な設定が間違っているということではない筈。

% openssl s_client -tls1_3 -connect www.example.com:443
CONNECTED(00000003)
34387091456:error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:ssl/record/rec_layer_s3.c:1528:SSL alert number 70
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 239 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)

TLS 1.3では通信できない。NginxのSSL/TLSのプロトコル設定で指定していないプロトコルを使用して接続しようとしている場合の応答とよく似ている。(でも全く同じではないような・・)
しかし、設定ファイルでは以下のとおりTLS 1.3とTLS 1.2の2つを指定している。

ssl_protocols           TLSv1.3 TLSv1.2;

自分の足りない頭では意味が解らなくてずいぶん悩まされた。Nginx側もエラーらしいエラーを出さないし。

Nginxのマニュアルを眺めていたらssl_protocolsを書く場所がserverディレクティブ以外にhttpディレクティブもアリみたいなことに気づいた。(いまごろ?)

これまでは1台のサーバでで複数のウェブサイト(バーチャルホスト/バーチャルドメイン)を動かすのでSSL/TLSの設定は全て各バーチャルホスト用の設定ファイルのserverディレクティブに書いていた。

/usr/local/etc/nginx/nginx.conf
1
2
3
4
http {
中略
  include /usr/local/etc/nginx/vhosts/*.conf;
}

include /usr/local/etc/nginx/vhosts/hage.example.com.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name hage.example.com;
中略
  ssl_certificate          /usr/local/etc/letsencrypt/live/hage.example.com/fullchain.pem;
  ssl_certificate_key      /usr/local/etc/letsencrypt/live/hage.example.com/privkey.pem;
  ssl_trusted_certificate  /usr/local/etc/letsencrypt/live/hage.example.com/chain.pem;
  ssl_dhparam              /usr/local/etc/nginx/dhparams/hage.example.com/dhparams4096.pem;

  ssl_protocols            TLSv1.3 TLSv1.2;
  ssl_ciphers              TLS13:EECDH+CHACHA20:EECDH+AESGCM:ECDHE:!COMPLEMENTOFDEFAULT;
中略
}

include /usr/local/etc/nginx/vhosts/hoge.example.com.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name hoge.example.com;
中略
  ssl_certificate           /usr/local/etc/letsencrypt/live/hoge.example.com/fullchain.pem;
  ssl_certificate_key       /usr/local/etc/letsencrypt/live/hoge.example.com/privkey.pem;
  ssl_trusted_certificate   /usr/local/etc/letsencrypt/live/hoge.example.com/chain.pem;
  ssl_dhparam               /usr/local/etc/nginx/dhparams/hoge.example.com/dhparams4096.pem;

  ssl_protocols             TLSv1.3 TLSv1.2;
  ssl_ciphers               TLS13:EECDH+CHACHA20:EECDH+AESGCM:ECDHE:!COMPLEMENTOFDEFAULT;
中略
}

要するに、こんな感じ。

これを変更。

/usr/local/etc/nginx/nginx.conf
1
2
3
4
5
6
7
http {
中略
  ssl_protocols     TLSv1.3 TLSv1.2;
  ssl_ciphers       TLS13:EECDH+CHACHA20:EECDH+AESGCM:ECDHE:!COMPLEMENTOFDEFAULT;
中略
  include           /usr/local/etc/nginx/vhosts/*.conf;
}

nginx.confのhttpディレクティブ内にssl_protocolsとssl_ciphersの設定行を移す。
もちろん各バーチャルホストの設定のserverディレクティブのssl_protocolsとssl_ciphersを削除する。

nginx.confのhttpディレクティブ内に書くとバーチャルホスト別に設定できなくなるが、どうせこれまでも全部のバーチャルホストで同じ設定だったし。
もしくはssl_protocolsだけhttpディレクティブに移動するとか。

% openssl s_client -tls1_3 -connect www.example.com:443
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = www.example.com
verify return:1

中略

---
SSL handshake has read 3109 bytes and written 319 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
closed

正常にTLS 1.3で接続できるようになった。

一応解決したから良いけど、いろいろ試した結果ようやく解決で凄い時間を無駄にした。
別のホストではserverディレクティブに書いたssl_protocolsの設定に関わらずTLS 1.3が有効で、今回のホストでは無効。でもssl_protocolsの設定自体が効かないわけではなくてTLS 1.2以下はserverディレクティブに書いたssl_protocolsの設定が効く。TLS 1.3だけが効かない。
これってNginxの仕様なのかしら、それともバグなのかしら。