]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
Merge r1868645, r1868743, r1868929, r1868934, r1869077 from trunk:
authorJim Jagielski <jim@apache.org>
Tue, 11 Feb 2020 13:21:48 +0000 (13:21 +0000)
committerJim Jagielski <jim@apache.org>
Tue, 11 Feb 2020 13:21:48 +0000 (13:21 +0000)
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

CHANGES
STATUS
docs/manual/mod/mod_ssl.xml
modules/ssl/ssl_engine_config.c
modules/ssl/ssl_engine_init.c
modules/ssl/ssl_engine_kernel.c
modules/ssl/ssl_private.h

diff --git a/CHANGES b/CHANGES
index 8c2dd29365b5c28c6f98c568d1773db56335a771..7992dc11b69e388ac66597e4c01eb59927f70c72 100644 (file)
--- 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 244b8fc2decff2016c7b8b73ceb1e6d2c1f60a66..7d6131a1b82bc787a352831490e1b787addae197 100644 (file)
--- 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 ]
index fcce96f0bf9f542850288c7ac655288fc29995f9..9ba6069454a559599a0d8f71974a205aa8f3f3be 100644 (file)
@@ -673,6 +673,31 @@ SSLProtocol TLSv1
 </highlight>
 </example>
 </usage>
+<note>
+<title><directive>SSLProtocol</directive> for name-based virtual hosts</title>
+<p>
+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 <directive>SSLProtocol</directive> negotiated was always based off
+the one of the <em>base virtual host</em> (first virtual host declared on the
+listening <code>IP:port</code> of the connection).
+</p>
+<p>
+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 <directive>SSLProtocol</directive> of each (name-based) virtual
+host can and will be honored.
+</p>
+<p>
+For compatibility with previous versions, if no
+<directive>SSLProtocol</directive> is configured in a name-based virtual host,
+the one from the base virtual host still applies, <strong>unless</strong>
+<directive>SSLProtocol</directive> is configured globally in which case the
+global value applies (this latter exception is more sensible than compatible,
+though).
+</p>
+</note>
 </directivesynopsis>
 
 <directivesynopsis>
index 6c10bb50777694164542e64a7cefaca4816eae57..7d888aebc478966d92c697906b0d0fa121cc3c44 100644 (file)
@@ -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;
     }
 
index fee17808298b3d01c6629cafb1911d5108373421..bd55c33f06968682c9a71b9778eec65eb95edcdb 100644 (file)
@@ -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
index f8596d57242483765178863803ba4c8f4b4303d3..4a9474318eaef1d11cb4fff4b8a490ab407c3423 100644 (file)
@@ -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) {
index f46814d0adb8098295404c71c34cf5c5f374f463..b802d9a31bd69e910bf22768680e789e2f58d78a 100644 (file)
@@ -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);