Opened 5 years ago
Closed 3 years ago
#1892 closed defect (fixed)
TLSv1.3 session resumption - session tickets renewing
Reported by: | Owned by: | ||
---|---|---|---|
Priority: | minor | Milestone: | |
Component: | nginx-core | Version: | 1.16.x |
Keywords: | TLS | Cc: | |
uname -a: | Linux a974b548d355 5.3.0-1-amd64 #1 SMP Debian 5.3.7-1 (2019-10-19) x86_64 x86_64 x86_64 GNU/Linux | ||
nginx -V: |
nginx version: nginx/1.16.1 (nginx)
built by gcc 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC) built with OpenSSL 1.1.1c 28 May 2019 TLS SNI support enabled configure arguments: --with-ld-opt=-Wl,-rpath,/usr/lib64 --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_geoip_module=dynamic --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-libatomic --with-openssl=../openssl --with-pcre=../pcre --with-pcre-jit --with-http_v2_hpack_enc --with-compat --add-module=../ngx_devel_kit --add-module=../headersmore --add-dynamic-module=../pushstream --add-module=../lua --add-module=../stream-lua --add-module=../nginx-module-vts --add-module=../nginx-auth-ldap --add-module=../nginx-module-sts --add-module=../nginx-module-stream-sts --add-module=../ngx_brotli --add-module=../nginx_upstream_check_module --add-dynamic-module=../ngx_aws_auth --add-dynamic-module=../nginx-module-opentracing/opentracing --add-dynamic-module=../nginx-rtmp-module --with-http_v2_module --with-cc-opt='-O3 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mmmx -msse -msse2 -DTCP_FASTOPEN=23 -Wno-error=strict-aliasing' --with-openssl-opt='no-ssl3 no-dtls no-rc5 enable-weak-ssl-ciphers' --build=nginx --with-debug |
Description
When using TLSv1.3, sessions tickets are not renewed while decrypting tickets and session resumption works only for two requests, because this is the default number of issued tickets.
On the contrary, default session ticket handler in openssl (used when no callbacks were registered via SSL_CTX_set_tlsext_ticket_key_cb
) renews session ticket whenever ticket is decrypted when TLSv1.3 is in use.
Nginx provides its own ticket callback and renews session ticket only when client came with a ticket encrypted with an expired key (ssl_session_ticket_key
rotated).
I believe ngx_ssl_session_ticket_key_callback
should return 2 not only when expired key was used, but also when TLSv1.3 is in use.
Simple working solution (not taking into account future TLS versions):
--- nginx-1.16.1-orig/src/event/ngx_event_openssl.c 2019-11-08 12:59:34.026387380 +0100 +++ nginx-1.16.1/src/event/ngx_event_openssl.c 2019-11-18 11:48:24.872995156 +0100 @@ -4138,7 +4222,8 @@ return -1; } - return (i == 0) ? 1 : 2 /* renew */; + // renew ticket when using TLSv1.3 or ticket was encrypted with expired key + return (i > 0 || ngx_strcmp(SSL_get_version(ssl_conn), "TLSv1.3") == 0) ? 2 /* renew */ : 1; } }
Change History (4)
comment:1 by , 3 years ago
comment:2 by , 3 years ago
Thanks for reporting this. I did some testing with various browsers, notably:
- Safari on Mac, version 15.2 (17612.3.6.1.6)
With TLS 1.2, only reuses sessions with
ssl_session_cache
, but not with session tickets. Further, a session is reused only once.
With TLS 1.3, does not reuse sessions at all regardless of nginx settings. Likely because all session reuse in TLS 1.3 uses session tickets.
- Firefox on Mac, version 96.0.1 (64-bit)
With TLS 1.2, only reuses session with session tickets, but not with
ssl_session_cache
(withssl_session_tickets off;
).
With TLS 1.3, reuses sessions with any settings. That's expected, as using session cache with TLS 1.3 effectively means sending session id as a ticket.
- Chrome on Mac, version 96.0.4664.110 (Official Build) (x86_64)
With TLS 1.2, reuses sessions with both session cache and session tickets.
With TLS 1.3, reuses sessions with both session cache and session tickets. With ticket keys explicitly set viassl_session_ticket_key
, only reuses session twice after the initial handshake, using different session tickets obtained during the initial handshake.
- Opera on Mac, version 83.0.4254.19 (x86_64)
With TLS 1.2, reuses sessions with both session cache and session tickets.
With TLS 1.3, reuses sessions with both session cache and session tickets. With ticket keys explicitly set viassl_session_ticket_key
, only reuses session twice after the initial handshake, using different session tickets obtained during the initial handshake.
Summing the above, it looks like the Chrome and Chrome-based browsers only use each session ticket once with TLS 1.3, and need tickets to be re-generated on each connection.
Most likely it is done as per this recommendation in RFC 8446 / TLS 1.3:
C.4. Client Tracking Prevention Clients SHOULD NOT reuse a ticket for multiple connections. Reuse of a ticket allows passive observers to correlate different connections. Servers that issue tickets SHOULD offer at least as many tickets as the number of connections that a client might use; for example, a web browser using HTTP/1.1 [RFC7230] might open six connections to a server. Servers SHOULD issue new tickets with every connection. This ensures that clients are always able to use a new ticket when creating a new connection.
I personally don't see how passive observers correlating different connections might be a threat here, since TCP already makes it possible to correlate different connections by using client addresses. Anyway, this probably needs addressing to ensure proper session reuse in Chrome and Chrome-based browsers when using ssl_session_ticket_key
.
OpenSSL's default behaviour for built-in tickets handling is to always renew tickets in case of TLS 1.3. We probably should do the same.
Just for the record, with BoringSSL tickets are always renewed with TLS 1.3, so sessions are properly reused regardless of ssl_session_ticket_key
being used. With LibreSSL, session reuse with TLS 1.3 does not seem to be implemented yet (as of LibreSSL 3.4.2).
Slightly cleaned up patch:
# HG changeset patch # User Maxim Dounin <mdounin@mdounin.ru> # Date 1642737110 -10800 # Fri Jan 21 06:51:50 2022 +0300 # Node ID cff51689a4a182cb11cba2eb9303e2bc21815432 # Parent 96ae8e57b3dd1b10f29d3060bbad93b7f9357b92 SSL: always renewing tickets with TLSv1.3 (ticket #1892). Chrome only use TLS session tickets once with TLS 1.3, likely following RFC 8446 Appendix C.4 recommendation. With OpenSSL, this works fine with built-in session tickets, since these are explicitly renewed in case of TLS 1.3 on each session reuse, but results in only two connections being reused after an initial handshake when using ssl_session_ticket_key. Fix is to always renew TLS session tickets in case of TLS 1.3 when using ssl_session_ticket_key, similarly to how it is done by OpenSSL internally. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -4448,7 +4448,21 @@ ngx_ssl_session_ticket_key_callback(ngx_ return -1; } - return (i == 0) ? 1 : 2 /* renew */; + /* renew if TLSv1.3 */ + +#ifdef TLS1_3_VERSION + if (SSL_version(ssl_conn) == TLS1_3_VERSION) { + return 2; + } +#endif + + /* renew if non-default key */ + + if (i != 0) { + return 2; + } + + return 1; } }
comment:4 by , 3 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
Fix committed, thanks for reporting this.
In the meantime, you can rotate tickets with a script like https://github.com/GrapheneOS/nginx-rotate-session-ticket-keys. You can use the provided systemd units/timers or add it to your crontab.
Relevant nginx config: https://github.com/GrapheneOS/grapheneos.org/blob/c78cf741be1148eb2378bac071954c813750b3e0/nginx/nginx.conf#L50