NGINX enable only TLS v1.2

nginxssl

I'm running NGINX 1.14.0 on an Ubuntu 18.04.02 LTS system with OpenSSL 1.1.1. To date, we've used TLSv1.3 only for our browser support, but we now have a 3rd party who wants to make an api call and they only have library support for TLSv1.2. So nginx checks the path, the presence of an auth header and then forwards:

location ~ ^/api/v2/blah {
    if ($http_authorization = '') {
        return 403;
    }
    include uwsgi_params;
    uwsgi_read_timeout 512;
    uwsgi_send_timeout 512;
    uwsgi_pass lis_django;
}

I thought it would be a simple matter of just specifying ssl_protocols TLSv1.2 TLSv1.3; in the server config but the client reported connectivity problems.

And in fact, using curl to explicitly use tlsv1.2:

curl -v --tlsv1.2 --tls-max 1.2 --location \
    --request GET https://example.com:53303/api/v2/blah' \
    --header 'Authorization: Token 606986de557cd5ca037d0.....'

produces:

Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying x.x.x.x:53303...
* TCP_NODELAY set
* Connected to example.com (x.x.x.x) port 53303 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS alert, protocol version (582):
* error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version
* Closing connection 0
curl: (35) error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version

I've set ssl_protocol: TLSv1.2; on all virtual servers but the problem exists. How can nginx not be supporting TLSv1.2 when it's explicitly configured?

Further details

I'm using a packaged deb but here's what nginx was built with:

nginx version: nginx/1.14.0 (Ubuntu)
built with OpenSSL 1.1.1  11 Sep 2018
TLS SNI support enabled
configure arguments:
--with-cc-opt='-g -O2
-fdebug-prefix-map=/build/nginx-GkiujU/nginx-1.14.0=.
-fstack-protector-strong
-Wformat
-Werror=format-security
-fPIC
-Wdate-time
-D_FORTIFY_SOURCE=2'
--with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx
--conf-path=/etc/nginx/nginx.conf
--http-log-path=/var/log/nginx/access.log
--error-log-path=/var/log/nginx/error.log
--lock-path=/var/lock/nginx.lock
--pid-path=/run/nginx.pid
--modules-path=/usr/lib/nginx/modules
--http-client-body-temp-path=/var/lib/nginx/body
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi
--http-proxy-temp-path=/var/lib/nginx/proxy
--http-scgi-temp-path=/var/lib/nginx/scgi
--http-uwsgi-temp-path=/var/lib/nginx/uwsgi
--with-debug
--with-pcre-jit
--with-http_ssl_module
--with-http_stub_status_module
--with-http_realip_module
--with-http_auth_request_module
--with-http_v2_module
--with-http_dav_module
--with-http_slice_module
--with-threads
--with-http_addition_module
--with-http_geoip_module=dynamic
--with-http_gunzip_module
--with-http_gzip_static_module
--with-http_image_filter_module=dynamic
--with-http_sub_module
--with-http_xslt_module=dynamic
--with-stream=dynamic
--with-stream_ssl_module
--with-mail=dynamic
--with-mail_ssl_module

And I have the base nginx.conf and a site_nginx.conf in /etc/nginx/sites-enabled. Each server conf uses the same ssl configuration:

ssl_protocols TLSv1.3 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM,EDH+AESGCM,ECDHE-ECDSA-AES256-CCM8";
ssl_dhparam /etc/nginx/dhparam;

When I'm explicit about TLSv1.3 with curl:

Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying x.x.x.x:53303...
* TCP_NODELAY set
* Connected to staging.example.com (x.x.x.x) port 53303 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=staging.example.com
*  start date: Jul  2 02:05:47 2020 GMT
*  expire date: Sep 30 02:05:47 2020 GMT
*  subjectAltName: host "staging.example.com" matched cert's "staging.example.com"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x55f7b9791db0)
> GET /api/v2/blah/TQ000003 HTTP/2
> Host: staging.example.com:53303
> user-agent: curl/7.68.0
> accept: */*
> authorization: Token 606986de5...............
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200 
< server: nginx/1.14.0 (Ubuntu)
< date: Sun, 19 Jul 2020 23:31:08 GMT
< content-type: application/json
< content-length: 327
< allow: GET, HEAD, OPTIONS
< x-frame-options: SAMEORIGIN
< vary: Cookie
< 
* Connection #0 to host staging.example.com left intact

I've also looked at the TLSv1.2 cipher support in curl at https://curl.haxx.se/docs/ssl-ciphers.html and explicitly added ECDHE-ECDSA-AES256-CCM8 to the nginx.conf(s). And then tried with the –ciphers parameter:

curl -v --tlsv1.2 --tls-max 1.2 --ciphers ECDHE-ECDSA-AES256-CCM8 --location --request GET 'https://staging.example.com:53303/api/v2/blah/TQ000003' --header 'Authorization: Token 606986de5...........'

but a similar result:

Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying x.x.x.x:53303...
* TCP_NODELAY set
* Connected to staging.example.com (x.x.x.x) port 53303 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ECDHE-ECDSA-AES256-CCM8
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS alert, protocol version (582):
* error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version
* Closing connection 0
curl: (35) error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version

Using OpenSSL client
openssl s_client produces:

openssl s_client -tls1_2 --connect staging.example.com:53303
CONNECTED(00000003)
139791010735424:error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:../ssl/record/rec_layer_s3.c:1543:SSL alert number 70
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 223 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID: 
    Session-ID-ctx: 
    Master-Key: 
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1595275021
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
---





Best Answer

TLS 1.3 has its own list of ciphers which are fixed and don't need to be specified, but TLS 1.2 does not. You need to specify ssl_ciphers when enabling TLS 1.2 (or lower).

A minimum configuration that should work with all modern TLS 1.2 clients would be:

ssl_protocols TLSv1.3 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM,EDH+AESGCM";
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /etc/nginx/dhparam.pem;

The specified list of ciphers is used only with TLS 1.2. With TLS 1.3 the internal fixed cipher list is used.

See the Mozilla SSL Configuration Generator if you need to enable additional ciphers for compatibility with oddball clients.