Coincidentally, I was about to finish the second part of my analysis
when I saw the "SNI in which release?" message earlier today.

So here it is, part two... Joe, your help in getting SNI into 2.2.10
(possibly) would be very much appreciated. Thanks if you find time for
looking into this.

We're now considering this case:

> (2) non-SNI client connecting to
>     (b) one of the other, also access-restricted vhosts (two or above)

Name-based SSL virtual hosts for "legacy" clients is kind of an oxymoron
- a fact which has always been documented in the mod_ssl FAQ. Trying to
set up such a configuration with 2.2.x will cause the familiar "You
should not use name-based virtual hosts in conjunction with SSL!!"
warning to appear in the error log, but httpd will start and, depending
on the specific setup, even provide a limited way of name-based virtual
hosting (see e.g. http://wiki.cacert.org/wiki/CSRGenerator to see what
"workarounds" people use).

IMO, the primary question is to what extent exactly we should support
legacy/non-SNI clients. In theory, we could just disallow access to all
but the first vhost for non-SNI clients - something I consider too
radical, and it would also break backwards compatibility.

The other strategy (and the one I propose to follow) is to support
non-SNI clients "as far as possible", and taking special care that no
unauthorized access is possible when client authentication comes into play.

Looking again at our list, we basically have to deal with all these
parameters:

mod_ssl directive        Configured in mod_ssl as
                         (modssl_ctx_t*)mctx->...
-------------------------------------------------
SSLCipherSuite           auth.cipher_suite
SSLVerifyDepth           auth.verify_depth
SSLVerifyClient          auth.verify_mode
SSLCACertificateFile     auth.ca_cert_file
SSLCACertificatePath     auth.ca_cert_path
SSLCARevocationFile      crl_file
SSLCARevocationPath      crl_path
SSLOCSPDefaultResponder  ocsp_responder
SSLOCSPEnable            ocsp_enabled
SSLOCSPOverrideResponder ocsp_force_default
SSLCertificateChainFile  cert_chain
SSLCADNRequestFile       pks->ca_name_file
SSLCADNRequestPath       pks->ca_name_path
SSLHonorCipherOrder      sc->cipher_server_pref
SSLProtocol              protocol

SSLCipherSuite and SSLHonorCipherOrder can be supported quite well by
modifications to ssl_hook_Access. A renegotiation will be needed, but
it's a case which is similar to per-directory SSLCipherSuite settings.

SSLProtocol can't be supported in a reasonable way in ssl_hook_Access,
as the handshake has been completed already at that point (on a side
note, I wonder whether SSLv2 should really be supported by mod_ssl any
longer - it has serious design flaws, and SSLv3 was introduced more
than 10 years ago).

The remaining ones actually all apply to client authentication
(verification of the peer's certificate): SSLVerify{Depth,Client},
SSLCACertificate{File,Path}, SSLCARevocation{File,Path},
SSLOCSP{DefaultResponder,Enable,OverrideResponder},
SSLCADNRequest{File,Path}. Unless SSLVerifyClient is set to "require",
I consider a partial/imperfect implementation of these directives for
legacy/non-SNI clients acceptable (when considering access to vhosts two
and above). It's something which was not supported in the past, and for
some of these settings it's simply not possible to reach the same level
of functionality (e.g., legacy clients will *always* run into name
mismatches when accessing a non-default vhost, that's the reason why the
TLS protocol had to be changed, after all).

Changing SSLCACertificate{File,Path} and SSLCADNRequest{File,Path} after
the initial handshake is not possible with current OpenSSL versions,
unfortunately (it would need the SSL_set_cert_store() function, which
was never added to any official release). SSLCARevocation{File,Path} and
SSLOCSP{Enable,DefaultResponder,OverrideResponder}, however, can be
supported by small modifications to ssl_callback_SSLVerify() and
ssl_callback_SSLVerify_CRL().

So, to support non-SNI clients "as far as possible", let me propose the
attached (additional) patch. It corrects the shortcomings of my earlier
attempt (no longer changing dc->nVerify{Client,Depth} in-place), and
includes the changes to support SSLCipherSuite, SSLHonorCipherOrder,
SSLCARevocation{File,Path} and
SSLOCSP{Enable,DefaultResponder,OverrideResponder} for non-SNI clients.

Thanks for any feedback that helps in getting SNI support into 2.2.x
(comments, questions, hints on remaining issues, etc.).

Kaspar
Index: httpd-trunk/modules/ssl/ssl_engine_kernel.c
===================================================================
--- httpd-trunk/modules/ssl/ssl_engine_kernel.c (revision 669140)
+++ httpd-trunk/modules/ssl/ssl_engine_kernel.c (working copy)
@@ -327,32 +327,35 @@ int ssl_hook_Access(request_rec *r)
      *   because it's the servers choice to select a cipher from the ones the
      *   client supports. So as long as the current cipher is still in the new
      *   cipher suite we're happy. Because we can assume we would have
      *   selected it again even when other (better) ciphers exists now in the
      *   new cipher suite. This approach is fine because the user explicitly
      *   has to enable this via ``SSLOptions +OptRenegotiate''. So we do no
      *   implicit optimizations.
      */
-    if (dc->szCipherSuite) {
+    if (dc->szCipherSuite || (r->server != r->connection->base_server)) {
         /* remember old state */
 
         if (dc->nOptions & SSL_OPT_OPTRENEGOTIATE) {
             cipher = SSL_get_current_cipher(ssl);
         }
         else {
             cipher_list_old = (STACK_OF(SSL_CIPHER) *)SSL_get_ciphers(ssl);
 
             if (cipher_list_old) {
                 cipher_list_old = sk_SSL_CIPHER_dup(cipher_list_old);
             }
         }
 
         /* configure new state */
-        if (!modssl_set_cipher_list(ssl, dc->szCipherSuite)) {
+        if ((dc->szCipherSuite &&
+             !modssl_set_cipher_list(ssl, dc->szCipherSuite)) ||
+            (sc->server->auth.cipher_suite &&
+             !modssl_set_cipher_list(ssl, sc->server->auth.cipher_suite))) {
             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                           "Unable to reconfigure (per-directory) "
                           "permitted SSL ciphers");
             ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, r->server);
 
             if (cipher_list_old) {
                 sk_SSL_CIPHER_free(cipher_list_old);
             }
@@ -408,18 +411,23 @@ int ssl_hook_Access(request_rec *r)
             }
         }
 
         /* cleanup */
         if (cipher_list_old) {
             sk_SSL_CIPHER_free(cipher_list_old);
         }
 
-        /* tracing */
         if (renegotiate) {
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+            if (sc->cipher_server_pref == TRUE) {
+                SSL_set_options(ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
+            }
+#endif
+            /* tracing */
             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                          "Reconfigured cipher suite will force renegotiation");
         }
     }
 
     /*
      * override of SSLVerifyDepth
      *
@@ -427,29 +435,26 @@ int ssl_hook_Access(request_rec *r)
      * function and not by OpenSSL internally (and our function is aware of
      * both the per-server and per-directory contexts). So we cannot ask
      * OpenSSL about the currently verify depth. Instead we remember it in our
      * ap_ctx attached to the SSL* of OpenSSL.  We've to force the
      * renegotiation if the reconfigured/new verify depth is less than the
      * currently active/remembered verify depth (because this means more
      * restriction on the certificate chain).
      */
-    if ((sc->server->auth.verify_depth != UNSET) &&
-        (dc->nVerifyDepth == UNSET)) {
-        /* apply per-vhost setting, if per-directory config is not set */
-        dc->nVerifyDepth = sc->server->auth.verify_depth;
-    }
-    if (dc->nVerifyDepth != UNSET) {
+    if ((dc->nVerifyDepth != UNSET) ||
+        (sc->server->auth.verify_depth != UNSET)) {
         /* XXX: doesnt look like sslconn->verify_depth is actually used */
         if (!(n = sslconn->verify_depth)) {
             sslconn->verify_depth = n = sc->server->auth.verify_depth;
         }
 
         /* determine whether a renegotiation has to be forced */
-        if (dc->nVerifyDepth < n) {
+        if ((dc->nVerifyDepth < n) ||
+            (sc->server->auth.verify_depth < n)) {
             renegotiate = TRUE;
             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                           "Reduced client verification depth will force "
                           "renegotiation");
         }
     }
 
     /*
@@ -461,33 +466,32 @@ int ssl_hook_Access(request_rec *r)
      * The order is: none << optional_no_ca << optional << require
      *
      * Additionally the following optimization is possible here: When the
      * currently active verify type is "none" but a client certificate is
      * already known/present, it's enough to manually force a client
      * verification but at least skip the I/O-intensive renegotation
      * handshake.
      */
-    if ((sc->server->auth.verify_mode != SSL_CVERIFY_UNSET) &&
-        (dc->nVerifyClient == SSL_CVERIFY_UNSET)) {
-        /* apply per-vhost setting, if per-directory config is not set */
-        dc->nVerifyClient = sc->server->auth.verify_mode;
-    }
-    if (dc->nVerifyClient != SSL_CVERIFY_UNSET) {
+    if ((dc->nVerifyClient != SSL_CVERIFY_UNSET) ||
+        (sc->server->auth.verify_mode != SSL_CVERIFY_UNSET)) {
         /* remember old state */
         verify_old = SSL_get_verify_mode(ssl);
         /* configure new state */
         verify = SSL_VERIFY_NONE;
 
-        if (dc->nVerifyClient == SSL_CVERIFY_REQUIRE) {
+        if ((dc->nVerifyClient == SSL_CVERIFY_REQUIRE) ||
+            (sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE)) {
             verify |= SSL_VERIFY_PEER_STRICT;
         }
 
         if ((dc->nVerifyClient == SSL_CVERIFY_OPTIONAL) ||
-            (dc->nVerifyClient == SSL_CVERIFY_OPTIONAL_NO_CA))
+            (dc->nVerifyClient == SSL_CVERIFY_OPTIONAL_NO_CA) ||
+            (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL) ||
+            (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
         {
             verify |= SSL_VERIFY_PEER;
         }
 
         modssl_set_verify(ssl, verify, ssl_callback_SSLVerify);
         SSL_set_verify_result(ssl, X509_V_OK);
 
         /* determine whether we've to force a renegotiation */
@@ -574,16 +578,50 @@ int ssl_hook_Access(request_rec *r)
 
         SSL_set_client_CA_list(ssl, ca_list);
         renegotiate = TRUE;
 
         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                       "Changed client verification locations will force "
                       "renegotiation");
     }
+#else
+#ifndef OPENSSL_NO_TLSEXT
+#define MODSSL_CFG_CA_NE(f, sc1, sc2) \
+    (sc1->server->auth.f && \
+     (!sc2->server->auth.f || \
+      sc2->server->auth.f && strNE(sc1->server->auth.f, sc2->server->auth.f)))
+
+    /* If we're handling a request for a vhost other than the default one,
+     * then we need to make sure that client authentication is properly
+     * enforced. For clients supplying an SNI extension, the peer certificate
+     * verification has happened in the handshake already (and r->server
+     * has been set to r->connection->base_server). For non-SNI requests,
+     * an additional check is needed here. If client authentication is
+     * configured as mandatory, then we can only proceed if the CA list
+     * doesn't have to be changed (SSL_set_cert_store() would be required
+     * for this).
+     */
+    if ((r->server != r->connection->base_server) &&
+        (verify & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) &&
+        renegotiate &&
+        !(SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
+        SSLSrvConfigRec *bssc = mySrvConfig(r->connection->base_server);
+
+        if (MODSSL_CFG_CA_NE(ca_cert_file, sc, bssc) ||
+            MODSSL_CFG_CA_NE(ca_cert_path, sc, bssc)) {
+            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+                 "Non-default virtual host with SSLVerify set to 'require' "
+                 "and VirtualHost-specific CA certificate list is only "
+                 "supported for clients with TLS server name indication "
+                 "(SNI) support");
+            return HTTP_FORBIDDEN;
+        }
+    }
+#endif /* OPENSSL_NO_TLSEXT */
 #endif /* HAVE_SSL_SET_CERT_STORE */
 
     /* If a renegotiation is now required for this location, and the
      * request includes a message body (and the client has not
      * requested a "100 Continue" response), then the client will be
      * streaming the request body over the wire already.  In that
      * case, it is not possible to stop and perform a new SSL
      * handshake immediately; once the SSL library moves to the
@@ -749,18 +787,20 @@ int ssl_hook_Access(request_rec *r)
             }
             sslconn->client_cert = cert;
             sslconn->client_dn = NULL;
         }
 
         /*
          * Finally check for acceptable renegotiation results
          */
-        if (dc->nVerifyClient != SSL_CVERIFY_NONE) {
-            BOOL do_verify = (dc->nVerifyClient == SSL_CVERIFY_REQUIRE);
+        if ((dc->nVerifyClient != SSL_CVERIFY_NONE) ||
+            (sc->server->auth.verify_mode != SSL_CVERIFY_NONE)) {
+            BOOL do_verify = ((dc->nVerifyClient == SSL_CVERIFY_REQUIRE) ||
+                              (sc->server->auth.verify_mode == 
SSL_CVERIFY_REQUIRE));
 
             if (do_verify && (SSL_get_verify_result(ssl) != X509_V_OK)) {
                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                               "Re-negotiation handshake failed: "
                               "Client verification failed");
 
                 return HTTP_FORBIDDEN;
             }
@@ -1266,18 +1306,18 @@ DH *ssl_callback_TmpDH(SSL *ssl, int exp
  * does client authentication and verifies the certificate chain.
  */
 int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
 {
     /* Get Apache context back through OpenSSL context */
     SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
                                           
SSL_get_ex_data_X509_STORE_CTX_idx());
     conn_rec *conn      = (conn_rec *)SSL_get_app_data(ssl);
-    server_rec *s       = conn->base_server;
     request_rec *r      = (request_rec *)SSL_get_app_data2(ssl);
+    server_rec *s       = r->server;
 
     SSLSrvConfigRec *sc = mySrvConfig(s);
     SSLDirConfigRec *dc = r ? myDirConfig(r) : NULL;
     SSLConnRec *sslconn = myConnConfig(conn);
     modssl_ctx_t *mctx  = myCtxConfig(sslconn, sc);
 
     /* Get verify ingredients */
     int errnum   = X509_STORE_CTX_get_error(ctx);
@@ -1397,17 +1437,20 @@ int ssl_callback_SSLVerify(int ok, X509_
     /*
      * And finally signal OpenSSL the (perhaps changed) state
      */
     return ok;
 }
 
 int ssl_callback_SSLVerify_CRL(int ok, X509_STORE_CTX *ctx, conn_rec *c)
 {
-    server_rec *s       = c->base_server;
+    SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
+                                          
SSL_get_ex_data_X509_STORE_CTX_idx());
+    request_rec *r      = (request_rec *)SSL_get_app_data2(ssl);
+    server_rec *s       = r->server;
     SSLSrvConfigRec *sc = mySrvConfig(s);
     SSLConnRec *sslconn = myConnConfig(c);
     modssl_ctx_t *mctx  = myCtxConfig(sslconn, sc);
     X509_OBJECT obj;
     X509_NAME *subject, *issuer;
     X509 *cert;
     X509_CRL *crl;
     EVP_PKEY *pubkey;

Reply via email to