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);