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]; diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index fbf4ab871..3778758e2 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -749,6 +749,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) cln->data = &conf->ssl; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + { + static ngx_ssl_client_hello_arg cb = { ngx_http_ssl_servername }; + + ngx_ssl_set_client_hello_callback(conf->ssl.ctx, &cb); if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, ngx_http_ssl_servername) @@ -759,7 +763,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) "dynamically to an OpenSSL library which has no tlsext support, " "therefore SNI is not available"); } - + } #endif #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 95cb1a133..6f6e975b7 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -891,27 +891,41 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) return SSL_TLSEXT_ERR_ALERT_FATAL; } + if (c->ssl->sni_accepted) { + return SSL_TLSEXT_ERR_OK; + } + hc = c->data; - servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + if (arg != NULL) { + host = *(ngx_str_t *) arg; - if (servername == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "SSL server name: null"); - goto done; + if (host.data == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "SSL server name: null"); + goto done; + } + + } else { + servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + + if (servername == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "SSL server name: null"); + goto done; + } + + host.len = ngx_strlen(servername); + host.data = (u_char *) servername; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "SSL server name: \"%s\"", servername); - - host.len = ngx_strlen(servername); + "SSL server name: \"%V\"", &host); if (host.len == 0) { goto done; } - host.data = (u_char *) servername; - rc = ngx_http_validate_host(&host, c->pool, 1); if (rc == NGX_ERROR) { @@ -933,31 +947,6 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) 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)); if (hc->ssl_servername == NULL) { goto error; @@ -971,6 +960,8 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) 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; if (sscf->ssl.ctx) { @@ -1019,6 +1010,7 @@ done: return SSL_TLSEXT_ERR_ALERT_FATAL; } + c->ssl->sni_accepted = 1; return SSL_TLSEXT_ERR_OK; error: diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 7ce1175f1..7bf6304e4 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -555,27 +555,41 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) return SSL_TLSEXT_ERR_ALERT_FATAL; } + if (c->ssl->sni_accepted) { + return SSL_TLSEXT_ERR_OK; + } + s = c->data; - servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + if (arg) { + host = *(ngx_str_t *) arg; - if (servername == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, - "SSL server name: null"); - goto done; + if (host.data == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: null"); + goto done; + } + + } else { + servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + + if (servername == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: null"); + goto done; + } + + host.len = ngx_strlen(servername); + host.data = (u_char *) servername; } ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, - "SSL server name: \"%s\"", servername); - - host.len = ngx_strlen(servername); + "SSL server name: \"%V\"", &host); if (host.len == 0) { goto done; } - host.data = (u_char *) servername; - rc = ngx_stream_validate_host(&host, c->pool, 1); if (rc == NGX_ERROR) { @@ -596,35 +610,12 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) 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; 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 (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { goto error; @@ -663,6 +654,7 @@ done: return SSL_TLSEXT_ERR_ALERT_FATAL; } + c->ssl->sni_accepted = 1; return SSL_TLSEXT_ERR_OK; error: @@ -1002,8 +994,14 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) cln->data = &conf->ssl; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + { + static ngx_ssl_client_hello_arg cb = { ngx_stream_ssl_servername }; + + ngx_ssl_set_client_hello_callback(conf->ssl.ctx, &cb); + SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, ngx_stream_ssl_servername); + } #endif #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation