On Mon, Jun 18, 2008 at 05:27:17PM +0200, I wrote:
> 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.

It turns out that I introduced a regression for SNI clients with this
patch, unfortunately... sorry about that, mea culpa. While the changes
to ssl_callback_SSLVerify and ssl_callback_SSLVerify_CRL in that version
of the patch do the right thing for non-SNI clients, the code will
segfault when trying to verify the peer cert of an SNI client.

The reason is simple - for SNI clients, no request_rec has been assigned
at that stage (it doesn't exist yet), so accessing r->server attempts to
dereference the NULL pointer.

The attached version (v5) fixes this issue, and an interdiff to v4 is
also included. Would it be feasible/acceptable to commit this to trunk?

Thanks,
Kaspar
Index: httpd-trunk/modules/ssl/ssl_engine_kernel.c
===================================================================
--- httpd-trunk/modules/ssl/ssl_engine_kernel.c (revision 671932)
+++ 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 ? r->server : conn->base_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 ? r->server : c->base_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;
diff -u httpd-trunk/modules/ssl/ssl_engine_kernel.c 
httpd-trunk/modules/ssl/ssl_engine_kernel.c
--- httpd-trunk/modules/ssl/ssl_engine_kernel.c (working copy)
+++ httpd-trunk/modules/ssl/ssl_engine_kernel.c (working copy)
@@ -1312,7 +1312,7 @@
                                           
SSL_get_ex_data_X509_STORE_CTX_idx());
     conn_rec *conn      = (conn_rec *)SSL_get_app_data(ssl);
     request_rec *r      = (request_rec *)SSL_get_app_data2(ssl);
-    server_rec *s       = r->server;
+    server_rec *s       = r ? r->server : conn->base_server;
 
     SSLSrvConfigRec *sc = mySrvConfig(s);
     SSLDirConfigRec *dc = r ? myDirConfig(r) : NULL;
@@ -1445,7 +1445,7 @@
     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;
+    server_rec *s       = r ? r->server : c->base_server;
     SSLSrvConfigRec *sc = mySrvConfig(s);
     SSLConnRec *sslconn = myConnConfig(c);
     modssl_ctx_t *mctx  = myCtxConfig(sslconn, sc);

Reply via email to