diff options
| author | Sergey Kandaurov <pluknet@nginx.com> | 2025-01-28 00:53:15 +0400 |
|---|---|---|
| committer | pluknet <pluknet@nginx.com> | 2025-09-25 19:25:08 +0400 |
| commit | 0373fe5d98c1515640e74fa6f4d32fac1f1d3ab2 (patch) | |
| tree | 10090027a69f6e84e65c079e53094558b446b377 /src/event | |
| parent | bc71625dcca1f1cbd0db7450af853feb90ebba85 (diff) | |
| download | nginx-0373fe5d98c1515640e74fa6f4d32fac1f1d3ab2.tar.gz nginx-0373fe5d98c1515640e74fa6f4d32fac1f1d3ab2.tar.bz2 | |
SNI: using the ClientHello callback.
The change introduces an SNI based virtual server selection during
early ClientHello processing. The callback is available since
OpenSSL 1.1.1; for older OpenSSL versions, the previous behaviour
is kept.
Using the ClientHello callback sets a reasonable processing order
for the "server_name" TLS extension. Notably, session resumption
decision now happens after applying server configuration chosen by
SNI, useful with enabled verification of client certificates, which
brings consistency with BoringSSL behaviour. The change supersedes
and reverts a fix made in 46b9f5d38 for TLSv1.3 resumed sessions.
In addition, since the callback is invoked prior to the protocol
version negotiation, this makes it possible to set "ssl_protocols"
on a per-virtual server basis.
To keep the $ssl_server_name variable working with TLSv1.2 resumed
sessions, as previously fixed in fd97b2a80, a limited server name
callback is preserved in order to acknowledge the extension.
Note that to allow third-party modules to properly chain the call to
ngx_ssl_client_hello_callback(), the servername callback function is
passed through exdata.
Diffstat (limited to 'src/event')
| -rw-r--r-- | src/event/ngx_event_openssl.c | 85 | ||||
| -rw-r--r-- | src/event/ngx_event_openssl.h | 15 |
2 files changed, 100 insertions, 0 deletions
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index e36f30c74..d9abcd082 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -128,6 +128,7 @@ int ngx_ssl_ticket_keys_index; int ngx_ssl_ocsp_index; int ngx_ssl_index; int ngx_ssl_certificate_name_index; +int ngx_ssl_client_hello_arg_index; u_char ngx_ssl_session_buffer[NGX_SSL_MAX_SESSION_SIZE]; @@ -270,6 +271,14 @@ ngx_ssl_init(ngx_log_t *log) return NGX_ERROR; } + ngx_ssl_client_hello_arg_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, + NULL, NULL); + if (ngx_ssl_client_hello_arg_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "SSL_CTX_get_ex_new_index() failed"); + return NGX_ERROR; + } + return NGX_OK; } @@ -1645,6 +1654,82 @@ ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) } +void +ngx_ssl_set_client_hello_callback(SSL_CTX *ssl_ctx, + ngx_ssl_client_hello_arg *cb) +{ +#ifdef SSL_CLIENT_HELLO_SUCCESS + + SSL_CTX_set_client_hello_cb(ssl_ctx, ngx_ssl_client_hello_callback, NULL); + SSL_CTX_set_ex_data(ssl_ctx, ngx_ssl_client_hello_arg_index, cb); + +#endif +} + + +#ifdef SSL_CLIENT_HELLO_SUCCESS + +int +ngx_ssl_client_hello_callback(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) +{ + u_char *p; + size_t len; + ngx_int_t rc; + ngx_str_t host; + ngx_connection_t *c; + ngx_ssl_client_hello_arg *cb; + + c = ngx_ssl_get_connection(ssl_conn); + cb = SSL_CTX_get_ex_data(c->ssl->session_ctx, + ngx_ssl_client_hello_arg_index); + + if (SSL_client_hello_get0_ext(ssl_conn, TLSEXT_TYPE_server_name, + (const unsigned char **) &p, &len) + == 0) + { + ngx_str_null(&host); + goto done; + } + + /* + * RFC 6066 mandates non-zero HostName length, we follow OpenSSL. + * No more than one ServerName is expected. + */ + + if (len < 5 + || (size_t) (p[0] << 8) + p[1] + 2 != len + || p[2] != TLSEXT_NAMETYPE_host_name + || (size_t) (p[3] << 8) + p[4] + 2 + 3 != len) + { + *ad = SSL_AD_DECODE_ERROR; + return SSL_CLIENT_HELLO_ERROR; + } + + len -= 5; + p += 5; + + if (len > TLSEXT_MAXLEN_host_name || ngx_strlchr(p, p + len, '\0')) { + *ad = SSL_AD_UNRECOGNIZED_NAME; + return SSL_CLIENT_HELLO_ERROR; + } + + host.len = len; + host.data = p; + +done: + + rc = cb->servername(ssl_conn, ad, &host); + + if (rc == SSL_TLSEXT_ERR_ALERT_FATAL) { + return SSL_CLIENT_HELLO_ERROR; + } + + return SSL_CLIENT_HELLO_SUCCESS; +} + +#endif + + ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index e7ccd51e8..544703f61 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -151,6 +151,7 @@ struct ngx_ssl_connection_s { unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; + unsigned sni_accepted:1; }; @@ -197,6 +198,13 @@ typedef struct { } ngx_ssl_session_cache_t; +typedef int (*ngx_ssl_servername_pt)(ngx_ssl_conn_t *, int *, void *); + +typedef struct { + ngx_ssl_servername_pt servername; +} ngx_ssl_client_hello_arg; + + #define NGX_SSL_SSLv2 0x0002 #define NGX_SSL_SSLv3 0x0004 #define NGX_SSL_TLSv1 0x0008 @@ -286,6 +294,12 @@ ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths); ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data); +void ngx_ssl_set_client_hello_callback(SSL_CTX *ssl_ctx, + ngx_ssl_client_hello_arg *cb); +#ifdef SSL_CLIENT_HELLO_SUCCESS +int ngx_ssl_client_hello_callback(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); +#endif + ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags); @@ -382,6 +396,7 @@ extern int ngx_ssl_ticket_keys_index; extern int ngx_ssl_ocsp_index; extern int ngx_ssl_index; extern int ngx_ssl_certificate_name_index; +extern int ngx_ssl_client_hello_arg_index; extern u_char ngx_ssl_session_buffer[NGX_SSL_MAX_SESSION_SIZE]; |
