SNI: using ClientHello callback.

The change introduces a SNI based virtual server selection with
the ClientHello callback used for early server-side ClientHello
processing.  It reverts 46b9f5d38.

The ClientHello callback is available in OpenSSL version 1.1.1+,
where the TLSv1.3 protocol support was introduced.  For other
versions, the servername callback is preserved.

Notably, this follows BoringSSL behaviour to process server name
before SSL session resumption.
This commit is contained in:
Sergey Kandaurov 2025-01-28 00:53:15 +04:00
parent d16251969b
commit 100f130af7
4 changed files with 137 additions and 82 deletions

View File

@ -737,7 +737,13 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
cln->handler = ngx_ssl_cleanup_ctx; cln->handler = ngx_ssl_cleanup_ctx;
cln->data = &conf->ssl; cln->data = &conf->ssl;
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME #ifdef SSL_CLIENT_HELLO_SUCCESS
/* OpenSSL 1.1.1+ */
SSL_CTX_set_client_hello_cb(conf->ssl.ctx, ngx_http_ssl_servername, NULL);
#elif defined SSL_CTRL_SET_TLSEXT_HOSTNAME
if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx,
ngx_http_ssl_servername) ngx_http_ssl_servername)

View File

@ -90,7 +90,8 @@ ngx_int_t ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
void ngx_http_init_connection(ngx_connection_t *c); void ngx_http_init_connection(ngx_connection_t *c);
void ngx_http_close_connection(ngx_connection_t *c); void ngx_http_close_connection(ngx_connection_t *c);
#if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME) #if (NGX_HTTP_SSL && (defined SSL_CLIENT_HELLO_SUCCESS \
|| defined SSL_CTRL_SET_TLSEXT_HOSTNAME))
int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg);
#endif #endif
#if (NGX_HTTP_SSL && defined SSL_R_CERT_CB_ERROR) #if (NGX_HTTP_SSL && defined SSL_R_CERT_CB_ERROR)

View File

@ -869,48 +869,92 @@ ngx_http_ssl_handshake_handler(ngx_connection_t *c)
} }
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME #if (defined SSL_CLIENT_HELLO_SUCCESS || defined SSL_CTRL_SET_TLSEXT_HOSTNAME)
int int
ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
{ {
ngx_int_t rc; ngx_int_t rc, success, error;
ngx_str_t host; ngx_str_t host;
const char *servername; const char *p;
ngx_connection_t *c; ngx_connection_t *c;
ngx_http_connection_t *hc; ngx_http_connection_t *hc;
ngx_http_ssl_srv_conf_t *sscf; ngx_http_ssl_srv_conf_t *sscf;
ngx_http_core_loc_conf_t *clcf; ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf; ngx_http_core_srv_conf_t *cscf;
#ifdef SSL_CLIENT_HELLO_SUCCESS
success = SSL_CLIENT_HELLO_SUCCESS;
error = SSL_CLIENT_HELLO_ERROR;
#else
success = SSL_TLSEXT_ERR_OK;
error = SSL_TLSEXT_ERR_ALERT_FATAL;
#endif
c = ngx_ssl_get_connection(ssl_conn); c = ngx_ssl_get_connection(ssl_conn);
if (c->ssl->handshaked) { if (c->ssl->handshaked) {
*ad = SSL_AD_NO_RENEGOTIATION; *ad = SSL_AD_NO_RENEGOTIATION;
return SSL_TLSEXT_ERR_ALERT_FATAL; return error;
} }
hc = c->data; hc = c->data;
servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); #ifdef SSL_CLIENT_HELLO_SUCCESS
if (servername == NULL) { if (SSL_client_hello_get0_ext(ssl_conn, TLSEXT_TYPE_server_name,
(const unsigned char **) &p, &host.len)
== 0)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"SSL server name: null"); "SSL server name: null");
goto done; goto done;
} }
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, /* RFC 6066 mandates non-zero HostName length, we follow OpenSSL */
"SSL server name: \"%s\"", servername);
host.len = ngx_strlen(servername); if (host.len < 5
|| (size_t) (p[0] << 8) + p[1] + 2 != host.len
|| p[2] != TLSEXT_NAMETYPE_host_name
|| (size_t) (p[3] << 8) + p[4] + 2 + 3 != host.len)
{
*ad = SSL_AD_DECODE_ERROR;
return error;
}
host.len -= 5;
host.data = (u_char *) p + 5;
if (host.len > TLSEXT_MAXLEN_host_name
|| ngx_strlchr(host.data, host.data + host.len, '\0'))
{
c->ssl->handshake_rejected = 1;
*ad = SSL_AD_UNRECOGNIZED_NAME;
return error;
}
#else
p = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name);
if (p == NULL) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"SSL server name: null");
goto done;
}
host.len = ngx_strlen(p);
host.data = (u_char *) p;
#endif
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"SSL server name: \"%V\"", &host);
if (host.len == 0) { if (host.len == 0) {
goto done; goto done;
} }
host.data = (u_char *) servername;
rc = ngx_http_validate_host(&host, c->pool, 1); rc = ngx_http_validate_host(&host, c->pool, 1);
if (rc == NGX_ERROR) { if (rc == NGX_ERROR) {
@ -932,31 +976,6 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
goto done; goto done;
} }
sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module);
#if (defined TLS1_3_VERSION \
&& !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL)
/*
* SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+,
* but servername being negotiated in every TLSv1.3 handshake
* is only returned in OpenSSL 1.1.1+ as well
*/
if (sscf->verify) {
const char *hostname;
hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn));
if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) {
c->ssl->handshake_rejected = 1;
*ad = SSL_AD_ACCESS_DENIED;
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
}
#endif
hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t)); hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t));
if (hc->ssl_servername == NULL) { if (hc->ssl_servername == NULL) {
goto error; goto error;
@ -970,6 +989,8 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
ngx_set_connection_log(c, clcf->error_log); ngx_set_connection_log(c, clcf->error_log);
sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module);
c->ssl->buffer_size = sscf->buffer_size; c->ssl->buffer_size = sscf->buffer_size;
if (sscf->ssl.ctx) { if (sscf->ssl.ctx) {
@ -1015,15 +1036,15 @@ done:
if (sscf->reject_handshake) { if (sscf->reject_handshake) {
c->ssl->handshake_rejected = 1; c->ssl->handshake_rejected = 1;
*ad = SSL_AD_UNRECOGNIZED_NAME; *ad = SSL_AD_UNRECOGNIZED_NAME;
return SSL_TLSEXT_ERR_ALERT_FATAL; return error;
} }
return SSL_TLSEXT_ERR_OK; return success;
error: error:
*ad = SSL_AD_INTERNAL_ERROR; *ad = SSL_AD_INTERNAL_ERROR;
return SSL_TLSEXT_ERR_ALERT_FATAL; return error;
} }
#endif #endif

View File

@ -22,7 +22,7 @@ static ngx_int_t ngx_stream_ssl_handler(ngx_stream_session_t *s);
static ngx_int_t ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, static ngx_int_t ngx_stream_ssl_init_connection(ngx_ssl_t *ssl,
ngx_connection_t *c); ngx_connection_t *c);
static void ngx_stream_ssl_handshake_handler(ngx_connection_t *c); static void ngx_stream_ssl_handshake_handler(ngx_connection_t *c);
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME #if (defined SSL_CLIENT_HELLO_SUCCESS || defined SSL_CTRL_SET_TLSEXT_HOSTNAME)
static int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, static int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad,
void *arg); void *arg);
#endif #endif
@ -528,47 +528,91 @@ ngx_stream_ssl_handshake_handler(ngx_connection_t *c)
} }
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME #if (defined SSL_CLIENT_HELLO_SUCCESS || defined SSL_CTRL_SET_TLSEXT_HOSTNAME)
static int static int
ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
{ {
ngx_int_t rc; ngx_int_t rc, success, error;
ngx_str_t host; ngx_str_t host;
const char *servername; const char *p;
ngx_connection_t *c; ngx_connection_t *c;
ngx_stream_session_t *s; ngx_stream_session_t *s;
ngx_stream_ssl_srv_conf_t *sscf; ngx_stream_ssl_srv_conf_t *sscf;
ngx_stream_core_srv_conf_t *cscf; ngx_stream_core_srv_conf_t *cscf;
#ifdef SSL_CLIENT_HELLO_SUCCESS
success = SSL_CLIENT_HELLO_SUCCESS;
error = SSL_CLIENT_HELLO_ERROR;
#else
success = SSL_TLSEXT_ERR_OK;
error = SSL_TLSEXT_ERR_ALERT_FATAL;
#endif
c = ngx_ssl_get_connection(ssl_conn); c = ngx_ssl_get_connection(ssl_conn);
if (c->ssl->handshaked) { if (c->ssl->handshaked) {
*ad = SSL_AD_NO_RENEGOTIATION; *ad = SSL_AD_NO_RENEGOTIATION;
return SSL_TLSEXT_ERR_ALERT_FATAL; return error;
} }
s = c->data; s = c->data;
servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); #ifdef SSL_CLIENT_HELLO_SUCCESS
if (servername == NULL) { if (SSL_client_hello_get0_ext(ssl_conn, TLSEXT_TYPE_server_name,
(const unsigned char **) &p, &host.len)
== 0)
{
ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
"SSL server name: null"); "SSL server name: null");
goto done; goto done;
} }
ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, /* RFC 6066 mandates non-zero HostName length, we follow OpenSSL */
"SSL server name: \"%s\"", servername);
host.len = ngx_strlen(servername); if (host.len < 5
|| (size_t) (p[0] << 8) + p[1] + 2 != host.len
|| p[2] != TLSEXT_NAMETYPE_host_name
|| (size_t) (p[3] << 8) + p[4] + 2 + 3 != host.len)
{
*ad = SSL_AD_DECODE_ERROR;
return error;
}
host.len -= 5;
host.data = (u_char *) p + 5;
if (host.len > TLSEXT_MAXLEN_host_name
|| ngx_strlchr(host.data, host.data + host.len, '\0'))
{
c->ssl->handshake_rejected = 1;
*ad = SSL_AD_UNRECOGNIZED_NAME;
return error;
}
#else
p = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name);
if (p == NULL) {
ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
"SSL server name: null");
goto done;
}
host.len = ngx_strlen(p);
host.data = (u_char *) p;
#endif
ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
"SSL server name: \"%V\"", &host);
if (host.len == 0) { if (host.len == 0) {
goto done; goto done;
} }
host.data = (u_char *) servername;
rc = ngx_stream_validate_host(&host, c->pool, 1); rc = ngx_stream_validate_host(&host, c->pool, 1);
if (rc == NGX_ERROR) { if (rc == NGX_ERROR) {
@ -589,35 +633,12 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
goto done; goto done;
} }
sscf = ngx_stream_get_module_srv_conf(cscf->ctx, ngx_stream_ssl_module);
#if (defined TLS1_3_VERSION \
&& !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL)
/*
* SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+,
* but servername being negotiated in every TLSv1.3 handshake
* is only returned in OpenSSL 1.1.1+ as well
*/
if (sscf->verify) {
const char *hostname;
hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn));
if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) {
c->ssl->handshake_rejected = 1;
*ad = SSL_AD_ACCESS_DENIED;
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
}
#endif
s->srv_conf = cscf->ctx->srv_conf; s->srv_conf = cscf->ctx->srv_conf;
ngx_set_connection_log(c, cscf->error_log); ngx_set_connection_log(c, cscf->error_log);
sscf = ngx_stream_get_module_srv_conf(cscf->ctx, ngx_stream_ssl_module);
if (sscf->ssl.ctx) { if (sscf->ssl.ctx) {
if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) {
goto error; goto error;
@ -653,15 +674,15 @@ done:
if (sscf->reject_handshake) { if (sscf->reject_handshake) {
c->ssl->handshake_rejected = 1; c->ssl->handshake_rejected = 1;
*ad = SSL_AD_UNRECOGNIZED_NAME; *ad = SSL_AD_UNRECOGNIZED_NAME;
return SSL_TLSEXT_ERR_ALERT_FATAL; return error;
} }
return SSL_TLSEXT_ERR_OK; return success;
error: error:
*ad = SSL_AD_INTERNAL_ERROR; *ad = SSL_AD_INTERNAL_ERROR;
return SSL_TLSEXT_ERR_ALERT_FATAL; return error;
} }
#endif #endif
@ -990,7 +1011,13 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
cln->handler = ngx_ssl_cleanup_ctx; cln->handler = ngx_ssl_cleanup_ctx;
cln->data = &conf->ssl; cln->data = &conf->ssl;
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME #ifdef SSL_CLIENT_HELLO_SUCCESS
/* OpenSSL 1.1.1+ */
SSL_CTX_set_client_hello_cb(conf->ssl.ctx, ngx_stream_ssl_servername, NULL);
#elif defined SSL_CTRL_SET_TLSEXT_HOSTNAME
SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx,
ngx_stream_ssl_servername); ngx_stream_ssl_servername);
#endif #endif