From: Jim Jagielski Date: Tue, 11 Feb 2020 13:21:48 +0000 (+0000) Subject: Merge r1868645, r1868743, r1868929, r1868934, r1869077 from trunk: X-Git-Tag: 2.4.42~102 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e0a4a2e331f49b185fe2cb549277837c30d542de;p=thirdparty%2Fapache%2Fhttpd.git Merge r1868645, r1868743, r1868929, r1868934, r1869077 from trunk: mod_ssl: negotiate the TLS protocol version per name based vhost configuration. By using the new ClientHello callback provided by OpenSSL 1.1.1, which runs at the earliest connection stage, we can switch the SSL_CTX of the SSL connection early enough for OpenSSL to take into account the protocol configuration of the vhost. In other words: SSL_set_SSL_CTX(c->SSL, s->SSL_CTX) followed by: SSL_set_{min,max}_proto_version(SSL_CTX_get_{min,max}_proto_version(s->SSL_CTX)) works as expected at this stage (while the same from the SNI callback is ignored by/due to OpenSSL's state machine). Extracting the SNI (to select the relevant vhost) in the ClientHello callback is not as easy as calling SSL_get_servername() though, we have to work with the raw TLS extensions helpers provided by OpenSSL. I stole this code from a test in the OpenSSL source code (i.e. client_hello_select_server_ctx() in test/handshake_helper.c). We can then call init_vhost() as with the SNI callback (in use only for OpenSSL versions earlier than 1.1.1 now), and pass it the extracted SNI. mod_ssl: follow up to r1868645. Restore ssl_callback_ServerNameIndication() even with OpenSSL 1.1.1+, which depends on its return value (OK/NOACK), mainly on session resumption, for SSL_get_servername() to consider or ignore the SNI (returning NULL thus making SSLStrictSNIVHostCheck fail for possibly legitimate cases). This means that init_vhost() should accurately return whether the SNI exists in the configured vhosts, even when it's called multiple times (e.g. first from ClientHello callback and then from SNI callback), so save that state in sslconn->vhost_found and reuse it. mod_ssl: follow up to r1868645. Keep the base server's SSLProtocol if none is configured on the vhost selected by Hello/SNI callback. mod_ssl: follow up to r1868645 and r1868929. Merge ->protocol_set. mod_ssl: follow up to r1868645. CHANGES entry and docs' note. Submitted by: ylavic Reviewed by: ylavic, minfrin, jim git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1873907 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 8c2dd29365b..7992dc11b69 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,12 @@ -*- coding: utf-8 -*- Changes with Apache 2.4.42 + *) mod_ssl: negotiate the TLS protocol version per name based vhost + configuration, when linked with OpenSSL-1.1.1 or later. The base vhost's + SSLProtocol (from the first vhost declared on the IP:port) is now only + relevant if no SSLProtocol is declared for the vhost or globally, + otherwise the vhost or global value apply. [Yann Ylavic] + *) mod_cgi, mod_cgid: Fix a memory leak in some error cases with large script output. PR 64096. [Joe Orton] diff --git a/STATUS b/STATUS index 244b8fc2dec..7d6131a1b82 100644 --- a/STATUS +++ b/STATUS @@ -133,18 +133,6 @@ PATCHES ACCEPTED TO BACKPORT FROM TRUNK: [ start all new proposals below, under PATCHES PROPOSED. ] - *) mod_ssl: negotiate the TLS protocol version per name based vhost... - trunk patch: http://svn.apache.org/r1868645 - http://svn.apache.org/r1868743 - http://svn.apache.org/r1868929 - http://svn.apache.org/r1868934 - http://svn.apache.org/r1869077 - 2.4.x patch: svn merge -c 1868645,1868743,1868929,1868934,1869077 ^/httpd/httpd/trunk . - (merge works modulo CHANGES, below patch for all in one review) - http://people.apache.org/~ylavic/patches/httpd-2.4.x-SSLProtocol_per_vhost.patch - +1: ylavic, minfrin, jim - minfrin: Tiny detail - docs refer to version 2.5.1 - PATCHES PROPOSED TO BACKPORT FROM TRUNK: [ New proposals should be added at the end of the list ] diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml index fcce96f0bf9..9ba6069454a 100644 --- a/docs/manual/mod/mod_ssl.xml +++ b/docs/manual/mod/mod_ssl.xml @@ -673,6 +673,31 @@ SSLProtocol TLSv1 + +<directive>SSLProtocol</directive> for name-based virtual hosts +

+Before OpenSSL 1.1.1, even though the Server Name Indication (SNI) allowed to +determine the targeted virtual host early in the TLS handshake, it was not +possible to switch the TLS protocol version of the connection at this point, +and thus the SSLProtocol negotiated was always based off +the one of the base virtual host (first virtual host declared on the +listening IP:port of the connection). +

+

+Beginning with Apache HTTP server version 2.4.42, when built/linked against +OpenSSL 1.1.1 or later, and when the SNI is provided by the client in the TLS +handshake, the SSLProtocol of each (name-based) virtual +host can and will be honored. +

+

+For compatibility with previous versions, if no +SSLProtocol is configured in a name-based virtual host, +the one from the base virtual host still applies, unless +SSLProtocol is configured globally in which case the +global value applies (this latter exception is more sensible than compatible, +though). +

+
diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c index 6c10bb50777..7d888aebc47 100644 --- a/modules/ssl/ssl_engine_config.c +++ b/modules/ssl/ssl_engine_config.c @@ -261,9 +261,11 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p, modssl_ctx_t *mrg) { if (add->protocol_set) { + mrg->protocol_set = 1; mrg->protocol = add->protocol; } else { + mrg->protocol_set = base->protocol_set; mrg->protocol = base->protocol; } diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index fee17808298..bd55c33f069 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -498,7 +498,9 @@ static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s, "Configuring TLS extension handling"); /* - * Server name indication (SNI) + * The Server Name Indication (SNI) provided by the ClientHello can be + * used to select the right (name-based-)vhost and its SSL configuration + * before the handshake takes place. */ if (!SSL_CTX_set_tlsext_servername_callback(mctx->ssl_ctx, ssl_callback_ServerNameIndication) || @@ -510,6 +512,16 @@ static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s, return ssl_die(s); } +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) + /* + * The ClientHello callback also allows to retrieve the SNI, but since it + * runs at the earliest possible connection stage we can even set the TLS + * protocol version(s) according to the selected (name-based-)vhost, which + * is not possible at the SNI callback stage (due to OpenSSL internals). + */ + SSL_CTX_set_client_hello_cb(mctx->ssl_ctx, ssl_callback_ClientHello, NULL); +#endif + #ifdef HAVE_OCSP_STAPLING /* * OCSP Stapling support, status_request extension @@ -678,7 +690,7 @@ static apr_status_t ssl_init_ctx_protocol(server_rec *s, #else /* #if OPENSSL_VERSION_NUMBER < 0x10100000L */ /* We first determine the maximum protocol version we should provide */ #if SSL_HAVE_PROTOCOL_TLSV1_3 - if (SSL_HAVE_PROTOCOL_TLSV1_3 && (protocol & SSL_PROTOCOL_TLSV1_3)) { + if (protocol & SSL_PROTOCOL_TLSV1_3) { prot = TLS1_3_VERSION; } else #endif diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index f8596d57242..4a9474318ea 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -2328,28 +2328,31 @@ static apr_status_t set_challenge_creds(conn_rec *c, const char *servername, * This function sets the virtual host from an extended * client hello with a server name indication extension ("SNI", cf. RFC 6066). */ -static apr_status_t init_vhost(conn_rec *c, SSL *ssl) +static apr_status_t init_vhost(conn_rec *c, SSL *ssl, const char *servername) { - const char *servername; X509 *cert; EVP_PKEY *key; if (c) { SSLConnRec *sslcon = myConnConfig(c); - - if (sslcon->server != c->base_server) { - /* already found the vhost */ - return APR_SUCCESS; + + if (sslcon->vhost_found) { + /* already found the vhost? */ + return sslcon->vhost_found > 0 ? APR_SUCCESS : APR_NOTFOUND; } + sslcon->vhost_found = -1; - servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) { + servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + } if (servername) { if (ap_vhost_iterate_given_conn(c, ssl_find_vhost, (void *)servername)) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02043) "SSL virtual host for servername %s found", servername); - + + sslcon->vhost_found = +1; return APR_SUCCESS; } else if (ssl_is_challenge(c, servername, &cert, &key)) { @@ -2399,11 +2402,71 @@ static apr_status_t init_vhost(conn_rec *c, SSL *ssl) int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx) { conn_rec *c = (conn_rec *)SSL_get_app_data(ssl); - apr_status_t status = init_vhost(c, ssl); + apr_status_t status = init_vhost(c, ssl, NULL); return (status == APR_SUCCESS)? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_NOACK; } +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) +/* + * This callback function is called when the ClientHello is received. + */ +int ssl_callback_ClientHello(SSL *ssl, int *al, void *arg) +{ + char *servername = NULL; + conn_rec *c = (conn_rec *)SSL_get_app_data(ssl); + const unsigned char *pos; + size_t len, remaining; + (void)arg; + + /* We can't use SSL_get_servername() at this earliest OpenSSL connection + * stage, and there is no SSL_client_hello_get0_servername() provided as + * of OpenSSL 1.1.1. So the code below, that extracts the SNI from the + * ClientHello's TLS extensions, is taken from some test code in OpenSSL, + * i.e. client_hello_select_server_ctx() in "test/handshake_helper.c". + */ + + /* + * The server_name extension was given too much extensibility when it + * was written, so parsing the normal case is a bit complex. + */ + if (!SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &pos, + &remaining) + || remaining <= 2) + goto give_up; + + /* Extract the length of the supplied list of names. */ + len = (*(pos++) << 8); + len += *(pos++); + if (len + 2 != remaining) + goto give_up; + remaining = len; + + /* + * The list in practice only has a single element, so we only consider + * the first one. + */ + if (remaining <= 3 || *pos++ != TLSEXT_NAMETYPE_host_name) + goto give_up; + remaining--; + + /* Now we can finally pull out the byte array with the actual hostname. */ + len = (*(pos++) << 8); + len += *(pos++); + if (len + 2 != remaining) + goto give_up; + + /* Use the SNI to switch to the relevant vhost, should it differ from + * c->base_server. + */ + servername = apr_pstrmemdup(c->pool, (const char *)pos, len); + +give_up: + init_vhost(c, ssl, servername); + return SSL_CLIENT_HELLO_SUCCESS; +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10101000L */ + /* * Find a (name-based) SSL virtual host where either the ServerName * or one of the ServerAliases matches the supplied name (to be used @@ -2423,12 +2486,25 @@ static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s) if (found && (ssl = sslcon->ssl) && (sc = mySrvConfig(s))) { SSL_CTX *ctx = SSL_set_SSL_CTX(ssl, sc->server->ssl_ctx); + /* * SSL_set_SSL_CTX() only deals with the server cert, * so we need to duplicate a few additional settings * from the ctx by hand */ SSL_set_options(ssl, SSL_CTX_get_options(ctx)); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L \ + && (!defined(LIBRESSL_VERSION_NUMBER) \ + || LIBRESSL_VERSION_NUMBER >= 0x20800000L) + /* + * Don't switch the protocol if none is configured for this vhost, + * the default in this case is still the base server's SSLProtocol. + */ + if (myCtxConfig(sslcon, sc)->protocol_set) { + SSL_set_min_proto_version(ssl, SSL_CTX_get_min_proto_version(ctx)); + SSL_set_max_proto_version(ssl, SSL_CTX_get_max_proto_version(ctx)); + } +#endif if ((SSL_get_verify_mode(ssl) == SSL_VERIFY_NONE) || (SSL_num_renegotiations(ssl) == 0)) { /* @@ -2625,7 +2701,7 @@ int ssl_callback_alpn_select(SSL *ssl, * they callback the SNI. We need to make sure that we know which vhost * we are dealing with so we respect the correct protocols. */ - init_vhost(c, ssl); + init_vhost(c, ssl, NULL); proposed = ap_select_protocol(c, NULL, sslconn->server, client_protos); if (!proposed) { diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index f46814d0adb..b802d9a31bd 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -554,6 +554,7 @@ typedef struct { const char *cipher_suite; /* cipher suite used in last reneg */ int service_unavailable; /* thouugh we negotiate SSL, no requests will be served */ + int vhost_found; /* whether we found vhost from SNI already */ } SSLConnRec; /* BIG FAT WARNING: SSLModConfigRec has unusual memory lifetime: it is @@ -928,6 +929,9 @@ void ssl_callback_Info(const SSL *, int, int); #ifdef HAVE_TLSEXT int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *); #endif +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) +int ssl_callback_ClientHello(SSL *, int *, void *); +#endif #ifdef HAVE_TLS_SESSION_TICKETS int ssl_callback_SessionTicket(SSL *, unsigned char *, unsigned char *, EVP_CIPHER_CTX *, HMAC_CTX *, int);