diff options
| author | 1999-09-29 06:28:49 +0000 | |
|---|---|---|
| committer | 1999-09-29 06:28:49 +0000 | |
| commit | a5311d6855861e61375991d1275235e931299ca4 (patch) | |
| tree | f44f3e07a9dd26d791b37aaf78429c2019699036 /usr.sbin/httpd/src/modules/ssl/ssl_engine_kernel.c | |
| parent | add -V option (diff) | |
| download | wireguard-openbsd-a5311d6855861e61375991d1275235e931299ca4.tar.xz wireguard-openbsd-a5311d6855861e61375991d1275235e931299ca4.zip | |
Apache 1.3.9 + Mod_ssl 2.4.2 - now builds with apaci nastiness.
Diffstat (limited to 'usr.sbin/httpd/src/modules/ssl/ssl_engine_kernel.c')
| -rw-r--r-- | usr.sbin/httpd/src/modules/ssl/ssl_engine_kernel.c | 1056 |
1 files changed, 806 insertions, 250 deletions
diff --git a/usr.sbin/httpd/src/modules/ssl/ssl_engine_kernel.c b/usr.sbin/httpd/src/modules/ssl/ssl_engine_kernel.c index 99b19843c07..3addfce1275 100644 --- a/usr.sbin/httpd/src/modules/ssl/ssl_engine_kernel.c +++ b/usr.sbin/httpd/src/modules/ssl/ssl_engine_kernel.c @@ -1,8 +1,8 @@ /* _ _ -** _ __ ___ ___ __| | ___ ___| | -** | '_ ` _ \ / _ \ / _` | / __/ __| | -** | | | | | | (_) | (_| | \__ \__ \ | mod_ssl - Apache Interface to SSLeay -** |_| |_| |_|\___/ \__,_|___|___/___/_| http://www.engelschall.com/sw/mod_ssl/ +** _ __ ___ ___ __| | ___ ___| | mod_ssl +** | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL +** | | | | | | (_) | (_| | \__ \__ \ | www.modssl.org +** |_| |_| |_|\___/ \__,_|___|___/___/_| ftp.modssl.org ** |_____| ** ssl_engine_kernel.c ** The SSL engine kernel @@ -27,7 +27,7 @@ * software must display the following acknowledgment: * "This product includes software developed by * Ralf S. Engelschall <rse@engelschall.com> for use in the - * mod_ssl project (http://www.engelschall.com/sw/mod_ssl/)." + * mod_ssl project (http://www.modssl.org/)." * * 4. The names "mod_ssl" must not be used to endorse or promote * products derived from this software without prior written @@ -42,7 +42,7 @@ * acknowledgment: * "This product includes software developed by * Ralf S. Engelschall <rse@engelschall.com> for use in the - * mod_ssl project (http://www.engelschall.com/sw/mod_ssl/)." + * mod_ssl project (http://www.modssl.org/)." * * THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE @@ -130,8 +130,11 @@ void ssl_hook_NewConnection(conn_rec *conn) server_rec *srvr; BUFF *fb; SSLSrvConfigRec *sc; + ap_ctx *apctx; SSL *ssl; char *cp; + char *cpVHostID; + X509 *xs; int rc; int n; @@ -158,8 +161,9 @@ void ssl_hook_NewConnection(conn_rec *conn) * Remember the connection information for * later access inside callback functions */ + cpVHostID = ssl_util_vhostid(conn->pool, srvr); ssl_log(srvr, SSL_LOG_INFO, "Connection to child %d established (server %s)", - conn->child_num, ssl_util_vhostid(conn->pool, srvr)); + conn->child_num, cpVHostID); /* * Seed the Pseudo Random Number Generator (PRNG) @@ -172,54 +176,41 @@ void ssl_hook_NewConnection(conn_rec *conn) * attach this to the socket. Additionally we register this attachment * so we can detach later. */ - ssl = SSL_new(sc->pSSLCtx); - SSL_set_app_data(ssl, conn); /* conn_rec (available now) */ - SSL_set_app_data2(ssl, NULL); /* request_rec (available later) */ + if ((ssl = SSL_new(sc->pSSLCtx)) == NULL) { + ssl_log(conn->server, SSL_LOG_ERROR|SSL_ADD_SSLERR, + "Unable to create a new SSL connection from the SSL context"); + ap_ctx_set(fb->ctx, "ssl", NULL); + ap_bsetflag(fb, B_EOF|B_EOUT, 1); + conn->aborted = 1; + return; + } + SSL_clear(ssl); + SSL_set_session_id_context(ssl, (unsigned char *)cpVHostID, strlen(cpVHostID)); + SSL_set_app_data(ssl, conn); + apctx = ap_ctx_new(conn->pool); + ap_ctx_set(apctx, "ssl::request_rec", NULL); + ap_ctx_set(apctx, "ssl::verify::depth", AP_CTX_NUM2PTR(0)); + SSL_set_app_data2(ssl, apctx); SSL_set_fd(ssl, fb->fd); ap_ctx_set(fb->ctx, "ssl", ssl); - ap_register_cleanup(conn->pool, (void *)conn, - ssl_hook_CloseConnection, ssl_hook_CloseConnection); /* - * Configure SSLeay BIO Data Logging + * Configure callbacks for SSL connection */ + SSL_set_tmp_rsa_callback(ssl, ssl_callback_TmpRSA); + SSL_set_tmp_dh_callback(ssl, ssl_callback_TmpDH); if (sc->nLogLevel >= SSL_LOG_DEBUG) { BIO_set_callback(SSL_get_rbio(ssl), ssl_io_data_cb); BIO_set_callback_arg(SSL_get_rbio(ssl), ssl); } /* - * Configure the server certificate and private key - * which should be used for this connection. - */ - if (sc->szCertificateFile != NULL) { - if (SSL_use_certificate(ssl, sc->px509Certificate) <= 0) { - ssl_log(conn->server, SSL_LOG_ERROR|SSL_ADD_SSLERR, - "Unable to configure server certificate for connection"); - SSL_free(ssl); - ap_ctx_set(fb->ctx, "ssl", NULL); - ap_bsetflag(fb, B_EOF|B_EOUT, 1); - conn->aborted = 1; - return; - } - if (SSL_use_RSAPrivateKey(ssl, sc->prsaKey) <= 0) { - ssl_log(conn->server, SSL_LOG_ERROR|SSL_ADD_SSLERR, - "Unable to configure server private key for connection"); - SSL_free(ssl); - ap_ctx_set(fb->ctx, "ssl", NULL); - ap_bsetflag(fb, B_EOF|B_EOUT, 1); - conn->aborted = 1; - return; - } - } - - /* * Predefine some client verification results */ ap_ctx_set(fb->ctx, "ssl::client::dn", NULL); ap_ctx_set(fb->ctx, "ssl::verify::error", NULL); - /* + /* * We have to manage a I/O timeout ourself, because Apache * does it the first time when reading the request, but we're * working some time before this happens. @@ -243,7 +234,7 @@ void ssl_hook_NewConnection(conn_rec *conn) ssl_log(srvr, SSL_LOG_INFO, "SSL handshake stopped: connection was closed"); SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); - while (!SSL_shutdown(ssl)); + SSL_smart_shutdown(ssl); SSL_free(ssl); ap_ctx_set(fb->ctx, "ssl", NULL); ap_bsetflag(fb, B_EOF|B_EOUT, 1); @@ -252,16 +243,15 @@ void ssl_hook_NewConnection(conn_rec *conn) } else if (ERR_GET_REASON(ERR_peek_error()) == SSL_R_HTTP_REQUEST) { /* - * The case where SSLeay has recognized a HTTP request: + * The case where OpenSSL has recognized a HTTP request: * This means the client speaks plain HTTP on our HTTPS * port. Hmmmm... At least for this error we can be more friendly * and try to provide him with a HTML error page. We have only one - * problem: SSLeay has already read some bytes from the HTTP + * problem: OpenSSL has already read some bytes from the HTTP * request. So we have to skip the request line manually and * instead provide a faked one in order to continue the internal * Apache processing. * - * (This feature is only available for SSLeay 0.9.0 and higher) */ char ca[2]; int rv; @@ -287,7 +277,7 @@ void ssl_hook_NewConnection(conn_rec *conn) /* third: kick away the SSL stuff */ SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); - while (!SSL_shutdown(ssl)); + SSL_smart_shutdown(ssl); SSL_free(ssl); ap_ctx_set(fb->ctx, "ssl", NULL); @@ -295,11 +285,11 @@ void ssl_hook_NewConnection(conn_rec *conn) return; } else if (ap_ctx_get(ap_global_ctx, "ssl::handshake::timeout") == (void *)TRUE) { - ssl_log(srvr, SSL_LOG_ERROR, + ssl_log(srvr, SSL_LOG_ERROR, "SSL handshake timed out (client %s, server %s)", - conn->remote_ip, ssl_util_vhostid(conn->pool, srvr)); + conn->remote_ip, cpVHostID); SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); - while (!SSL_shutdown(ssl)); + SSL_smart_shutdown(ssl); SSL_free(ssl); ap_ctx_set(fb->ctx, "ssl", NULL); ap_bsetflag(fb, B_EOF|B_EOUT, 1); @@ -309,10 +299,16 @@ void ssl_hook_NewConnection(conn_rec *conn) else if (SSL_get_error(ssl, rc) == SSL_ERROR_SYSCALL) { if (errno == EINTR) continue; - ssl_log(srvr, SSL_LOG_ERROR|SSL_ADD_ERRNO, - "SSL handshake interrupted by system"); + if (errno > 0) + ssl_log(srvr, SSL_LOG_ERROR|SSL_ADD_SSLERR|SSL_ADD_ERRNO, + "SSL handshake interrupted by system " + "[Hint: Stop button pressed in browser?!]"); + else + ssl_log(srvr, SSL_LOG_INFO|SSL_ADD_SSLERR|SSL_ADD_ERRNO, + "Spurious SSL handshake interrupt" + "[Hint: Usually just one of those OpenSSL confusions!?]"); SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); - while (!SSL_shutdown(ssl)); + SSL_smart_shutdown(ssl); SSL_free(ssl); ap_ctx_set(fb->ctx, "ssl", NULL); ap_bsetflag(fb, B_EOF|B_EOUT, 1); @@ -325,7 +321,7 @@ void ssl_hook_NewConnection(conn_rec *conn) */ ssl_log(srvr, SSL_LOG_ERROR|SSL_ADD_SSLERR|SSL_ADD_ERRNO, "SSL handshake failed (client %s, server %s)", - conn->remote_ip, ssl_util_vhostid(conn->pool, srvr)); + conn->remote_ip, cpVHostID); /* * try to gracefully shutdown the connection: @@ -335,7 +331,7 @@ void ssl_hook_NewConnection(conn_rec *conn) * - block the socket, so Apache cannot operate any more */ SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); - while (!SSL_shutdown(ssl)); + SSL_smart_shutdown(ssl); SSL_free(ssl); ap_ctx_set(fb->ctx, "ssl", NULL); ap_bsetflag(fb, B_EOF|B_EOUT, 1); @@ -351,7 +347,7 @@ void ssl_hook_NewConnection(conn_rec *conn) ssl_log(srvr, SSL_LOG_ERROR|SSL_ADD_SSLERR, "SSL client authentication failed: %s", cp); SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); - while (!SSL_shutdown(ssl)); + SSL_smart_shutdown(ssl); SSL_free(ssl); ap_ctx_set(fb->ctx, "ssl", NULL); ap_bsetflag(fb, B_EOF|B_EOUT, 1); @@ -360,17 +356,12 @@ void ssl_hook_NewConnection(conn_rec *conn) } /* - * Remember the peer certificate when - * client authentication was done + * Remember the peer certificate's DN */ - if (sc->nVerifyClient != SSL_CVERIFY_NONE) { - char *s; - X509 *xs; - if ((xs = SSL_get_peer_certificate(ssl)) != NULL) { - s = X509_NAME_oneline(X509_get_subject_name(xs), NULL, 0); - ap_ctx_set(fb->ctx, "ssl::client::dn", ap_pstrdup(conn->pool, s)); - free(s); - } + if ((xs = SSL_get_peer_certificate(ssl)) != NULL) { + cp = X509_NAME_oneline(X509_get_subject_name(xs), NULL, 0); + ap_ctx_set(fb->ctx, "ssl::client::dn", ap_pstrdup(conn->pool, cp)); + free(cp); } /* @@ -382,7 +373,7 @@ void ssl_hook_NewConnection(conn_rec *conn) ssl_log(srvr, SSL_LOG_ERROR, "No acceptable peer certificate available"); SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); - while (!SSL_shutdown(ssl)); + SSL_smart_shutdown(ssl); SSL_free(ssl); ap_ctx_set(fb->ctx, "ssl", NULL); ap_bsetflag(fb, B_EOF|B_EOUT, 1); @@ -399,7 +390,7 @@ void ssl_hook_NewConnection(conn_rec *conn) /* * Improve I/O throughput by using - * SSLeay's read-ahead functionality + * OpenSSL's read-ahead functionality * (don't used under Win32, because * there we use select()) */ @@ -407,6 +398,12 @@ void ssl_hook_NewConnection(conn_rec *conn) SSL_set_read_ahead(ssl, TRUE); #endif +#ifdef SSL_VENDOR + /* Allow vendors to do more things on connection time... */ + ap_hook_use("ap::mod_ssl::vendor::new_connection", + AP_HOOK_SIG2(void,ptr), AP_HOOK_ALL, conn); +#endif + return; } @@ -422,29 +419,89 @@ void ssl_hook_TimeoutConnection(int sig) /* * Close the SSL part of the socket connection + * (called immediately _before_ the socket is closed) */ -void ssl_hook_CloseConnection(void *_conn) +void ssl_hook_CloseConnection(conn_rec *conn) { - conn_rec *conn = _conn; SSL *ssl; + char *cpType; + + ssl = ap_ctx_get(conn->client->ctx, "ssl"); + if (ssl == NULL) + return; /* - * Optionally shutdown the SSL session + * First make sure that no more data is pending in Apache's BUFF, + * because when it's (implicitly) flushed later by the ap_bclose() + * calls of Apache it would lead to an I/O error in the browser due + * to the fact that the SSL layer was already removed by us. + */ + ap_bflush(conn->client); + + /* + * Now close the SSL layer of the connection. We've to take + * the TLSv1 standard into account here: + * + * | 7.2.1. Closure alerts + * | + * | The client and the server must share knowledge that the connection is + * | ending in order to avoid a truncation attack. Either party may + * | initiate the exchange of closing messages. + * | + * | close_notify + * | This message notifies the recipient that the sender will not send + * | any more messages on this connection. The session becomes + * | unresumable if any connection is terminated without proper + * | close_notify messages with level equal to warning. + * | + * | Either party may initiate a close by sending a close_notify alert. + * | Any data received after a closure alert is ignored. + * | + * | Each party is required to send a close_notify alert before closing + * | the write side of the connection. It is required that the other party + * | respond with a close_notify alert of its own and close down the + * | connection immediately, discarding any pending writes. It is not + * | required for the initiator of the close to wait for the responding + * | close_notify alert before closing the read side of the connection. + * + * This means we've to send a close notify message, but haven't to wait + * for the close notify of the client. Actually we cannot wait for the + * close notify of the client because some clients (including Netscape + * 4.x) don't send one, so we would hang. */ - ssl = ap_ctx_get(conn->client->ctx, "ssl"); - if (ssl != NULL) { - SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); - while (!SSL_shutdown(ssl)); - SSL_free(ssl); - ap_ctx_set(conn->client->ctx, "ssl", NULL); - } /* - * And finally log the connection close + * exchange close notify messages, but allow the user + * to force the type of handshake via SetEnvIf directive */ - ssl_log(conn->server, SSL_LOG_INFO, "Connection to child %d closed (server %s)", - conn->child_num, ssl_util_vhostid(conn->pool, conn->server)); + if (ap_ctx_get(conn->client->ctx, "ssl::flag::unclean-shutdown") == PTRUE) { + /* perform no close notify handshake at all + (violates the SSL/TLS standard!) */ + SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); + cpType = "unclean"; + } + else if (ap_ctx_get(conn->client->ctx, "ssl::flag::accurate-shutdown") == PTRUE) { + /* send close notify and wait for clients close notify + (standard compliant, but usually causes connection hangs) */ + SSL_set_shutdown(ssl, 0); + cpType = "accurate"; + } + else { + /* send close notify, but don't wait for clients close notify + (standard compliant and safe, so it's the DEFAULT!) */ + SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); + cpType = "standard"; + } + SSL_smart_shutdown(ssl); + + /* deallocate the SSL connection */ + SSL_free(ssl); + ap_ctx_set(conn->client->ctx, "ssl", NULL); + /* and finally log the fact that we've closed the connection */ + ssl_log(conn->server, SSL_LOG_INFO, + "Connection to child %d closed with %s shutdown (server %s)", + conn->child_num, cpType, ssl_util_vhostid(conn->pool, conn->server)); return; } @@ -454,14 +511,17 @@ void ssl_hook_CloseConnection(void *_conn) int ssl_hook_ReadReq(request_rec *r) { SSL *ssl; + ap_ctx *apctx; /* - * Get the SSL connection structure and perform the + * Get the SSL connection structure and perform the * delayed interlinking from SSL back to request_rec */ ssl = ap_ctx_get(r->connection->client->ctx, "ssl"); - if (ssl != NULL) - SSL_set_app_data2(ssl, r); + if (ssl != NULL) { + apctx = SSL_get_app_data2(ssl); + ap_ctx_set(apctx, "ssl::request_rec", r); + } /* * Force the mod_ssl content handler when URL indicates this @@ -480,6 +540,43 @@ int ssl_hook_ReadReq(request_rec *r) } /* + * URL Translation Handler + */ +int ssl_hook_Translate(request_rec *r) +{ + if (ap_ctx_get(r->connection->client->ctx, "ssl") == NULL) + return DECLINED; + + /* + * Log information about incoming HTTPS requests + */ + if (ap_is_initial_req(r)) + ssl_log(r->server, SSL_LOG_INFO, + "%s HTTPS request received for child %d (server %s)", + r->connection->keepalives <= 0 ? + "Initial (No.1)" : + ap_psprintf(r->pool, "Subsequent (No.%d)", + r->connection->keepalives+1), + r->connection->child_num, + ssl_util_vhostid(r->pool, r->server)); + + /* + * Move SetEnvIf information from request_rec to conn_rec/BUFF + * to allow the close connection handler to use them. + */ + if (ap_table_get(r->subprocess_env, "ssl-unclean-shutdown") != NULL) + ap_ctx_set(r->connection->client->ctx, "ssl::flag::unclean-shutdown", PTRUE); + else + ap_ctx_set(r->connection->client->ctx, "ssl::flag::unclean-shutdown", PFALSE); + if (ap_table_get(r->subprocess_env, "ssl-accurate-shutdown") != NULL) + ap_ctx_set(r->connection->client->ctx, "ssl::flag::accurate-shutdown", PTRUE); + else + ap_ctx_set(r->connection->client->ctx, "ssl::flag::accurate-shutdown", PFALSE); + + return DECLINED; +} + +/* * Content Handler */ int ssl_hook_Handler(request_rec *r) @@ -510,55 +607,6 @@ int ssl_hook_Handler(request_rec *r) } /* - * Auth Handler: - * Fake a Basic authentication from the X509 client certificate. - * - * This must be run fairly early on to prevent a real authentication from - * occuring, in particular it must be run before anything else that - * authenticates a user. This means that the Module statement for this - * module should be LAST in the Configuration file. - */ -int ssl_hook_Auth(request_rec *r) -{ - SSLSrvConfigRec *sc = mySrvConfig(r->server); - SSLDirConfigRec *dc = myDirConfig(r); - char b1[MAX_STRING_LEN], b2[MAX_STRING_LEN]; - char *clientdn; - - /* - * We decline operation in various situations.. - */ - if (!sc->bEnabled) - return DECLINED; - if (ap_ctx_get(r->connection->client->ctx, "ssl") == NULL) - return DECLINED; - if (!(dc->nOptions & SSL_OPT_FAKEBASICAUTH)) - return DECLINED; - if (r->connection->user) - return DECLINED; - if ((clientdn = (char *)ap_ctx_get(r->connection->client->ctx, "ssl::client::dn")) == NULL) - return DECLINED; - - /* - * Fake a password - which one would be immaterial, as, it seems, an empty - * password in the users file would match ALL incoming passwords, if only - * we were using the standard crypt library routine. Unfortunately, SSLeay - * "fixes" a "bug" in crypt and thus prevents blank passwords from - * working. (IMHO what they really fix is a bug in the users of the code - * - failing to program correctly for shadow passwords). We need, - * therefore, to provide a password. This password can be matched by - * adding the string "xxj31ZMTZzkVA" as the password in the user file. - * This is just the crypted variant of the word "password" ;-) - */ - ap_snprintf(b1, sizeof(b1), "%s:password", clientdn); - ssl_util_uuencode(b2, b1, FALSE); - ap_snprintf(b1, sizeof(b1), "Basic %s", b2); - ap_table_set(r->headers_in, "Authorization", b1); - - return DECLINED; -} - -/* * Access Handler */ int ssl_hook_Access(request_rec *r) @@ -566,25 +614,35 @@ int ssl_hook_Access(request_rec *r) SSLDirConfigRec *dc; SSLSrvConfigRec *sc; SSL *ssl; - SSL_CTX *ctx; + SSL_CTX *ctx = NULL; array_header *apRequirement; ssl_require_t *pRequirements; ssl_require_t *pRequirement; char *cp; int ok; int i; - BOOL renegotiate; + BOOL renegotiate; + BOOL renegotiate_quick; #ifdef SSL_EXPERIMENTAL BOOL reconfigured_locations; - STACK *skCAList; + STACK_OF(X509_NAME) *skCAList; char *cpCAPath; char *cpCAFile; #endif - STACK *skCipherOld; - STACK *skCipher; + X509 *cert; + STACK_OF(X509) *certstack; + X509_STORE *certstore; + X509_STORE_CTX certstorectx; + int depth; + STACK_OF(SSL_CIPHER) *skCipherOld; + STACK_OF(SSL_CIPHER) *skCipher; + SSL_CIPHER *pCipher; + ap_ctx *apctx; int nVerifyOld; int nVerify; int n; + void *vp; + int rc; dc = myDirConfig(r); sc = mySrvConfig(r->server); @@ -597,6 +655,8 @@ int ssl_hook_Access(request_rec *r) */ if (dc->bSSLRequired && ssl == NULL) { ap_log_reason("SSL connection required", r->filename, r); + /* remember forbidden access for strict require option */ + ap_table_setn(r->notes, "ssl-access-forbidden", (void *)1); return FORBIDDEN; } @@ -609,83 +669,154 @@ int ssl_hook_Access(request_rec *r) return DECLINED; /* - * Support for per-directory SSL connection parameters. - * + * Support for per-directory reconfigured SSL connection parameters. + * * This is implemented by forcing an SSL renegotiation with the * reconfigured parameter suite. But Apache's internal API processing - * makes our life very hard, because when internal sub-requests occur we - * nevertheless should avoid multiple unnecessary SSL handshakes (they - * need network I/O and time to perform). But the optimization for - * filtering out the unnecessary handshakes isn't such obvious. - * Especially because while Apache is in its sub-request processing the - * client could force additional handshakes, too. And these take place - * perhaps without our notice. So the only possibility is to ask - * SSLeay/OpenSSL whether the renegotiation has to be performed or not. It - * has to performed when some parameters which were previously known (by - * us) are not those we've now reconfigured (as known by SSLeay/OpenSSL). - */ - renegotiate = FALSE; + * makes our life very hard here, because when internal sub-requests occur + * we nevertheless should avoid multiple unnecessary SSL handshakes (they + * require extra network I/O and especially time to perform). + * + * But the optimization for filtering out the unnecessary handshakes isn't + * obvious and trivial. Especially because while Apache is in its + * sub-request processing the client could force additional handshakes, + * too. And these take place perhaps without our notice. So the only + * possibility is to explicitly _ask_ OpenSSL whether the renegotiation + * has to be performed or not. It has to performed when some parameters + * which were previously known (by us) are not those we've now + * reconfigured (as known by OpenSSL) or (in optimized way) at least when + * the reconfigured parameter suite is stronger (more restrictions) than + * the currently active one. + */ + renegotiate = FALSE; + renegotiate_quick = FALSE; #ifdef SSL_EXPERIMENTAL reconfigured_locations = FALSE; #endif - /* - * override of SSLCipherSuite + /* + * Override of SSLCipherSuite + * + * We provide two options here: + * + * o The paranoid and default approach where we force a renegotiation when + * the cipher suite changed in _any_ way (which is straight-forward but + * often forces renegotiations too often and is perhaps not what the + * user actually wanted). + * + * o The optimized and still secure way where we force a renegotiation + * only if the currently active cipher is no longer contained in the + * reconfigured/new cipher suite. Any other changes are not important + * 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 != NULL) { - /* remember old cipher suite for comparison */ - if ((skCipherOld = SSL_get_ciphers(ssl)) != NULL) - skCipherOld = sk_dup(skCipherOld); - /* configure new cipher suite */ + /* remember old state */ + pCipher = NULL; + skCipherOld = NULL; + if (dc->nOptions & SSL_OPT_OPTRENEGOTIATE) + pCipher = SSL_get_current_cipher(ssl); + else { + skCipherOld = SSL_get_ciphers(ssl); + if (skCipherOld != NULL) + skCipherOld = sk_SSL_CIPHER_dup(skCipherOld); + } + /* configure new state */ if (!SSL_set_cipher_list(ssl, dc->szCipherSuite)) { ssl_log(r->server, SSL_LOG_WARN|SSL_ADD_SSLERR, "Unable to reconfigure (per-directory) permitted SSL ciphers"); + if (skCipherOld != NULL) + sk_SSL_CIPHER_free(skCipherOld); return FORBIDDEN; } - /* determine whether the cipher suite was actually changed */ + /* determine whether a renegotiation has to be forced */ skCipher = SSL_get_ciphers(ssl); - if ((skCipherOld == NULL && skCipher != NULL) || - (skCipherOld != NULL && skCipher == NULL) ) - renegotiate = TRUE; - else if (skCipherOld != NULL && skCipher != NULL) { - for (n = 0; n < sk_num(skCipher); n++) { - if (sk_find(skCipherOld, sk_value(skCipher, n)) < 0) { - renegotiate = TRUE; - break; - } + if (dc->nOptions & SSL_OPT_OPTRENEGOTIATE) { + /* optimized way */ + if ((pCipher == NULL && skCipher != NULL) || + (pCipher != NULL && skCipher == NULL) ) + renegotiate = TRUE; + else if (pCipher != NULL && skCipher != NULL + && sk_SSL_CIPHER_find(skCipher, pCipher) < 0) { + renegotiate = TRUE; } - for (n = 0; n < sk_num(skCipherOld); n++) { - if (sk_find(skCipher, sk_value(skCipherOld, n)) < 0) { - renegotiate = TRUE; - break; + } + else { + /* paranoid way */ + if ((skCipherOld == NULL && skCipher != NULL) || + (skCipherOld != NULL && skCipher == NULL) ) + renegotiate = TRUE; + else if (skCipherOld != NULL && skCipher != NULL) { + for (n = 0; !renegotiate && n < sk_SSL_CIPHER_num(skCipher); n++) { + if (sk_SSL_CIPHER_find(skCipherOld, sk_SSL_CIPHER_value(skCipher, n)) < 0) + renegotiate = TRUE; + } + for (n = 0; !renegotiate && n < sk_SSL_CIPHER_num(skCipherOld); n++) { + if (sk_SSL_CIPHER_find(skCipher, sk_SSL_CIPHER_value(skCipherOld, n)) < 0) + renegotiate = TRUE; } } } - /* free old cipher suite */ + /* cleanup */ if (skCipherOld != NULL) - sk_free(skCipherOld); + sk_SSL_CIPHER_free(skCipherOld); + /* tracing */ + if (renegotiate) + ssl_log(r->server, SSL_LOG_TRACE, + "Reconfigured cipher suite will force renegotiation"); } /* - * override of SSLVerifyDepth: - * This is handled by us manually inside the verify callback - * function and not by SSLeay internally. And our function is - * aware of both the per-server and per-directory contexts. - * All we have to do is to force the renegotiation when the - * maximum allowed depth is changed. + * override of SSLVerifyDepth + * + * The depth checks are handled by us manually inside the verify callback + * 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 (dc->nVerifyDepth != UNSET) { - if (dc->nVerifyDepth != sc->nVerifyDepth) + apctx = SSL_get_app_data2(ssl); + if ((vp = ap_ctx_get(apctx, "ssl::verify::depth")) != NULL) + n = AP_CTX_PTR2NUM(vp); + else + n = sc->nVerifyDepth; + ap_ctx_set(apctx, "ssl::verify::depth", + AP_CTX_NUM2PTR(dc->nVerifyDepth)); + /* determine whether a renegotiation has to be forced */ + if (dc->nVerifyDepth < n) { renegotiate = TRUE; + ssl_log(r->server, SSL_LOG_TRACE, + "Reduced client verification depth will force renegotiation"); + } } - /* + /* * override of SSLVerifyClient + * + * We force a renegotiation if the reconfigured/new verify type is + * stronger than the currently active verify type. + * + * 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 (dc->nVerifyClient != SSL_CVERIFY_UNSET) { - /* remember old verify mode */ + /* remember old state */ nVerifyOld = SSL_get_verify_mode(ssl); - /* configure new verify mode */ + /* configure new state */ nVerify = SSL_VERIFY_NONE; if (dc->nVerifyClient == SSL_CVERIFY_REQUIRE) nVerify |= SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT; @@ -694,9 +825,25 @@ int ssl_hook_Access(request_rec *r) nVerify |= SSL_VERIFY_PEER; SSL_set_verify(ssl, nVerify, ssl_callback_SSLVerify); SSL_set_verify_result(ssl, X509_V_OK); - /* determine whether the verify mode was actually changed */ - if (nVerify != nVerifyOld) - renegotiate = TRUE; + /* determine whether we've to force a renegotiation */ + if (nVerify != nVerifyOld) { + if ( ( (nVerifyOld == SSL_VERIFY_NONE) + && (nVerify != SSL_VERIFY_NONE)) + || ( !(nVerifyOld & SSL_VERIFY_PEER) + && (nVerify & SSL_VERIFY_PEER)) + || ( !(nVerifyOld & (SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT)) + && (nVerify & (SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT)))) { + renegotiate = TRUE; + /* optimization */ + if ( dc->nOptions & SSL_OPT_OPTRENEGOTIATE + && nVerifyOld == SSL_VERIFY_NONE + && SSL_get_peer_certificate(ssl) != NULL) + renegotiate_quick = TRUE; + ssl_log(r->server, SSL_LOG_TRACE, + "Changed client verification type will force %srenegotiation", + renegotiate_quick ? "quick " : ""); + } + } } /* @@ -710,13 +857,13 @@ int ssl_hook_Access(request_rec *r) * chance to provide this functionality... */ #ifdef SSL_EXPERIMENTAL - if ( ( dc->szCACertificateFile != NULL - && ( sc->szCACertificateFile == NULL - || ( sc->szCACertificateFile != NULL + if ( ( dc->szCACertificateFile != NULL + && ( sc->szCACertificateFile == NULL + || ( sc->szCACertificateFile != NULL && strNE(dc->szCACertificateFile, sc->szCACertificateFile)))) - || ( dc->szCACertificatePath != NULL - && ( sc->szCACertificatePath == NULL - || ( sc->szCACertificatePath != NULL + || ( dc->szCACertificatePath != NULL + && ( sc->szCACertificatePath == NULL + || ( sc->szCACertificatePath != NULL && strNE(dc->szCACertificatePath, sc->szCACertificatePath)))) ) { cpCAFile = dc->szCACertificateFile != NULL ? dc->szCACertificateFile : sc->szCACertificateFile; @@ -724,8 +871,8 @@ int ssl_hook_Access(request_rec *r) dc->szCACertificatePath : sc->szCACertificatePath; /* FIXME: This should be... - if (!SSL_load_verify_locations(ssl, cpCAFile, cpCAPath)) { - ...but SSLeay/OpenSSL still doesn't provide this! + if (!SSL_load_verify_locations(ssl, cpCAFile, cpCAPath)) { + ...but OpenSSL still doesn't provide this! */ if (!SSL_CTX_load_verify_locations(ctx, cpCAFile, cpCAPath)) { ssl_log(r->server, SSL_LOG_ERROR|SSL_ADD_SSLERR, @@ -733,7 +880,7 @@ int ssl_hook_Access(request_rec *r) "for client authentication"); return FORBIDDEN; } - if ((skCAList = ssl_init_FindCAList(r->server, r->pool, + if ((skCAList = ssl_init_FindCAList(r->server, r->pool, cpCAFile, cpCAPath)) == NULL) { ssl_log(r->server, SSL_LOG_ERROR, "Unable to determine list of available " @@ -743,17 +890,33 @@ int ssl_hook_Access(request_rec *r) SSL_set_client_CA_list(ssl, skCAList); renegotiate = TRUE; reconfigured_locations = TRUE; + ssl_log(r->server, SSL_LOG_TRACE, + "Changed client verification locations will force renegotiation"); } #endif /* SSL_EXPERIMENTAL */ +#ifndef SSL_EXPERIMENTAL /* + * SSL renegotiations in conjunction with HTTP + * requests using the POST method are not supported. + */ + if (renegotiate && r->method_number == M_POST) { + ssl_log(r->server, SSL_LOG_ERROR, + "SSL Re-negotiation in conjunction with POST method not supported!"); + ssl_log(r->server, SSL_LOG_INFO, + "There is only experimental support which has to be enabled first"); + return METHOD_NOT_ALLOWED; + } +#endif /* not SSL_EXPERIMENTAL */ + + /* * now do the renegotiation if anything was actually reconfigured */ if (renegotiate) { - /* + /* * Now we force the SSL renegotation by sending the Hello Request * message to the client. Here we have to do a workaround: Actually - * SSLeay returns immediately after sending the Hello Request (the + * OpenSSL returns immediately after sending the Hello Request (the * intent AFAIK is because the SSL/TLS protocol says it's not a must * that the client replies to a Hello Request). But because we insist * on a reply (anything else is an error for us) we have to go to the @@ -762,33 +925,83 @@ int ssl_hook_Access(request_rec *r) * state explicitly and continue the handshake manually. */ ssl_log(r->server, SSL_LOG_INFO, "Requesting connection re-negotiation"); - SSL_renegotiate(ssl); - SSL_do_handshake(ssl); - if (SSL_get_state(ssl) != SSL_ST_OK) { - ssl_log(r->server, SSL_LOG_ERROR, "Re-negotation request failed"); - return FORBIDDEN; + if (renegotiate_quick) { + /* perform just a manual re-verification of the peer */ + ssl_log(r->server, SSL_LOG_TRACE, + "Performing quick renegotiation: just re-verifying the peer"); + certstore = SSL_CTX_get_cert_store(ctx); + if (certstore == NULL) { + ssl_log(r->server, SSL_LOG_ERROR, "Cannot find certificate storage"); + return FORBIDDEN; + } + certstack = SSL_get_peer_cert_chain(ssl); + if (certstack == NULL || sk_X509_num(certstack) == 0) { + ssl_log(r->server, SSL_LOG_ERROR, "Cannot find peer certificate chain"); + return FORBIDDEN; + } + cert = sk_X509_value(certstack, 0); + X509_STORE_CTX_init(&certstorectx, certstore, cert, certstack); + depth = SSL_get_verify_depth(ssl); + if (depth >= 0) + X509_STORE_CTX_set_depth(&certstorectx, depth); + X509_STORE_CTX_set_ex_data(&certstorectx, + SSL_get_ex_data_X509_STORE_CTX_idx(), (char *)ssl); + if (!X509_verify_cert(&certstorectx)) + ssl_log(r->server, SSL_LOG_ERROR|SSL_ADD_SSLERR, + "Re-negotiation verification step failed"); + SSL_set_verify_result(ssl, certstorectx.error); + X509_STORE_CTX_cleanup(&certstorectx); } - ssl_log(r->server, SSL_LOG_INFO, "Awaiting re-negotiation handshake"); - SSL_set_state(ssl, SSL_ST_ACCEPT); - SSL_do_handshake(ssl); - if (SSL_get_state(ssl) != SSL_ST_OK) { - ssl_log(r->server, SSL_LOG_ERROR, - "Re-negotiation handshake failed: Not accepted by client!?"); - return FORBIDDEN; + else { + /* do a full renegotiation */ + ssl_log(r->server, SSL_LOG_TRACE, + "Performing full renegotiation: complete handshake protocol"); + if (r->main != NULL) + SSL_set_session_id_context(ssl, (unsigned char *)&(r->main), sizeof(r->main)); + else + SSL_set_session_id_context(ssl, (unsigned char *)&r, sizeof(r)); +#ifdef SSL_EXPERIMENTAL + ssl_io_suck(r, ssl); +#endif + SSL_renegotiate(ssl); + SSL_do_handshake(ssl); + if (SSL_get_state(ssl) != SSL_ST_OK) { + ssl_log(r->server, SSL_LOG_ERROR, "Re-negotiation request failed"); + return FORBIDDEN; + } + ssl_log(r->server, SSL_LOG_INFO, "Awaiting re-negotiation handshake"); + SSL_set_state(ssl, SSL_ST_ACCEPT); + SSL_do_handshake(ssl); + if (SSL_get_state(ssl) != SSL_ST_OK) { + ssl_log(r->server, SSL_LOG_ERROR, + "Re-negotiation handshake failed: Not accepted by client!?"); + return FORBIDDEN; + } + } + + /* + * Remember the peer certificate's DN + */ + if ((cert = SSL_get_peer_certificate(ssl)) != NULL) { + cp = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + ap_ctx_set(r->connection->client->ctx, "ssl::client::dn", + ap_pstrdup(r->connection->pool, cp)); + free(cp); } /* * Finally check for acceptable renegotiation results */ if (dc->nVerifyClient != SSL_CVERIFY_NONE) { - if (SSL_get_verify_result(ssl) != X509_V_OK) { - ssl_log(r->server, SSL_LOG_ERROR, + if ( dc->nVerifyClient == SSL_CVERIFY_REQUIRE + && SSL_get_verify_result(ssl) != X509_V_OK ) { + ssl_log(r->server, SSL_LOG_ERROR, "Re-negotiation handshake failed: Client verification failed"); return FORBIDDEN; } - if ( dc->nVerifyClient == SSL_CVERIFY_REQUIRE + if ( dc->nVerifyClient == SSL_CVERIFY_REQUIRE && SSL_get_peer_certificate(ssl) == NULL ) { - ssl_log(r->server, SSL_LOG_ERROR, + ssl_log(r->server, SSL_LOG_ERROR, "Re-negotiation handshake failed: Client certificate missing"); return FORBIDDEN; } @@ -796,10 +1009,10 @@ int ssl_hook_Access(request_rec *r) } /* - * Under old SSLeay we had to change the X509_STORE inside the SSL_CTX - * instead inside the SSL structure, so we have to reconfigure it to the - * old values. This should be changed with forthcoming OpenSSL version - * when better functionality is avaiable. + * Under old OpenSSL we had to change the X509_STORE inside the + * SSL_CTX instead inside the SSL structure, so we have to reconfigure it + * to the old values. This should be changed with forthcoming OpenSSL + * versions when better functionality is avaiable. */ #ifdef SSL_EXPERIMENTAL if (renegotiate && reconfigured_locations) { @@ -817,7 +1030,7 @@ int ssl_hook_Access(request_rec *r) * Check SSLRequire boolean expressions */ apRequirement = dc->aRequirement; - pRequirements = (ssl_require_t *)apRequirement->elts; + pRequirements = (ssl_require_t *)apRequirement->elts; for (i = 0; i < apRequirement->nelts; i++) { pRequirement = &pRequirements[i]; ok = ssl_expr_exec(r, pRequirement->mpExpr); @@ -825,6 +1038,8 @@ int ssl_hook_Access(request_rec *r) cp = ap_psprintf(r->pool, "Failed to execute SSL requirement expression: %s", ssl_expr_get_error()); ap_log_reason(cp, r->filename, r); + /* remember forbidden access for strict require option */ + ap_table_setn(r->notes, "ssl-access-forbidden", (void *)1); return FORBIDDEN; } if (ok != 1) { @@ -835,14 +1050,97 @@ int ssl_hook_Access(request_rec *r) "Failed expression: %s", pRequirement->cpExpr); ap_log_reason("SSL requirement expression not fulfilled " "(see SSL logfile for more details)", r->filename, r); + /* remember forbidden access for strict require option */ + ap_table_setn(r->notes, "ssl-access-forbidden", (void *)1); return FORBIDDEN; } } /* * Else access is granted... + * (except vendor handlers override) */ - return OK; + rc = OK; +#ifdef SSL_VENDOR + ap_hook_use("ap::mod_ssl::vendor::access_handler", + AP_HOOK_SIG2(int,ptr), AP_HOOK_DECLINE(DECLINED), + &rc, r); +#endif + return rc; +} + +/* + * Auth Handler: + * Fake a Basic authentication from the X509 client certificate. + * + * This must be run fairly early on to prevent a real authentication from + * occuring, in particular it must be run before anything else that + * authenticates a user. This means that the Module statement for this + * module should be LAST in the Configuration file. + */ +int ssl_hook_Auth(request_rec *r) +{ + SSLSrvConfigRec *sc = mySrvConfig(r->server); + SSLDirConfigRec *dc = myDirConfig(r); + char b1[MAX_STRING_LEN], b2[MAX_STRING_LEN]; + char *clientdn; + + /* + * Additionally forbid access (again) + * when strict require option is used. + */ + if ( (dc->nOptions & SSL_OPT_STRICTREQUIRE) + && (ap_table_get(r->notes, "ssl-access-forbidden") != NULL)) + return FORBIDDEN; + + /* + * We decline operation in various situations... + */ + if (!sc->bEnabled) + return DECLINED; + if (ap_ctx_get(r->connection->client->ctx, "ssl") == NULL) + return DECLINED; + if (!(dc->nOptions & SSL_OPT_FAKEBASICAUTH)) + return DECLINED; + if (r->connection->user) + return DECLINED; + if ((clientdn = (char *)ap_ctx_get(r->connection->client->ctx, "ssl::client::dn")) == NULL) + return DECLINED; + + /* + * Fake a password - which one would be immaterial, as, it seems, an empty + * password in the users file would match ALL incoming passwords, if only + * we were using the standard crypt library routine. Unfortunately, OpenSSL + * "fixes" a "bug" in crypt and thus prevents blank passwords from + * working. (IMHO what they really fix is a bug in the users of the code + * - failing to program correctly for shadow passwords). We need, + * therefore, to provide a password. This password can be matched by + * adding the string "xxj31ZMTZzkVA" as the password in the user file. + * This is just the crypted variant of the word "password" ;-) + */ + ap_snprintf(b1, sizeof(b1), "%s:password", clientdn); + ssl_util_uuencode(b2, b1, FALSE); + ap_snprintf(b1, sizeof(b1), "Basic %s", b2); + ap_table_set(r->headers_in, "Authorization", b1); + ssl_log(r->server, SSL_LOG_INFO, + "Faking HTTP Basic Auth header: \"Authorization: %s\"", b1); + + return DECLINED; +} + +int ssl_hook_UserCheck(request_rec *r) +{ + SSLDirConfigRec *dc = myDirConfig(r); + + /* + * Additionally forbid access (again) + * when strict require option is used. + */ + if ( (dc->nOptions & SSL_OPT_STRICTREQUIRE) + && (ap_table_get(r->notes, "ssl-access-forbidden") != NULL)) + return FORBIDDEN; + + return DECLINED; } /* @@ -901,6 +1199,7 @@ static const char *ssl_hook_Fixup_vars[] = { "SSL_SERVER_I_DN_Email", "SSL_SERVER_A_KEY", "SSL_SERVER_A_SIG", + "SSL_SESSION_ID", NULL }; @@ -955,22 +1254,95 @@ int ssl_hook_Fixup(request_rec *r) /* _________________________________________________________________ ** -** SSLeay Callback Functions +** OpenSSL Callback Functions ** _________________________________________________________________ */ /* - * Handle out the already generated RSA key... + * Handle out temporary RSA private keys on demand + * + * The background of this as the TLSv1 standard explains it: + * + * | D.1. Temporary RSA keys + * | + * | US Export restrictions limit RSA keys used for encryption to 512 + * | bits, but do not place any limit on lengths of RSA keys used for + * | signing operations. Certificates often need to be larger than 512 + * | bits, since 512-bit RSA keys are not secure enough for high-value + * | transactions or for applications requiring long-term security. Some + * | certificates are also designated signing-only, in which case they + * | cannot be used for key exchange. + * | + * | When the public key in the certificate cannot be used for encryption, + * | the server signs a temporary RSA key, which is then exchanged. In + * | exportable applications, the temporary RSA key should be the maximum + * | allowable length (i.e., 512 bits). Because 512-bit RSA keys are + * | relatively insecure, they should be changed often. For typical + * | electronic commerce applications, it is suggested that keys be + * | changed daily or every 500 transactions, and more often if possible. + * | Note that while it is acceptable to use the same temporary key for + * | multiple transactions, it must be signed each time it is used. + * | + * | RSA key generation is a time-consuming process. In many cases, a + * | low-priority process can be assigned the task of key generation. + * | Whenever a new key is completed, the existing temporary key can be + * | replaced with the new one. + * + * So we generated 512 and 1024 bit temporary keys on startup + * which we now just handle out on demand.... + * */ -RSA *ssl_callback_TmpRSA(SSL *pSSL, int nExport) +RSA *ssl_callback_TmpRSA(SSL *pSSL, int nExport, int nKeyLen) { SSLModConfigRec *mc = myModConfig(); + RSA *rsa; + + rsa = NULL; + if (nExport) { + /* It's because an export cipher is used */ + if (nKeyLen == 512) + rsa = mc->pRSATmpKey512; + else if (nKeyLen == 1024) + rsa = mc->pRSATmpKey1024; + else + /* it's too expensive to generate on-the-fly, so keep 1024bit */ + rsa = mc->pRSATmpKey1024; + } + else { + /* It's because a sign-only certificate situation exists */ + rsa = mc->pRSATmpKey1024; + } + return rsa; +} - return mc->pRSATmpKey; +/* + * Handle out the already generated DH parameters... + */ +DH *ssl_callback_TmpDH(SSL *pSSL, int nExport, int nKeyLen) +{ + SSLModConfigRec *mc = myModConfig(); + DH *dh; + + dh = NULL; + if (nExport) { + /* It's because an export cipher is used */ + if (nKeyLen == 512) + dh = mc->pDHTmpParam512; + else if (nKeyLen == 1024) + dh = mc->pDHTmpParam1024; + else + /* it's too expensive to generate on-the-fly, so keep 1024bit */ + dh = mc->pDHTmpParam1024; + } + else { + /* It's because a sign-only certificate situation exists */ + dh = mc->pDHTmpParam1024; + } + return dh; } /* - * This SSLeay callback function is called when SSLeay + * This OpenSSL callback function is called when OpenSSL * does client authentication and verifies the certificate chain. */ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) @@ -981,19 +1353,22 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) request_rec *r; SSLSrvConfigRec *sc; SSLDirConfigRec *dc; + ap_ctx *actx; X509 *xs; int errnum; int errdepth; char *cp; char *cp2; int depth; + int verify; /* - * Get Apache context back through SSLeay context + * Get Apache context back through OpenSSL context */ ssl = (SSL *)X509_STORE_CTX_get_app_data(ctx); conn = (conn_rec *)SSL_get_app_data(ssl); - r = (request_rec *)SSL_get_app_data2(ssl); + actx = (ap_ctx *)SSL_get_app_data2(ssl); + r = (request_rec *)ap_ctx_get(actx, "ssl::request_rec"); s = conn->server; sc = mySrvConfig(s); dc = (r != NULL ? myDirConfig(r) : NULL); @@ -1022,11 +1397,15 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) /* * Check for optionally acceptable non-verifiable issuer situation */ + if (dc != NULL && dc->nVerifyClient != SSL_CVERIFY_UNSET) + verify = dc->nVerifyClient; + else + verify = sc->nVerifyClient; if ( ( errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN || errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY || errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE ) - && sc->nVerifyClient == SSL_CVERIFY_OPTIONAL_NO_CA ) { + && verify == SSL_CVERIFY_OPTIONAL_NO_CA ) { ssl_log(s, SSL_LOG_TRACE, "Certificate Verification: Verifiable Issuer is configured as " "optional, therefore we're accepting the certificate"); @@ -1034,6 +1413,14 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) } /* + * Additionally perform CRL-based revocation checks + */ + if (ok) { + ok = ssl_callback_SSLVerify_CRL(ok, ctx, s); + errnum = X509_STORE_CTX_get_error(ctx); + } + + /* * If we already know it's not ok, log the real reason */ if (!ok) { @@ -1041,7 +1428,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) errnum, X509_verify_cert_error_string(errnum)); ap_ctx_set(conn->client->ctx, "ssl::client::dn", NULL); ap_ctx_set(conn->client->ctx, "ssl::verify::error", - X509_verify_cert_error_string(errnum)); + (void *)X509_verify_cert_error_string(errnum)); } /* @@ -1049,27 +1436,191 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) */ if (dc != NULL && dc->nVerifyDepth != UNSET) depth = dc->nVerifyDepth; - else + else depth = sc->nVerifyDepth; if (errdepth > depth) { ssl_log(s, SSL_LOG_ERROR, "Certificate Verification: Certificate Chain too long " - "(chain has %d certificates, but maximum allowed are only %d)", + "(chain has %d certificates, but maximum allowed are only %d)", errdepth, depth); ap_ctx_set(conn->client->ctx, "ssl::verify::error", - X509_verify_cert_error_string(X509_V_ERR_CERT_CHAIN_TOO_LONG)); + (void *)X509_verify_cert_error_string(X509_V_ERR_CERT_CHAIN_TOO_LONG)); ok = FALSE; } /* - * And finally signal SSLeay the (perhaps changed) state + * And finally signal OpenSSL the (perhaps changed) state */ return (ok); } +int ssl_callback_SSLVerify_CRL( + int ok, X509_STORE_CTX *ctx, server_rec *s) +{ + SSLSrvConfigRec *sc; + X509_OBJECT obj; + X509_NAME *subject; + X509_NAME *issuer; + X509 *xs; + X509_CRL *crl; + X509_REVOKED *revoked; + long serial; + BIO *bio; + int i, n, rc; + char *cp; + char *cp2; + + /* + * Unless a revocation store for CRLs was created we + * cannot do any CRL-based verification, of course. + */ + sc = mySrvConfig(s); + if (sc->pRevocationStore == NULL) + return ok; + + /* + * Determine certificate ingredients in advance + */ + xs = X509_STORE_CTX_get_current_cert(ctx); + subject = X509_get_subject_name(xs); + issuer = X509_get_issuer_name(xs); + + /* + * OpenSSL provides the general mechanism to deal with CRLs but does not + * use them automatically when verifying certificates, so we do it + * explicitly here. We will check the CRL for the currently checked + * certificate, if there is such a CRL in the store. + * + * We come through this procedure for each certificate in the certificate + * chain, starting with the root-CA's certificate. At each step we've to + * both verify the signature on the CRL (to make sure it's a valid CRL) + * and it's revocation list (to make sure the current certificate isn't + * revoked). But because to check the signature on the CRL we need the + * public key of the issuing CA certificate (which was already processed + * one round before), we've a little problem. But we can both solve it and + * at the same time optimize the processing by using the following + * verification scheme (idea and code snippets borrowed from the GLOBUS + * project): + * + * 1. We'll check the signature of a CRL in each step when we find a CRL + * through the _subject_ name of the current certificate. This CRL + * itself will be needed the first time in the next round, of course. + * But we do the signature processing one round before this where the + * public key of the CA is available. + * + * 2. We'll check the revocation list of a CRL in each step when + * we find a CRL through the _issuer_ name of the current certificate. + * This CRLs signature was then already verified one round before. + * + * This verification scheme allows a CA to revoke its own certificate as + * well, of course. + */ + + /* + * Try to retrieve a CRL corresponding to the _subject_ of + * the current certificate in order to verify it's integrity. + */ + memset((char *)&obj, 0, sizeof(obj)); + rc = SSL_X509_STORE_lookup(sc->pRevocationStore, X509_LU_CRL, subject, &obj); + crl = obj.data.crl; + if (rc > 0 && crl != NULL) { + /* + * Log information about CRL + * (A little bit complicated because of ASN.1 and BIOs...) + */ + if (ssl_log_applies(s, SSL_LOG_TRACE)) { + bio = BIO_new(BIO_s_mem()); + BIO_printf(bio, "lastUpdate: "); + ASN1_UTCTIME_print(bio, X509_CRL_get_lastUpdate(crl)); + BIO_printf(bio, ", nextUpdate: "); + ASN1_UTCTIME_print(bio, X509_CRL_get_nextUpdate(crl)); + n = BIO_pending(bio); + cp = malloc(n+1); + n = BIO_read(bio, cp, n); + cp[n] = NUL; + BIO_free(bio); + cp2 = X509_NAME_oneline(subject, NULL, 0); + ssl_log(s, SSL_LOG_TRACE, "CA CRL: Issuer: %s, %s", cp2, cp); + free(cp2); + free(cp); + } + + /* + * Verify the signature on this CRL + */ + if (X509_CRL_verify(crl, X509_get_pubkey(xs)) <= 0) { + ssl_log(s, SSL_LOG_WARN, "Invalid signature on CRL"); + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE); + X509_OBJECT_free_contents(&obj); + return FALSE; + } + + /* + * Check date of CRL to make sure it's not expired + */ + i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl)); + if (i == 0) { + ssl_log(s, SSL_LOG_WARN, "Found CRL has invalid nextUpdate field"); + X509_STORE_CTX_set_error(ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); + X509_OBJECT_free_contents(&obj); + return FALSE; + } + if (i < 0) { + ssl_log(s, SSL_LOG_WARN, + "Found CRL is expired - " + "revoking all certificates until you get updated CRL"); + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED); + X509_OBJECT_free_contents(&obj); + return FALSE; + } + X509_OBJECT_free_contents(&obj); + } + + /* + * Try to retrieve a CRL corresponding to the _issuer_ of + * the current certificate in order to check for revocation. + */ + memset((char *)&obj, 0, sizeof(obj)); + rc = SSL_X509_STORE_lookup(sc->pRevocationStore, X509_LU_CRL, issuer, &obj); + crl = obj.data.crl; + if (rc > 0 && crl != NULL) { + /* + * Check if the current certificate is revoked by this CRL + */ +#if SSL_LIBRARY_VERSION < 0x00904000 + n = sk_num(X509_CRL_get_REVOKED(crl)); +#else + n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl)); +#endif + for (i = 0; i < n; i++) { +#if SSL_LIBRARY_VERSION < 0x00904000 + revoked = (X509_REVOKED *)sk_value(X509_CRL_get_REVOKED(crl), i); +#else + revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i); +#endif + if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(xs)) == 0) { + + serial = ASN1_INTEGER_get(revoked->serialNumber); + cp = X509_NAME_oneline(issuer, NULL, 0); + ssl_log(s, SSL_LOG_INFO, + "Certificate with serial %ld (0x%lX) " + "revoked per CRL from issuer %s", + serial, serial, cp); + free(cp); + + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED); + X509_OBJECT_free_contents(&obj); + return FALSE; + } + } + X509_OBJECT_free_contents(&obj); + } + return ok; +} + /* - * This callback function is executed by SSLeay whenever a new SSL_SESSION is - * added to the internal SSLeay session cache. We use this hook to spread the + * This callback function is executed by OpenSSL whenever a new SSL_SESSION is + * added to the internal OpenSSL session cache. We use this hook to spread the * SSL_SESSION also to the inter-process disk-cache to make share it with our * other Apache pre-forked server processes. */ @@ -1079,16 +1630,17 @@ int ssl_callback_NewSessionCacheEntry(SSL *ssl, SSL_SESSION *pNew) server_rec *s; SSLSrvConfigRec *sc; long t; + BOOL rc; /* - * Get Apache context back through SSLeay context + * Get Apache context back through OpenSSL context */ conn = (conn_rec *)SSL_get_app_data(ssl); s = conn->server; sc = mySrvConfig(s); /* - * Set the timeout also for the internal SSLeay cache, because this way + * Set the timeout also for the internal OpenSSL cache, because this way * our inter-process cache is consulted only when it's really necessary. */ t = (SSL_get_time(pNew) + sc->nSessionCacheTimeout); @@ -1098,26 +1650,27 @@ int ssl_callback_NewSessionCacheEntry(SSL *ssl, SSL_SESSION *pNew) * Store the SSL_SESSION in the inter-process cache with the * same expire time, so it expires automatically there, too. */ - ssl_scache_store(s, pNew, t); + rc = ssl_scache_store(s, pNew, t); /* * Log this cache operation */ ssl_log(s, SSL_LOG_TRACE, "Inter-Process Session Cache: " - "request=SET id=%s timeout=%ds (session caching)", + "request=SET status=%s id=%s timeout=%ds (session caching)", + rc == TRUE ? "OK" : "BAD", ssl_scache_id2sz(pNew->session_id, pNew->session_id_length), t-time(NULL)); /* - * return 0 which means to SSLeay that the pNew is still + * return 0 which means to OpenSSL that the pNew is still * valid and was not freed by us with SSL_SESSION_free(). */ return 0; } /* - * This callback function is executed by SSLeay whenever a - * SSL_SESSION is looked up in the internal SSLeay cache and it + * This callback function is executed by OpenSSL whenever a + * SSL_SESSION is looked up in the internal OpenSSL cache and it * was not found. We use this to lookup the SSL_SESSION in the * inter-process disk-cache where it was perhaps stored by one * of our other Apache pre-forked server processes. @@ -1130,7 +1683,7 @@ SSL_SESSION *ssl_callback_GetSessionCacheEntry( SSL_SESSION *pSession; /* - * Get Apache context back through SSLeay context + * Get Apache context back through OpenSSL context */ conn = (conn_rec *)SSL_get_app_data(ssl); s = conn->server; @@ -1163,8 +1716,8 @@ SSL_SESSION *ssl_callback_GetSessionCacheEntry( } /* - * This callback function is executed by SSLeay whenever a - * SSL_SESSION is removed from the the internal SSLeay cache. + * This callback function is executed by OpenSSL whenever a + * SSL_SESSION is removed from the the internal OpenSSL cache. * We use this to remove the SSL_SESSION in the inter-process * disk-cache, too. */ @@ -1174,9 +1727,11 @@ void ssl_callback_DelSessionCacheEntry( server_rec *s; /* - * Get Apache context back through SSLeay context + * Get Apache context back through OpenSSL context */ s = (server_rec *)SSL_CTX_get_app_data(ctx); + if (s == NULL) /* on server shutdown Apache is already gone */ + return; /* * Remove the SSL_SESSION from the inter-process cache @@ -1195,9 +1750,9 @@ void ssl_callback_DelSessionCacheEntry( } /* - * This callback function is executed while SSLeay processes the + * This callback function is executed while OpenSSL processes the * SSL handshake and does SSL record layer stuff. We use it to - * trace SSLeay's processing in out SSL logfile. + * trace OpenSSL's processing in out SSL logfile. */ void ssl_callback_LogTracingState(SSL *ssl, int where, int rc) { @@ -1224,26 +1779,27 @@ void ssl_callback_LogTracingState(SSL *ssl, int where, int rc) else if (where & SSL_CB_HANDSHAKE_DONE) ssl_log(s, SSL_LOG_TRACE, "%s: Handshake: done", SSL_LIBRARY_NAME); else if (where & SSL_CB_LOOP) - ssl_log(s, SSL_LOG_TRACE, "%s: Loop: %s", + ssl_log(s, SSL_LOG_TRACE, "%s: Loop: %s", SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); else if (where & SSL_CB_READ) ssl_log(s, SSL_LOG_TRACE, "%s: Read: %s", SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); else if (where & SSL_CB_WRITE) - ssl_log(s, SSL_LOG_TRACE, "%s: Write: %s", + ssl_log(s, SSL_LOG_TRACE, "%s: Write: %s", SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); else if (where & SSL_CB_ALERT) { str = (where & SSL_CB_READ) ? "read" : "write"; - ssl_log(s, SSL_LOG_TRACE, "%s: Alert: %s:%s\n", SSL_LIBRARY_NAME, + ssl_log(s, SSL_LOG_TRACE, "%s: Alert: %s:%s:%s\n", + SSL_LIBRARY_NAME, str, SSL_alert_type_string_long(rc), SSL_alert_desc_string_long(rc)); } else if (where & SSL_CB_EXIT) { if (rc == 0) - ssl_log(s, SSL_LOG_TRACE, "%s: Exit: failed in %s", + ssl_log(s, SSL_LOG_TRACE, "%s: Exit: failed in %s", SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); else if (rc < 0) - ssl_log(s, SSL_LOG_TRACE, "%s: Exit: error in %s", + ssl_log(s, SSL_LOG_TRACE, "%s: Exit: error in %s", SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); } } |
