details: http://freenginx.org/hg/nginx/rev/094e0ea330f5 branches: changeset: 9336:094e0ea330f5 user: Maxim Dounin <mdou...@mdounin.ru> date: Sat Mar 08 21:11:07 2025 +0300 description: SSL: added additional verify context check for OpenSSL.
When using TLSv1.3, OpenSSL 1.1.1e+ allows session resumption with names other than initially negotiated, and provides no documented way to prevent it or even detect. This makes it possible to resume the session with a certificate verified in the context of a different server block, similarly to 5095:4fbef397c753. There is no such problem in LibreSSL since it doesn't support session resumption with TLSv1.3 (and does not allow session resumption with different names with TLSv1.2 and below). Similarly, there is no such problem with BoringSSL. While BoringSSL allows sessions to be resumed with different names, it does so only after checking session id context set by the servername callback. With this change, SSL_get_servername() (which, for TLSv1.3, returns name as sent by the client in the current handshake) is checked against SSL_SESSION_get0_hostname(), and thus prevents incorrect use of certificates verified in different contexts. Note that SSL_SESSION_get0_hostname() is documented to return NULL with TLSv1.3, but in practice it returns the originally negotiated name at least up to OpenSSL 3.4.1, and the documentation is expected to be fixed. diffstat: src/event/ngx_event_openssl.c | 55 +++++++++++++++++++++++++++++++++++++++--- 1 files changed, 50 insertions(+), 5 deletions(-) diffs (74 lines): 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 @@ -5091,20 +5091,65 @@ ngx_ssl_check_verify_context(ngx_connect name = SSL_get_servername(c->ssl->connection, TLSEXT_NAMETYPE_host_name); if (ctx == ssl->ctx) { - return NGX_OK; + goto next; } if (ctx == c->ssl->session_ctx && (ssl->ctx == NULL || name == NULL)) { - return NGX_OK; + goto next; } return NGX_ERROR; -#else +next: + +#if (defined TLS1_3_VERSION \ + && !defined LIBRESSL_VERSION_NUMBER \ + && !defined OPENSSL_IS_BORINGSSL) + { + const char *orig; + SSL_SESSION *sess; + + /* + * When using TLSv1.3, OpenSSL 1.1.1e+ allows session resumption + * with names other than initially negotiated. We use + * SSL_SESSION_get0_hostname() to prevent use of certificates + * verified in different contexts. + */ + + if (SSL_version(c->ssl->connection) != TLS1_3_VERSION) { + return NGX_OK; + } + + if (!SSL_session_reused(c->ssl->connection)) { + return NGX_OK; + } + + sess = SSL_get0_session(c->ssl->connection); + + if (sess == NULL) { + return NGX_OK; + } + + /* + * If a server name was originally negotiated, it shouldn't be changed + * or dropped. If there was no server name, it is not checked, since + * non-SNI clients are allowed to request other virtual servers. + */ + + orig = SSL_SESSION_get0_hostname(sess); + + if (orig == NULL) { + return NGX_OK; + } + + if (name == NULL || ngx_strcmp(name, orig) != 0) { + return NGX_ERROR; + } + } +#endif +#endif return NGX_OK; - -#endif }