1
0
mirror of https://github.com/apache/httpd.git synced 2025-08-08 15:02:10 +03:00

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.


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1868645 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yann Ylavic
2019-10-20 10:50:33 +00:00
parent 82b7e77b82
commit 076e28399c
3 changed files with 91 additions and 7 deletions

View File

@@ -500,8 +500,11 @@ static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s,
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01893)
"Configuring TLS extension handling");
#if OPENSSL_VERSION_NUMBER < 0x10101000L || defined(LIBRESSL_VERSION_NUMBER)
/*
* 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) ||
@@ -512,6 +515,15 @@ static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s,
ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
return ssl_die(s);
}
#else
/*
* 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
/*
@@ -706,7 +718,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

View File

@@ -2346,9 +2346,8 @@ 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;
@@ -2360,7 +2359,9 @@ static apr_status_t init_vhost(conn_rec *c, SSL *ssl)
return APR_SUCCESS;
}
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)) {
@@ -2410,6 +2411,7 @@ static apr_status_t init_vhost(conn_rec *c, SSL *ssl)
return APR_NOTFOUND;
}
#if OPENSSL_VERSION_NUMBER < 0x10101000L || defined(LIBRESSL_VERSION_NUMBER)
/*
* This callback function is executed when OpenSSL encounters an extended
* client hello with a server name indication extension ("SNI", cf. RFC 6066).
@@ -2417,10 +2419,69 @@ 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;
}
#else /* OPENSSL_VERSION_NUMBER < 0x10101000L */
/*
* This callback function 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
@@ -2441,12 +2502,19 @@ 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)
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)) {
/*
@@ -2643,7 +2711,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) {

View File

@@ -915,7 +915,11 @@ SSL_SESSION *ssl_callback_GetSessionCacheEntry(SSL *, IDCONST unsigned char *, i
void ssl_callback_DelSessionCacheEntry(SSL_CTX *, SSL_SESSION *);
void ssl_callback_Info(const SSL *, int, int);
#ifdef HAVE_TLSEXT
#if OPENSSL_VERSION_NUMBER < 0x10101000L || defined(LIBRESSL_VERSION_NUMBER)
int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *);
#else
int ssl_callback_ClientHello(SSL *, int *, void *);
#endif
#endif
#ifdef HAVE_TLS_SESSION_TICKETS
int ssl_callback_SessionTicket(SSL *, unsigned char *, unsigned char *,