diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-25 04:41:26 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-25 04:41:26 +0000 |
commit | 7b31d4f4901cdb89a79f2f7de4a6b8bb637b523b (patch) | |
tree | fdeb0b5ff80273f95ce61607fc3613dff0b9a235 /modules/ssl | |
parent | Adding upstream version 2.4.38. (diff) | |
download | apache2-upstream.tar.xz apache2-upstream.zip |
Adding upstream version 2.4.59.upstream/2.4.59upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/ssl')
-rw-r--r-- | modules/ssl/mod_ssl.c | 162 | ||||
-rw-r--r-- | modules/ssl/mod_ssl.h | 29 | ||||
-rw-r--r-- | modules/ssl/mod_ssl_openssl.h | 49 | ||||
-rw-r--r-- | modules/ssl/ssl_engine_config.c | 56 | ||||
-rw-r--r-- | modules/ssl/ssl_engine_init.c | 723 | ||||
-rw-r--r-- | modules/ssl/ssl_engine_io.c | 341 | ||||
-rw-r--r-- | modules/ssl/ssl_engine_kernel.c | 355 | ||||
-rw-r--r-- | modules/ssl/ssl_engine_log.c | 18 | ||||
-rw-r--r-- | modules/ssl/ssl_engine_ocsp.c | 3 | ||||
-rw-r--r-- | modules/ssl/ssl_engine_pphrase.c | 322 | ||||
-rw-r--r-- | modules/ssl/ssl_engine_vars.c | 52 | ||||
-rw-r--r-- | modules/ssl/ssl_private.h | 171 | ||||
-rw-r--r-- | modules/ssl/ssl_scache.c | 4 | ||||
-rw-r--r-- | modules/ssl/ssl_util.c | 51 | ||||
-rw-r--r-- | modules/ssl/ssl_util_ocsp.c | 6 | ||||
-rw-r--r-- | modules/ssl/ssl_util_ssl.c | 180 | ||||
-rw-r--r-- | modules/ssl/ssl_util_ssl.h | 31 | ||||
-rw-r--r-- | modules/ssl/ssl_util_stapling.c | 190 |
18 files changed, 1995 insertions, 748 deletions
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c index 9fdf9e0..fb66d18 100644 --- a/modules/ssl/mod_ssl.c +++ b/modules/ssl/mod_ssl.c @@ -25,8 +25,7 @@ */ #include "ssl_private.h" -#include "mod_ssl.h" -#include "mod_ssl_openssl.h" + #include "util_md5.h" #include "util_mutex.h" #include "ap_provider.h" @@ -75,11 +74,9 @@ static const command_rec ssl_config_cmds[] = { SSL_CMD_SRV(SessionCache, TAKE1, "SSL Session Cache storage " "('none', 'nonenotnull', 'dbm:/path/to/file')") -#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT) SSL_CMD_SRV(CryptoDevice, TAKE1, "SSL external Crypto Device usage " "('builtin', '...')") -#endif SSL_CMD_SRV(RandomSeed, TAKE23, "SSL Pseudo Random Number Generator (PRNG) seeding source " "('startup|connect builtin|file:/path|exec:/path [bytes]')") @@ -94,7 +91,7 @@ static const command_rec ssl_config_cmds[] = { "Enable FIPS-140 mode " "(`on', `off')") SSL_CMD_ALL(CipherSuite, TAKE12, - "Colon-delimited list of permitted SSL Ciphers, optional preceeded " + "Colon-delimited list of permitted SSL Ciphers, optional preceded " "by protocol identifier ('XXX:...:XXX' - see manual)") SSL_CMD_SRV(CertificateFile, TAKE1, "SSL Server Certificate file " @@ -187,7 +184,7 @@ static const command_rec ssl_config_cmds[] = { "('[+-][" SSL_PROTOCOLS "] ...' - see manual)") SSL_CMD_PXY(ProxyCipherSuite, TAKE12, "SSL Proxy: colon-delimited list of permitted SSL ciphers " - ", optionally preceeded by protocol specifier ('XXX:...:XXX' - see manual)") + ", optionally preceded by protocol specifier ('XXX:...:XXX' - see manual)") SSL_CMD_PXY(ProxyVerify, TAKE1, "SSL Proxy: whether to verify the remote certificate " "('on' or 'off')") @@ -328,12 +325,17 @@ static int modssl_is_prelinked(void) static apr_status_t ssl_cleanup_pre_config(void *data) { - /* - * Try to kill the internals of the SSL library. +#if HAVE_OPENSSL_INIT_SSL || (OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) + /* Openssl v1.1+ handles all termination automatically from + * OPENSSL_init_ssl(). Do nothing in this case. */ -#ifdef HAVE_FIPS - FIPS_mode_set(0); -#endif + +#else + /* Termination below is for legacy Openssl versions v1.0.x and + * older. + */ + /* Corresponds to OBJ_create()s */ OBJ_cleanup(); /* Corresponds to OPENSSL_load_builtin_modules() */ @@ -373,12 +375,14 @@ static apr_status_t ssl_cleanup_pre_config(void *data) if (!modssl_running_statically) { CRYPTO_cleanup_all_ex_data(); } +#endif /* * TODO: determine somewhere we can safely shove out diagnostics * (when enabled) at this late stage in the game: * CRYPTO_mem_leaks_fp(stderr); */ + return APR_SUCCESS; } @@ -388,16 +392,23 @@ static int ssl_hook_pre_config(apr_pool_t *pconf, { modssl_running_statically = modssl_is_prelinked(); - /* Some OpenSSL internals are allocated per-thread, make sure they - * are associated to the/our same thread-id until cleaned up. +#if HAVE_OPENSSL_INIT_SSL || (OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) + /* Openssl v1.1+ handles all initialisation automatically, apart + * from hints as to how we want to use the library. + * + * We tell openssl we want to include engine support. + */ + OPENSSL_init_ssl(OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL); + +#else + /* Configuration below is for legacy versions Openssl v1.0 and + * older. */ + #if APR_HAS_THREADS && MODSSL_USE_OPENSSL_PRE_1_1_API ssl_util_thread_id_setup(pconf); #endif - - /* We must register the library in full, to ensure our configuration - * code can successfully test the SSL environment. - */ #if MODSSL_USE_OPENSSL_PRE_1_1_API || defined(LIBRESSL_VERSION_NUMBER) (void)CRYPTO_malloc_init(); #else @@ -411,6 +422,7 @@ static int ssl_hook_pre_config(apr_pool_t *pconf, #endif OpenSSL_add_all_algorithms(); OPENSSL_load_builtin_modules(); +#endif if (OBJ_txt2nid("id-on-dnsSRV") == NID_undef) { (void)OBJ_create("1.3.6.1.5.5.7.8.7", "id-on-dnsSRV", @@ -445,17 +457,30 @@ static int ssl_hook_pre_config(apr_pool_t *pconf, } static SSLConnRec *ssl_init_connection_ctx(conn_rec *c, - ap_conf_vector_t *per_dir_config) + ap_conf_vector_t *per_dir_config, + int reinit) { SSLConnRec *sslconn = myConnConfig(c); - SSLSrvConfigRec *sc; - - if (sslconn) { + int need_setup = 0; + + /* mod_proxy's (r->)per_dir_config has the lifetime of the request, thus + * it uses ssl_engine_set() to reset sslconn->dc when reusing SSL backend + * connections, so we must fall through here. But in the case where we are + * called from ssl_init_ssl_connection() with no per_dir_config (which also + * includes mod_proxy's later run_pre_connection call), sslconn->dc should + * be preserved if it's already set. + */ + if (!sslconn) { + sslconn = apr_pcalloc(c->pool, sizeof(*sslconn)); + need_setup = 1; + } + else if (!reinit) { return sslconn; } - sslconn = apr_pcalloc(c->pool, sizeof(*sslconn)); - + /* Reinit dc in any case because it may be r->per_dir_config scoped + * and thus a caller like mod_proxy needs to update it per request. + */ if (per_dir_config) { sslconn->dc = ap_get_module_config(per_dir_config, &ssl_module); } @@ -464,12 +489,19 @@ static SSLConnRec *ssl_init_connection_ctx(conn_rec *c, &ssl_module); } - sslconn->server = c->base_server; - sslconn->verify_depth = UNSET; - sc = mySrvConfig(c->base_server); - sslconn->cipher_suite = sc->server->auth.cipher_suite; + if (need_setup) { + sslconn->server = c->base_server; + sslconn->verify_depth = UNSET; + if (c->outgoing) { + sslconn->cipher_suite = sslconn->dc->proxy->auth.cipher_suite; + } + else { + SSLSrvConfigRec *sc = mySrvConfig(c->base_server); + sslconn->cipher_suite = sc->server->auth.cipher_suite; + } - myConnConfigSet(c, sslconn); + myConnConfigSet(c, sslconn); + } return sslconn; } @@ -480,10 +512,11 @@ static int ssl_engine_status(conn_rec *c, SSLConnRec *sslconn) return DECLINED; } if (sslconn) { + /* This connection has already been configured. Check what applies. */ if (sslconn->disabled) { return SUSPENDED; } - if (sslconn->is_proxy) { + if (c->outgoing) { if (!sslconn->dc->proxy_enabled) { return DECLINED; } @@ -495,54 +528,48 @@ static int ssl_engine_status(conn_rec *c, SSLConnRec *sslconn) } } else { - if (mySrvConfig(c->base_server)->enabled != SSL_ENABLED_TRUE) { + /* we decline by default for outgoing connections and for incoming + * where the base_server is not enabled. */ + if (c->outgoing || mySrvConfig(c->base_server)->enabled != SSL_ENABLED_TRUE) { return DECLINED; } } return OK; } -static int ssl_engine_set(conn_rec *c, - ap_conf_vector_t *per_dir_config, - int proxy, int enable) +static int ssl_hook_ssl_bind_outgoing(conn_rec *c, + ap_conf_vector_t *per_dir_config, + int enable_ssl) { SSLConnRec *sslconn; int status; - - if (proxy) { - sslconn = ssl_init_connection_ctx(c, per_dir_config); - sslconn->is_proxy = 1; - } - else { - sslconn = myConnConfig(c); + + sslconn = ssl_init_connection_ctx(c, per_dir_config, 1); + if (sslconn->ssl) { + /* we are already bound to this connection. We have rebound + * or removed the reference to a previous per_dir_config, + * there is nothing more to do. */ + return OK; } status = ssl_engine_status(c, sslconn); - - if (proxy && status == DECLINED) { - if (enable) { + if (enable_ssl) { + if (status != OK) { SSLSrvConfigRec *sc = mySrvConfig(sslconn->server); - ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01961) - "SSL Proxy requested for %s but not enabled " - "[Hint: SSLProxyEngine]", sc->vhost_id); + sslconn->disabled = 1; + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10272) + "SSL Proxy requested for %s but not enabled for us.", + sc->vhost_id); + } + else { + sslconn->disabled = 0; + return OK; } - sslconn->disabled = 1; } - else if (sslconn) { - sslconn->disabled = !enable; + else { + sslconn->disabled = 1; } - - return status != DECLINED; -} - -static int ssl_proxy_enable(conn_rec *c) -{ - return ssl_engine_set(c, NULL, 1, 1); -} - -static int ssl_engine_disable(conn_rec *c) -{ - return ssl_engine_set(c, NULL, 0, 0); + return DECLINED; } int ssl_init_ssl_connection(conn_rec *c, request_rec *r) @@ -558,7 +585,7 @@ int ssl_init_ssl_connection(conn_rec *c, request_rec *r) /* * Create or retrieve SSL context */ - sslconn = ssl_init_connection_ctx(c, r ? r->per_dir_config : NULL); + sslconn = ssl_init_connection_ctx(c, r ? r->per_dir_config : NULL, 0); server = sslconn->server; sc = mySrvConfig(server); @@ -566,9 +593,9 @@ int ssl_init_ssl_connection(conn_rec *c, request_rec *r) * Seed the Pseudo Random Number Generator (PRNG) */ ssl_rand_seed(server, c->pool, SSL_RSCTX_CONNECT, - sslconn->is_proxy ? "Proxy: " : "Server: "); + c->outgoing ? "Proxy: " : "Server: "); - mctx = myCtxConfig(sslconn, sc); + mctx = myConnCtxConfig(c, sc); /* * Create a new SSL connection with the configured server SSL context and @@ -586,7 +613,7 @@ int ssl_init_ssl_connection(conn_rec *c, request_rec *r) return DECLINED; /* XXX */ } - rc = ssl_run_pre_handshake(c, ssl, sslconn->is_proxy ? 1 : 0); + rc = ssl_run_pre_handshake(c, ssl, c->outgoing ? 1 : 0); if (rc != OK && rc != DECLINED) { return rc; } @@ -718,10 +745,7 @@ static void ssl_register_hooks(apr_pool_t *p) APR_HOOK_MIDDLE); ssl_var_register(p); - - APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable); - APR_REGISTER_OPTIONAL_FN(ssl_engine_disable); - APR_REGISTER_OPTIONAL_FN(ssl_engine_set); + ap_hook_ssl_bind_outgoing (ssl_hook_ssl_bind_outgoing, NULL, NULL, APR_HOOK_MIDDLE); ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl", AUTHZ_PROVIDER_VERSION, diff --git a/modules/ssl/mod_ssl.h b/modules/ssl/mod_ssl.h index 24a65a0..a360911 100644 --- a/modules/ssl/mod_ssl.h +++ b/modules/ssl/mod_ssl.h @@ -29,6 +29,7 @@ #include "httpd.h" #include "http_config.h" #include "apr_optional.h" +#include "apr_tables.h" /* for apr_array_header_t */ /* Create a set of SSL_DECLARE(type), SSL_DECLARE_NONSTD(type) and * SSL_DECLARE_DATA with appropriate export and import tags for the platform @@ -86,6 +87,34 @@ APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *)); APR_DECLARE_OPTIONAL_FN(int, ssl_engine_set, (conn_rec *, ap_conf_vector_t *, int proxy, int enable)); + +/* Check for availability of new hooks */ +#define SSL_CERT_HOOKS +#ifdef SSL_CERT_HOOKS + +/** Lets others add certificate and key files to the given server. + * For each cert a key must also be added. + * @param cert_file and array of const char* with the path to the certificate chain + * @param key_file and array of const char* with the path to the private key file + */ +APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, add_cert_files, + (server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files)) + +/** In case no certificates are available for a server, this + * lets other modules add a fallback certificate for the time + * being. Regular requests against this server will be answered + * with a 503. + * @param cert_file and array of const char* with the path to the certificate chain + * @param key_file and array of const char* with the path to the private key file + */ +APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, add_fallback_cert_files, + (server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files)) + +#endif /* SSL_CERT_HOOKS */ #endif /* __MOD_SSL_H__ */ /** @} */ diff --git a/modules/ssl/mod_ssl_openssl.h b/modules/ssl/mod_ssl_openssl.h index 0fa654a..e251bd9 100644 --- a/modules/ssl/mod_ssl_openssl.h +++ b/modules/ssl/mod_ssl_openssl.h @@ -30,14 +30,17 @@ /* OpenSSL headers */ -#ifndef SSL_PRIVATE_H #include <openssl/opensslv.h> -#if (OPENSSL_VERSION_NUMBER >= 0x10001000) +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#include <openssl/macros.h> /* for OPENSSL_API_LEVEL */ +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10001000 /* must be defined before including ssl.h */ #define OPENSSL_NO_SSL_INTERN #endif #include <openssl/ssl.h> -#endif +#include <openssl/evp.h> +#include <openssl/x509.h> /** * init_server hook -- allow SSL_CTX-specific initialization to be performed by @@ -69,5 +72,45 @@ APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, pre_handshake, APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, proxy_post_handshake, (conn_rec *c, SSL *ssl)) +/** On TLS connections that do not relate to a configured virtual host, + * allow other modules to provide a X509 certificate and EVP_PKEY to + * be used on the connection. This first hook which does not + * return DECLINED will determine the outcome. */ +APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, answer_challenge, + (conn_rec *c, const char *server_name, + X509 **pcert, EVP_PKEY **pkey)) + +/** During post_config phase, ask around if someone wants to provide + * OCSP stapling status information for the given cert (with the also + * provided issuer certificate). The first hook which does not + * return DECLINED promises to take responsibility (and respond + * in later calls via hook ssl_get_stapling_status). + * If no hook takes over, mod_ssl's own stapling implementation will + * be applied (if configured). + */ +APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, init_stapling_status, + (server_rec *s, apr_pool_t *p, + X509 *cert, X509 *issuer)) + +/** Anyone answering positive to ssl_init_stapling_status for a + * certificate, needs to register here and supply the actual OCSP stapling + * status data (OCSP_RESP) for a new connection. + * A hook supplying the response data must return APR_SUCCESS. + * The data is returned in DER encoded bytes via pder and pderlen. The + * returned pointer may be NULL, which indicates that data is (currently) + * unavailable. + * If DER data is returned, it MUST come from a response with + * status OCSP_RESPONSE_STATUS_SUCCESSFUL and V_OCSP_CERTSTATUS_GOOD + * or V_OCSP_CERTSTATUS_REVOKED, not V_OCSP_CERTSTATUS_UNKNOWN. This means + * errors in OCSP retrieval are to be handled/logged by the hook and + * are not done by mod_ssl. + * Any DER bytes returned MUST be allocated via malloc() and ownership + * passes to mod_ssl. Meaning, the hook must return a malloced copy of + * the data it has. mod_ssl (or OpenSSL) will free it. + */ +APR_DECLARE_EXTERNAL_HOOK(ssl, SSL, int, get_stapling_status, + (unsigned char **pder, int *pderlen, + conn_rec *c, server_rec *s, X509 *cert)) + #endif /* __MOD_SSL_OPENSSL_H__ */ /** @} */ diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c index 6c10bb5..9af6f70 100644 --- a/modules/ssl/ssl_engine_config.c +++ b/modules/ssl/ssl_engine_config.c @@ -27,6 +27,7 @@ damned if you don't.'' -- Unknown */ #include "ssl_private.h" + #include "util_mutex.h" #include "ap_provider.h" @@ -75,6 +76,13 @@ SSLModConfigRec *ssl_config_global_create(server_rec *s) mc->stapling_refresh_mutex = NULL; #endif +#ifdef HAVE_OPENSSL_KEYLOG + mc->keylog_file = NULL; +#endif +#ifdef HAVE_FIPS + mc->fips = UNSET; +#endif + apr_pool_userdata_set(mc, SSL_MOD_CONFIG_KEY, apr_pool_cleanup_null, pool); @@ -220,9 +228,6 @@ static SSLSrvConfigRec *ssl_config_server_new(apr_pool_t *p) #ifdef HAVE_TLSEXT sc->strict_sni_vhost_check = SSL_ENABLED_UNSET; #endif -#ifdef HAVE_FIPS - sc->fips = UNSET; -#endif #ifndef OPENSSL_NO_COMP sc->compression = UNSET; #endif @@ -261,9 +266,11 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p, modssl_ctx_t *mrg) { if (add->protocol_set) { + mrg->protocol_set = 1; mrg->protocol = add->protocol; } else { + mrg->protocol_set = base->protocol_set; mrg->protocol = base->protocol; } @@ -393,9 +400,6 @@ void *ssl_config_server_merge(apr_pool_t *p, void *basev, void *addv) #ifdef HAVE_TLSEXT cfgMerge(strict_sni_vhost_check, SSL_ENABLED_UNSET); #endif -#ifdef HAVE_FIPS - cfgMergeBool(fips); -#endif #ifndef OPENSSL_NO_COMP cfgMergeBool(compression); #endif @@ -589,14 +593,15 @@ const char *ssl_cmd_SSLPassPhraseDialog(cmd_parms *cmd, return NULL; } -#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT) const char *ssl_cmd_SSLCryptoDevice(cmd_parms *cmd, void *dcfg, const char *arg) { SSLModConfigRec *mc = myModConfig(cmd->server); const char *err; +#if MODSSL_HAVE_ENGINE_API ENGINE *e; +#endif if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) { return err; @@ -605,13 +610,16 @@ const char *ssl_cmd_SSLCryptoDevice(cmd_parms *cmd, if (strcEQ(arg, "builtin")) { mc->szCryptoDevice = NULL; } +#if MODSSL_HAVE_ENGINE_API else if ((e = ENGINE_by_id(arg))) { mc->szCryptoDevice = arg; ENGINE_free(e); } +#endif else { err = "SSLCryptoDevice: Invalid argument; must be one of: " "'builtin' (none)"; +#if MODSSL_HAVE_ENGINE_API e = ENGINE_get_first(); while (e) { err = apr_pstrcat(cmd->pool, err, ", '", ENGINE_get_id(e), @@ -620,12 +628,12 @@ const char *ssl_cmd_SSLCryptoDevice(cmd_parms *cmd, * on the 'old' e, per the docs in engine.h. */ e = ENGINE_get_next(e); } +#endif return err; } return NULL; } -#endif const char *ssl_cmd_SSLRandomSeed(cmd_parms *cmd, void *dcfg, @@ -743,7 +751,7 @@ const char *ssl_cmd_SSLEngine(cmd_parms *cmd, void *dcfg, const char *arg) const char *ssl_cmd_SSLFIPS(cmd_parms *cmd, void *dcfg, int flag) { #ifdef HAVE_FIPS - SSLSrvConfigRec *sc = mySrvConfig(cmd->server); + SSLModConfigRec *mc = myModConfig(cmd->server); #endif const char *err; @@ -752,9 +760,9 @@ const char *ssl_cmd_SSLFIPS(cmd_parms *cmd, void *dcfg, int flag) } #ifdef HAVE_FIPS - if ((sc->fips != UNSET) && (sc->fips != (BOOL)(flag ? TRUE : FALSE))) + if ((mc->fips != UNSET) && (mc->fips != (BOOL)(flag ? TRUE : FALSE))) return "Conflicting SSLFIPS options, cannot be both On and Off"; - sc->fips = flag ? TRUE : FALSE; + mc->fips = flag ? TRUE : FALSE; #else if (flag) return "SSLFIPS invalid, rebuild httpd and openssl compiled for FIPS"; @@ -795,7 +803,7 @@ const char *ssl_cmd_SSLCipherSuite(cmd_parms *cmd, return NULL; } #endif - return apr_pstrcat(cmd->pool, "procotol '", arg1, "' not supported", NULL); + return apr_pstrcat(cmd->pool, "protocol '", arg1, "' not supported", NULL); } #define SSL_FLAGS_CHECK_FILE \ @@ -807,8 +815,14 @@ const char *ssl_cmd_SSLCipherSuite(cmd_parms *cmd, static const char *ssl_cmd_check_file(cmd_parms *parms, const char **file) { - const char *filepath = ap_server_root_relative(parms->pool, *file); + const char *filepath; + + /* If only dumping the config, don't verify the paths */ + if (ap_state_query(AP_SQ_RUN_MODE) == AP_SQ_RM_CONFIG_DUMP) { + return NULL; + } + filepath = ap_server_root_relative(parms->pool, *file); if (!filepath) { return apr_pstrcat(parms->pool, parms->cmd->name, ": Invalid file path ", *file, NULL); @@ -847,10 +861,12 @@ const char *ssl_cmd_SSLCompression(cmd_parms *cmd, void *dcfg, int flag) } } sc->compression = flag ? TRUE : FALSE; - return NULL; #else - return "Setting Compression mode unsupported; not implemented by the SSL library"; + if (flag) { + return "Setting Compression mode unsupported; not implemented by the SSL library"; + } #endif + return NULL; } const char *ssl_cmd_SSLHonorCipherOrder(cmd_parms *cmd, void *dcfg, int flag) @@ -916,7 +932,9 @@ const char *ssl_cmd_SSLCertificateFile(cmd_parms *cmd, SSLSrvConfigRec *sc = mySrvConfig(cmd->server); const char *err; - if ((err = ssl_cmd_check_file(cmd, &arg))) { + /* Only check for non-ENGINE based certs. */ + if (!modssl_is_engine_id(arg) + && (err = ssl_cmd_check_file(cmd, &arg))) { return err; } @@ -932,7 +950,9 @@ const char *ssl_cmd_SSLCertificateKeyFile(cmd_parms *cmd, SSLSrvConfigRec *sc = mySrvConfig(cmd->server); const char *err; - if ((err = ssl_cmd_check_file(cmd, &arg))) { + /* Check keyfile exists for non-ENGINE keys. */ + if (!modssl_is_engine_id(arg) + && (err = ssl_cmd_check_file(cmd, &arg))) { return err; } @@ -1549,7 +1569,7 @@ const char *ssl_cmd_SSLProxyCipherSuite(cmd_parms *cmd, return NULL; } #endif - return apr_pstrcat(cmd->pool, "procotol '", arg1, "' not supported", NULL); + return apr_pstrcat(cmd->pool, "protocol '", arg1, "' not supported", NULL); } const char *ssl_cmd_SSLProxyVerify(cmd_parms *cmd, diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index 18d18c6..c2ec048 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -27,15 +27,36 @@ see Recursive.'' -- Unknown */ #include "ssl_private.h" -#include "mod_ssl.h" -#include "mod_ssl_openssl.h" + #include "mpm_common.h" #include "mod_md.h" +static apr_status_t ssl_init_ca_cert_path(server_rec *, apr_pool_t *, const char *, + STACK_OF(X509_NAME) *, STACK_OF(X509_INFO) *); + APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, init_server, (server_rec *s,apr_pool_t *p,int is_proxy,SSL_CTX *ctx), (s,p,is_proxy,ctx), OK, DECLINED) +APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, add_cert_files, + (server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, apr_array_header_t *key_files), + (s, p, cert_files, key_files), + OK, DECLINED) + +APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, add_fallback_cert_files, + (server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, apr_array_header_t *key_files), + (s, p, cert_files, key_files), + OK, DECLINED) + +APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, answer_challenge, + (conn_rec *c, const char *server_name, + X509 **pcert, EVP_PKEY **pkey), + (c, server_name, pcert, pkey), + DECLINED, DECLINED) + + /* _________________________________________________________________ ** ** Module Initialization @@ -69,7 +90,6 @@ static int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) return 1; } -#endif /* * Grab well-defined DH parameters from OpenSSL, see the BN_get_rfc* @@ -149,40 +169,64 @@ DH *modssl_get_dh_params(unsigned keylen) return NULL; /* impossible to reach. */ } +#endif -static void ssl_add_version_components(apr_pool_t *p, +static void ssl_add_version_components(apr_pool_t *ptemp, apr_pool_t *pconf, server_rec *s) { - char *modver = ssl_var_lookup(p, s, NULL, NULL, "SSL_VERSION_INTERFACE"); - char *libver = ssl_var_lookup(p, s, NULL, NULL, "SSL_VERSION_LIBRARY"); - char *incver = ssl_var_lookup(p, s, NULL, NULL, + char *modver = ssl_var_lookup(ptemp, s, NULL, NULL, "SSL_VERSION_INTERFACE"); + char *libver = ssl_var_lookup(ptemp, s, NULL, NULL, "SSL_VERSION_LIBRARY"); + char *incver = ssl_var_lookup(ptemp, s, NULL, NULL, "SSL_VERSION_LIBRARY_INTERFACE"); - ap_add_version_component(p, libver); + ap_add_version_component(pconf, libver); ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01876) "%s compiled against Server: %s, Library: %s", modver, AP_SERVER_BASEVERSION, incver); } -/**************************************************************************************************/ -/* Managed Domains Interface */ - -static APR_OPTIONAL_FN_TYPE(md_is_managed) *md_is_managed; -static APR_OPTIONAL_FN_TYPE(md_get_certificate) *md_get_certificate; -static APR_OPTIONAL_FN_TYPE(md_is_challenge) *md_is_challenge; +/* _________________________________________________________________ +** +** Let other answer special connection attempts. +** Used in ACME challenge handling by mod_md. +** _________________________________________________________________ +*/ int ssl_is_challenge(conn_rec *c, const char *servername, - X509 **pcert, EVP_PKEY **pkey) + X509 **pcert, EVP_PKEY **pkey, + const char **pcert_pem, const char **pkey_pem) { - if (md_is_challenge) { - return md_is_challenge(c, servername, pcert, pkey); - } *pcert = NULL; *pkey = NULL; + *pcert_pem = *pkey_pem = NULL; + if (ap_ssl_answer_challenge(c, servername, pcert_pem, pkey_pem)) { + return 1; + } + else if (OK == ssl_run_answer_challenge(c, servername, pcert, pkey)) { + return 1; + } return 0; } +#ifdef HAVE_FIPS +static apr_status_t modssl_fips_cleanup(void *data) +{ + modssl_fips_enable(0); + return APR_SUCCESS; +} +#endif + +static APR_INLINE unsigned long modssl_runtime_lib_version(void) +{ +#if MODSSL_USE_OPENSSL_PRE_1_1_API + return SSLeay(); +#else + return OpenSSL_version_num(); +#endif +} + + /* * Per-module initialization */ @@ -190,18 +234,22 @@ apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *base_server) { + unsigned long runtime_lib_version = modssl_runtime_lib_version(); SSLModConfigRec *mc = myModConfig(base_server); SSLSrvConfigRec *sc; server_rec *s; apr_status_t rv; apr_array_header_t *pphrases; - if (SSLeay() < MODSSL_LIBRARY_VERSION) { + AP_DEBUG_ASSERT(mc); + + if (runtime_lib_version < MODSSL_LIBRARY_VERSION) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(01882) "Init: this version of mod_ssl was compiled against " - "a newer library (%s, version currently loaded is %s)" + "a newer library (%s (%s), version currently loaded is 0x%lX)" " - may result in undefined or erroneous behavior", - MODSSL_LIBRARY_TEXT, MODSSL_LIBRARY_DYNTEXT); + MODSSL_LIBRARY_TEXT, MODSSL_LIBRARY_DYNTEXT, + runtime_lib_version); } /* We initialize mc->pid per-process in the child init, @@ -223,16 +271,6 @@ apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog, ssl_config_global_create(base_server); /* just to avoid problems */ ssl_config_global_fix(mc); - /* Initialize our interface to mod_md, if it is loaded - */ - md_is_managed = APR_RETRIEVE_OPTIONAL_FN(md_is_managed); - md_get_certificate = APR_RETRIEVE_OPTIONAL_FN(md_get_certificate); - md_is_challenge = APR_RETRIEVE_OPTIONAL_FN(md_is_challenge); - if (!md_is_managed || !md_get_certificate) { - md_is_managed = NULL; - md_get_certificate = NULL; - } - /* * try to fix the configuration and open the dedicated SSL * logfile as early as possible @@ -279,12 +317,6 @@ apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog, if (sc->server && sc->server->pphrase_dialog_type == SSL_PPTYPE_UNSET) { sc->server->pphrase_dialog_type = SSL_PPTYPE_BUILTIN; } - -#ifdef HAVE_FIPS - if (sc->fips == UNSET) { - sc->fips = FALSE; - } -#endif } #if APR_HAS_THREADS && MODSSL_USE_OPENSSL_PRE_1_1_API @@ -294,13 +326,11 @@ apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog, /* * SSL external crypto device ("engine") support */ -#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT) if ((rv = ssl_init_Engine(base_server, p)) != APR_SUCCESS) { return rv; } -#endif - ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01883) + ap_log_error(APLOG_MARK, APLOG_INFO, 0, base_server, APLOGNO(01883) "Init: Initialized %s library", MODSSL_LIBRARY_NAME); /* @@ -311,22 +341,28 @@ apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog, ssl_rand_seed(base_server, ptemp, SSL_RSCTX_STARTUP, "Init: "); #ifdef HAVE_FIPS - if(sc->fips) { - if (!FIPS_mode()) { - if (FIPS_mode_set(1)) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(01884) - "Operating in SSL FIPS mode"); - } - else { - ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01885) "FIPS mode failed"); - ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); - return ssl_die(s); - } + if (!modssl_fips_is_enabled() && mc->fips == TRUE) { + if (!modssl_fips_enable(1)) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, base_server, APLOGNO(01885) + "Could not enable FIPS mode"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, base_server); + return ssl_die(base_server); } + + apr_pool_cleanup_register(p, NULL, modssl_fips_cleanup, + apr_pool_cleanup_null); + } + + /* Log actual FIPS mode which the SSL library is operating under, + * which may have been set outside of the mod_ssl + * configuration. */ + if (modssl_fips_is_enabled()) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, base_server, APLOGNO(01884) + MODSSL_LIBRARY_NAME " has FIPS mode enabled"); } else { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01886) - "SSL FIPS mode disabled"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(01886) + MODSSL_LIBRARY_NAME " has FIPS mode disabled"); } #endif @@ -409,25 +445,48 @@ apr_status_t ssl_init_Module(apr_pool_t *p, apr_pool_t *plog, * Announce mod_ssl and SSL library in HTTP Server field * as ``mod_ssl/X.X.X OpenSSL/X.X.X'' */ - ssl_add_version_components(p, base_server); + ssl_add_version_components(ptemp, p, base_server); modssl_init_app_data2_idx(); /* for modssl_get_app_data2() at request time */ +#if MODSSL_USE_OPENSSL_PRE_1_1_API init_dh_params(); -#if !MODSSL_USE_OPENSSL_PRE_1_1_API +#else init_bio_methods(); #endif +#ifdef HAVE_OPENSSL_KEYLOG + { + const char *logfn = getenv("SSLKEYLOGFILE"); + + if (logfn) { + rv = apr_file_open(&mc->keylog_file, logfn, + APR_FOPEN_CREATE|APR_FOPEN_WRITE|APR_FOPEN_APPEND|APR_FOPEN_LARGEFILE, + APR_FPROT_UREAD|APR_FPROT_UWRITE, + mc->pPool); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, s, APLOGNO(10226) + "Could not open log file '%s' configured via SSLKEYLOGFILE", + logfn); + return rv; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(10227) + "Init: Logging SSL private key material to %s", logfn); + } + } +#endif + return OK; } /* * Support for external a Crypto Device ("engine"), usually - * a hardware accellerator card for crypto operations. + * a hardware accelerator card for crypto operations. */ -#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT) apr_status_t ssl_init_Engine(server_rec *s, apr_pool_t *p) { +#if MODSSL_HAVE_ENGINE_API SSLModConfigRec *mc = myModConfig(s); ENGINE *e; @@ -459,10 +518,9 @@ apr_status_t ssl_init_Engine(server_rec *s, apr_pool_t *p) ENGINE_free(e); } - +#endif return APR_SUCCESS; } -#endif #ifdef HAVE_TLSEXT static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s, @@ -479,7 +537,9 @@ static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s, "Configuring TLS extension handling"); /* - * Server name indication (SNI) + * The Server Name Indication (SNI) provided by the ClientHello can be + * used to select the right (name-based-)vhost and its SSL configuration + * before the handshake takes place. */ if (!SSL_CTX_set_tlsext_servername_callback(mctx->ssl_ctx, ssl_callback_ServerNameIndication) || @@ -491,6 +551,16 @@ static apr_status_t ssl_init_ctx_tls_extensions(server_rec *s, return ssl_die(s); } +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) + /* + * The ClientHello callback also allows to retrieve the SNI, but since it + * runs at the earliest possible connection stage we can even set the TLS + * protocol version(s) according to the selected (name-based-)vhost, which + * is not possible at the SNI callback stage (due to OpenSSL internals). + */ + SSL_CTX_set_client_hello_cb(mctx->ssl_ctx, ssl_callback_ClientHello, NULL); +#endif + #ifdef HAVE_OCSP_STAPLING /* * OCSP Stapling support, status_request extension @@ -659,9 +729,9 @@ static apr_status_t ssl_init_ctx_protocol(server_rec *s, #else /* #if OPENSSL_VERSION_NUMBER < 0x10100000L */ /* We first determine the maximum protocol version we should provide */ #if SSL_HAVE_PROTOCOL_TLSV1_3 - if (SSL_HAVE_PROTOCOL_TLSV1_3 && (protocol & SSL_PROTOCOL_TLSV1_3)) { + if (protocol & SSL_PROTOCOL_TLSV1_3) { prot = TLS1_3_VERSION; - } else + } else #endif if (protocol & SSL_PROTOCOL_TLSV1_2) { prot = TLS1_2_VERSION; @@ -767,6 +837,20 @@ static apr_status_t ssl_init_ctx_protocol(server_rec *s, * https://github.com/openssl/openssl/issues/7178 */ SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY); #endif + +#ifdef HAVE_OPENSSL_KEYLOG + if (mctx->sc->mc->keylog_file) { + SSL_CTX_set_keylog_callback(ctx, modssl_callback_keylog); + } +#endif + +#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF + /* For server-side SSL_CTX, enable ignoring unexpected EOF */ + /* (OpenSSL 1.1.1 behavioural compatibility).. */ + if (!mctx->pkp) { + SSL_CTX_set_options(ctx, SSL_OP_IGNORE_UNEXPECTED_EOF); + } +#endif return APR_SUCCESS; } @@ -795,7 +879,11 @@ static void ssl_init_ctx_callbacks(server_rec *s, { SSL_CTX *ctx = mctx->ssl_ctx; +#if MODSSL_USE_OPENSSL_PRE_1_1_API + /* Note that for OpenSSL>=1.1, auto selection is enabled via + * SSL_CTX_set_dh_auto(,1) if no parameter is configured. */ SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH); +#endif SSL_CTX_set_info_callback(ctx, ssl_callback_Info); @@ -804,6 +892,23 @@ static void ssl_init_ctx_callbacks(server_rec *s, #endif } +static APR_INLINE +int modssl_CTX_load_verify_locations(SSL_CTX *ctx, + const char *file, + const char *path) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (!SSL_CTX_load_verify_locations(ctx, file, path)) + return 0; +#else + if (file && !SSL_CTX_load_verify_file(ctx, file)) + return 0; + if (path && !SSL_CTX_load_verify_dir(ctx, path)) + return 0; +#endif + return 1; +} + static apr_status_t ssl_init_ctx_verify(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, @@ -844,10 +949,8 @@ static apr_status_t ssl_init_ctx_verify(server_rec *s, ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "Configuring client authentication"); - if (!SSL_CTX_load_verify_locations(ctx, - mctx->auth.ca_cert_file, - mctx->auth.ca_cert_path)) - { + if (!modssl_CTX_load_verify_locations(ctx, mctx->auth.ca_cert_file, + mctx->auth.ca_cert_path)) { ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01895) "Unable to configure verify locations " "for client authentication"); @@ -932,6 +1035,23 @@ static apr_status_t ssl_init_ctx_cipher_suite(server_rec *s, return APR_SUCCESS; } +static APR_INLINE +int modssl_X509_STORE_load_locations(X509_STORE *store, + const char *file, + const char *path) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (!X509_STORE_load_locations(store, file, path)) + return 0; +#else + if (file && !X509_STORE_load_file(store, file)) + return 0; + if (path && !X509_STORE_load_path(store, path)) + return 0; +#endif + return 1; +} + static apr_status_t ssl_init_ctx_crl(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, @@ -970,8 +1090,8 @@ static apr_status_t ssl_init_ctx_crl(server_rec *s, ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01900) "Configuring certificate revocation facility"); - if (!store || !X509_STORE_load_locations(store, mctx->crl_file, - mctx->crl_path)) { + if (!store || !modssl_X509_STORE_load_locations(store, mctx->crl_file, + mctx->crl_path)) { ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01901) "Host %s: unable to configure X.509 CRL storage " "for certificate revocation", mctx->sc->vhost_id); @@ -1200,6 +1320,22 @@ static int ssl_no_passwd_prompt_cb(char *buf, int size, int rwflag, return 0; } +/* SSL_CTX_use_PrivateKey_file() can fail either because the private + * key was encrypted, or due to a mismatch between an already-loaded + * cert and the key - a common misconfiguration - from calling + * X509_check_private_key(). This macro is passed the last error code + * off the OpenSSL stack and evaluates to true only for the first + * case. With OpenSSL < 3 the second case is identifiable by the + * function code, but function codes are not used from 3.0. */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#define CHECK_PRIVKEY_ERROR(ec) (ERR_GET_FUNC(ec) != X509_F_X509_CHECK_PRIVATE_KEY) +#else +#define CHECK_PRIVKEY_ERROR(ec) (ERR_GET_LIB(ec) != ERR_LIB_X509 \ + || (ERR_GET_REASON(ec) != X509_R_KEY_TYPE_MISMATCH \ + && ERR_GET_REASON(ec) != X509_R_KEY_VALUES_MISMATCH \ + && ERR_GET_REASON(ec) != X509_R_UNKNOWN_KEY_TYPE)) +#endif + static apr_status_t ssl_init_server_certs(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, @@ -1209,15 +1345,10 @@ static apr_status_t ssl_init_server_certs(server_rec *s, SSLModConfigRec *mc = myModConfig(s); const char *vhost_id = mctx->sc->vhost_id, *key_id, *certfile, *keyfile; int i; - X509 *cert; - DH *dhparams; + EVP_PKEY *pkey; #ifdef HAVE_ECC - EC_GROUP *ecparams = NULL; - int nid; - EC_KEY *eckey = NULL; -#endif -#ifndef HAVE_SSL_CONF_CMD - SSL *ssl; + EC_GROUP *ecgroup = NULL; + int curve_nid = 0; #endif /* no OpenSSL default prompts for any of the SSL_CTX_use_* calls, please */ @@ -1228,12 +1359,18 @@ static apr_status_t ssl_init_server_certs(server_rec *s, (certfile = APR_ARRAY_IDX(mctx->pks->cert_files, i, const char *)); i++) { + X509 *cert = NULL; + const char *engine_certfile = NULL; + key_id = apr_psprintf(ptemp, "%s:%d", vhost_id, i); ERR_clear_error(); /* first the certificate (public key) */ - if (mctx->cert_chain) { + if (modssl_is_engine_id(certfile)) { + engine_certfile = certfile; + } + else if (mctx->cert_chain) { if ((SSL_CTX_use_certificate_file(mctx->ssl_ctx, certfile, SSL_FILETYPE_PEM) < 1)) { ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02561) @@ -1262,12 +1399,43 @@ static apr_status_t ssl_init_server_certs(server_rec *s, ERR_clear_error(); - if ((SSL_CTX_use_PrivateKey_file(mctx->ssl_ctx, keyfile, - SSL_FILETYPE_PEM) < 1) && - (ERR_GET_FUNC(ERR_peek_last_error()) - != X509_F_X509_CHECK_PRIVATE_KEY)) { + if (modssl_is_engine_id(keyfile)) { + apr_status_t rv; + + if ((rv = modssl_load_engine_keypair(s, ptemp, vhost_id, + engine_certfile, keyfile, + &cert, &pkey))) { + return rv; + } + + if (cert) { + if (SSL_CTX_use_certificate(mctx->ssl_ctx, cert) < 1) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10137) + "Failed to configure engine certificate %s, check %s", + key_id, certfile); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); + return APR_EGENERAL; + } + + /* SSL_CTX now owns the cert. */ + X509_free(cert); + } + + if (SSL_CTX_use_PrivateKey(mctx->ssl_ctx, pkey) < 1) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10130) + "Failed to configure private key %s from engine", + keyfile); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); + return APR_EGENERAL; + } + + /* SSL_CTX now owns the key */ + EVP_PKEY_free(pkey); + } + else if ((SSL_CTX_use_PrivateKey_file(mctx->ssl_ctx, keyfile, + SSL_FILETYPE_PEM) < 1) + && CHECK_PRIVKEY_ERROR(ERR_peek_last_error())) { ssl_asn1_t *asn1; - EVP_PKEY *pkey; const unsigned char *ptr; ERR_clear_error(); @@ -1304,22 +1472,21 @@ static apr_status_t ssl_init_server_certs(server_rec *s, * assume that if SSL_CONF is available, it's OpenSSL 1.0.2 or later, * and SSL_CTX_get0_certificate is implemented.) */ - if (!(cert = SSL_CTX_get0_certificate(mctx->ssl_ctx))) { + cert = SSL_CTX_get0_certificate(mctx->ssl_ctx); #else - ssl = SSL_new(mctx->ssl_ctx); - if (ssl) { - /* Workaround bug in SSL_get_certificate in OpenSSL 0.9.8y */ - SSL_set_connect_state(ssl); - cert = SSL_get_certificate(ssl); + { + SSL *ssl = SSL_new(mctx->ssl_ctx); + if (ssl) { + /* Workaround bug in SSL_get_certificate in OpenSSL 0.9.8y */ + SSL_set_connect_state(ssl); + cert = SSL_get_certificate(ssl); + SSL_free(ssl); + } } - if (!ssl || !cert) { #endif + if (!cert) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02566) "Unable to retrieve certificate %s", key_id); -#ifndef HAVE_SSL_CONF_CMD - if (ssl) - SSL_free(ssl); -#endif return APR_EGENERAL; } @@ -1334,18 +1501,13 @@ static apr_status_t ssl_init_server_certs(server_rec *s, * loaded via SSLOpenSSLConfCmd Certificate), so for 1.0.2 and * later, we defer to the code in ssl_init_server_ctx. */ - if ((mctx->stapling_enabled == TRUE) && - !ssl_stapling_init_cert(s, p, ptemp, mctx, cert)) { + if (!ssl_stapling_init_cert(s, p, ptemp, mctx, cert)) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(02567) "Unable to configure certificate %s for stapling", key_id); } #endif -#ifndef HAVE_SSL_CONF_CMD - SSL_free(ssl); -#endif - ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02568) "Certificate and private key %s configured from %s and %s", key_id, certfile, keyfile); @@ -1354,27 +1516,69 @@ static apr_status_t ssl_init_server_certs(server_rec *s, /* * Try to read DH parameters from the (first) SSLCertificateFile */ - if ((certfile = APR_ARRAY_IDX(mctx->pks->cert_files, 0, const char *)) && - (dhparams = ssl_dh_GetParamFromFile(certfile))) { - SSL_CTX_set_tmp_dh(mctx->ssl_ctx, dhparams); - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02540) - "Custom DH parameters (%d bits) for %s loaded from %s", - DH_bits(dhparams), vhost_id, certfile); - DH_free(dhparams); + certfile = APR_ARRAY_IDX(mctx->pks->cert_files, 0, const char *); + if (certfile && !modssl_is_engine_id(certfile)) { + int done = 0, num_bits = 0; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DH *dh = modssl_dh_from_file(certfile); + if (dh) { + num_bits = DH_bits(dh); + SSL_CTX_set_tmp_dh(mctx->ssl_ctx, dh); + DH_free(dh); + done = 1; + } +#else + pkey = modssl_dh_pkey_from_file(certfile); + if (pkey) { + num_bits = EVP_PKEY_get_bits(pkey); + if (!SSL_CTX_set0_tmp_dh_pkey(mctx->ssl_ctx, pkey)) { + EVP_PKEY_free(pkey); + } + else { + done = 1; + } + } +#endif + if (done) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02540) + "Custom DH parameters (%d bits) for %s loaded from %s", + num_bits, vhost_id, certfile); + } + } +#if !MODSSL_USE_OPENSSL_PRE_1_1_API + else { + /* If no parameter is manually configured, enable auto + * selection. */ + SSL_CTX_set_dh_auto(mctx->ssl_ctx, 1); } +#endif #ifdef HAVE_ECC /* * Similarly, try to read the ECDH curve name from SSLCertificateFile... */ - if ((certfile != NULL) && - (ecparams = ssl_ec_GetParamFromFile(certfile)) && - (nid = EC_GROUP_get_curve_name(ecparams)) && - (eckey = EC_KEY_new_by_curve_name(nid))) { - SSL_CTX_set_tmp_ecdh(mctx->ssl_ctx, eckey); - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02541) - "ECDH curve %s for %s specified in %s", - OBJ_nid2sn(nid), vhost_id, certfile); + if (certfile && !modssl_is_engine_id(certfile) + && (ecgroup = modssl_ec_group_from_file(certfile)) + && (curve_nid = EC_GROUP_get_curve_name(ecgroup))) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *eckey = EC_KEY_new_by_curve_name(curve_nid); + if (eckey) { + SSL_CTX_set_tmp_ecdh(mctx->ssl_ctx, eckey); + EC_KEY_free(eckey); + } + else { + curve_nid = 0; + } +#else + if (!SSL_CTX_set1_curves(mctx->ssl_ctx, &curve_nid, 1)) { + curve_nid = 0; + } +#endif + if (curve_nid) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02541) + "ECDH curve %s for %s specified in %s", + OBJ_nid2sn(curve_nid), vhost_id, certfile); + } } /* * ...otherwise, enable auto curve selection (OpenSSL 1.0.2) @@ -1382,18 +1586,20 @@ static apr_status_t ssl_init_server_certs(server_rec *s, * ECDH is always enabled in 1.1.0 unless excluded from SSLCipherList */ #if MODSSL_USE_OPENSSL_PRE_1_1_API - else { + if (!curve_nid) { #if defined(SSL_CTX_set_ecdh_auto) SSL_CTX_set_ecdh_auto(mctx->ssl_ctx, 1); #else - eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - SSL_CTX_set_tmp_ecdh(mctx->ssl_ctx, eckey); + EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (eckey) { + SSL_CTX_set_tmp_ecdh(mctx->ssl_ctx, eckey); + EC_KEY_free(eckey); + } #endif } #endif /* OpenSSL assures us that _free() is NULL-safe */ - EC_KEY_free(eckey); - EC_GROUP_free(ecparams); + EC_GROUP_free(ecgroup); #endif return APR_SUCCESS; @@ -1411,6 +1617,7 @@ static apr_status_t ssl_init_ticket_key(server_rec *s, char buf[TLSEXT_TICKET_KEY_LEN]; char *path; modssl_ticket_key_t *ticket_key = mctx->ticket_key; + int res; if (!ticket_key->file_path) { return APR_SUCCESS; @@ -1438,11 +1645,22 @@ static apr_status_t ssl_init_ticket_key(server_rec *s, } memcpy(ticket_key->key_name, buf, 16); - memcpy(ticket_key->hmac_secret, buf + 16, 16); memcpy(ticket_key->aes_key, buf + 32, 16); - - if (!SSL_CTX_set_tlsext_ticket_key_cb(mctx->ssl_ctx, - ssl_callback_SessionTicket)) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + memcpy(ticket_key->hmac_secret, buf + 16, 16); + res = SSL_CTX_set_tlsext_ticket_key_cb(mctx->ssl_ctx, + ssl_callback_SessionTicket); +#else + ticket_key->mac_params[0] = + OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, buf + 16, 16); + ticket_key->mac_params[1] = + OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, "sha256", 0); + ticket_key->mac_params[2] = + OSSL_PARAM_construct_end(); + res = SSL_CTX_set_tlsext_ticket_key_evp_cb(mctx->ssl_ctx, + ssl_callback_SessionTicket); +#endif + if (!res) { ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01913) "Unable to initialize TLS session ticket key callback " "(incompatible OpenSSL version?)"); @@ -1493,8 +1711,12 @@ static apr_status_t ssl_init_proxy_certs(server_rec *s, STACK_OF(X509) *chain; X509_STORE_CTX *sctx; X509_STORE *store = SSL_CTX_get_cert_store(mctx->ssl_ctx); + int addl_chain = 0; /* non-zero if additional chain certs were + * added to store */ -#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + ap_assert(store != NULL); /* safe to assume always non-NULL? */ + +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL && !defined(LIBRESSL_VERSION_NUMBER) /* For OpenSSL >=1.1.1, turn on client cert support which is * otherwise turned off by default (by design). * https://github.com/openssl/openssl/issues/6933 */ @@ -1515,42 +1737,31 @@ static apr_status_t ssl_init_proxy_certs(server_rec *s, } if (pkp->cert_path) { - apr_dir_t *dir; - apr_finfo_t dirent; - apr_int32_t finfo_flags = APR_FINFO_TYPE|APR_FINFO_NAME; - - if (apr_dir_open(&dir, pkp->cert_path, ptemp) == APR_SUCCESS) { - while ((apr_dir_read(&dirent, finfo_flags, dir)) == APR_SUCCESS) { - const char *fullname; - - if (dirent.filetype == APR_DIR) { - continue; /* don't try to load directories */ - } - - fullname = apr_pstrcat(ptemp, - pkp->cert_path, "/", dirent.name, - NULL); - load_x509_info(ptemp, sk, fullname); - } - - apr_dir_close(dir); - } - } - - if ((ncerts = sk_X509_INFO_num(sk)) <= 0) { - sk_X509_INFO_free(sk); - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02206) - "no client certs found for SSL proxy"); - return APR_SUCCESS; + ssl_init_ca_cert_path(s, ptemp, pkp->cert_path, NULL, sk); } /* Check that all client certs have got certificates and private - * keys. */ - for (n = 0; n < ncerts; n++) { + * keys. Note the number of certs in the stack may decrease + * during the loop. */ + for (n = 0; n < sk_X509_INFO_num(sk); n++) { X509_INFO *inf = sk_X509_INFO_value(sk, n); + int has_privkey = inf->x_pkey && inf->x_pkey->dec_pkey; + + /* For a lone certificate in the file, trust it as a + * CA/intermediate certificate. */ + if (inf->x509 && !has_privkey && !inf->enc_data) { + ssl_log_xerror(SSLLOG_MARK, APLOG_DEBUG, 0, ptemp, s, inf->x509, + APLOGNO(10261) "Trusting non-leaf certificate"); + X509_STORE_add_cert(store, inf->x509); /* increments inf->x509 */ + /* Delete from the stack and iterate again. */ + X509_INFO_free(inf); + sk_X509_INFO_delete(sk, n); + n--; + addl_chain = 1; + continue; + } - if (!inf->x509 || !inf->x_pkey || !inf->x_pkey->dec_pkey || - inf->enc_data) { + if (!has_privkey || inf->enc_data) { sk_X509_INFO_free(sk); ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, s, APLOGNO(02252) "incomplete client cert configured for SSL proxy " @@ -1567,13 +1778,21 @@ static apr_status_t ssl_init_proxy_certs(server_rec *s, } } + if ((ncerts = sk_X509_INFO_num(sk)) <= 0) { + sk_X509_INFO_free(sk); + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(02206) + "no client certs found for SSL proxy"); + return APR_SUCCESS; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02207) "loaded %d client certs for SSL proxy", ncerts); pkp->certs = sk; - - if (!pkp->ca_cert_file || !store) { + /* If any chain certs are configured, build the ->ca_certs chains + * corresponding to the loaded keypairs. */ + if (!pkp->ca_cert_file && !addl_chain) { return APR_SUCCESS; } @@ -1589,16 +1808,21 @@ static apr_status_t ssl_init_proxy_certs(server_rec *s, ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02208) "SSL proxy client cert initialization failed"); ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); + sk_X509_INFO_free(sk); return ssl_die(s); } - X509_STORE_load_locations(store, pkp->ca_cert_file, NULL); + modssl_X509_STORE_load_locations(store, pkp->ca_cert_file, NULL); for (n = 0; n < ncerts; n++) { int i; X509_INFO *inf = sk_X509_INFO_value(pkp->certs, n); - X509_STORE_CTX_init(sctx, store, inf->x509, NULL); + if (!X509_STORE_CTX_init(sctx, store, inf->x509, NULL)) { + sk_X509_INFO_free(sk); + X509_STORE_CTX_free(sctx); + return ssl_die(s); + } /* Attempt to verify the client cert */ if (X509_verify_cert(sctx) != 1) { @@ -1729,11 +1953,13 @@ static apr_status_t ssl_init_server_ctx(server_rec *s, apr_array_header_t *pphrases) { apr_status_t rv; + modssl_pk_server_t *pks; #ifdef HAVE_SSL_CONF_CMD ssl_ctx_param_t *param = (ssl_ctx_param_t *)sc->server->ssl_ctx_param->elts; SSL_CONF_CTX *cctx = sc->server->ssl_ctx_config; int i; #endif + int n; /* * Check for problematic re-initializations @@ -1745,52 +1971,30 @@ static apr_status_t ssl_init_server_ctx(server_rec *s, return APR_EGENERAL; } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10083) - "Init: (%s) mod_md support is %s.", ssl_util_vhostid(p, s), - md_is_managed? "available" : "unavailable"); - if (md_is_managed && md_is_managed(s)) { - modssl_pk_server_t *const pks = sc->server->pks; - if (pks->cert_files->nelts > 0 || pks->key_files->nelts > 0) { - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10084) - "Init: (%s) You configured certificate/key files on this host, but " - "is is covered by a Managed Domain. You need to remove these directives " - "for the Managed Domain to take over.", ssl_util_vhostid(p, s)); - } - else { - const char *key_file, *cert_file, *chain_file; - - key_file = cert_file = chain_file = NULL; - - if (md_get_certificate) { - rv = md_get_certificate(s, p, &key_file, &cert_file); - } - else { - rv = APR_ENOTIMPL; - } - - if (key_file && cert_file) { - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, - "%s: installing key=%s, cert=%s, chain=%s", - ssl_util_vhostid(p, s), key_file, cert_file, chain_file); - APR_ARRAY_PUSH(pks->key_files, const char *) = key_file; - APR_ARRAY_PUSH(pks->cert_files, const char *) = cert_file; - sc->server->cert_chain = chain_file; - } - - if (APR_STATUS_IS_EAGAIN(rv)) { - /* Managed Domain not ready yet. This is not a reason to fail the config */ - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10085) - "Init: %s will respond with '503 Service Unavailable' for now. This " - "host is part of a Managed Domain, but no SSL certificate is " - "available (yet).", ssl_util_vhostid(p, s)); - pks->service_unavailable = 1; - } - else if (rv != APR_SUCCESS) { - return rv; - } + /* Allow others to provide certificate files */ + pks = sc->server->pks; + n = pks->cert_files->nelts; + ap_ssl_add_cert_files(s, p, pks->cert_files, pks->key_files); + ssl_run_add_cert_files(s, p, pks->cert_files, pks->key_files); + + if (apr_is_empty_array(pks->cert_files)) { + /* does someone propose a certiciate to fall back on here? */ + ap_ssl_add_fallback_cert_files(s, p, pks->cert_files, pks->key_files); + ssl_run_add_fallback_cert_files(s, p, pks->cert_files, pks->key_files); + if (n < pks->cert_files->nelts) { + pks->service_unavailable = 1; + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10085) + "Init: %s will respond with '503 Service Unavailable' for now. There " + "are no SSL certificates configured and no other module contributed any.", + ssl_util_vhostid(p, s)); } } + if (n < pks->cert_files->nelts) { + /* additionally installed certs overrides any old chain configuration */ + sc->server->cert_chain = NULL; + } + if ((rv = ssl_init_ctx(s, p, ptemp, sc->server)) != APR_SUCCESS) { return rv; } @@ -1841,7 +2045,7 @@ static apr_status_t ssl_init_server_ctx(server_rec *s, * (late) point makes sure that we catch both certificates loaded * via SSLCertificateFile and SSLOpenSSLConfCmd Certificate. */ - if (sc->server->stapling_enabled == TRUE) { + do { X509 *cert; int i = 0; int ret = SSL_CTX_set_current_cert(sc->server->ssl_ctx, @@ -1858,7 +2062,7 @@ static apr_status_t ssl_init_server_ctx(server_rec *s, SSL_CERT_SET_NEXT); i++; } - } + } while(0); #endif #ifdef HAVE_TLS_SESSION_TICKETS @@ -2038,50 +2242,38 @@ int ssl_proxy_section_post_config(apr_pool_t *p, apr_pool_t *plog, return OK; } -static int ssl_init_FindCAList_X509NameCmp(const X509_NAME * const *a, - const X509_NAME * const *b) -{ - return(X509_NAME_cmp(*a, *b)); -} - -static void ssl_init_PushCAList(STACK_OF(X509_NAME) *ca_list, - server_rec *s, apr_pool_t *ptemp, - const char *file) +static apr_status_t ssl_init_ca_cert_path(server_rec *s, + apr_pool_t *ptemp, + const char *path, + STACK_OF(X509_NAME) *ca_list, + STACK_OF(X509_INFO) *xi_list) { - int n; - STACK_OF(X509_NAME) *sk; + apr_dir_t *dir; + apr_finfo_t direntry; + apr_int32_t finfo_flags = APR_FINFO_TYPE|APR_FINFO_NAME; - sk = (STACK_OF(X509_NAME) *) - SSL_load_client_CA_file(file); - - if (!sk) { - return; + if (!path || (!ca_list && !xi_list) || + (apr_dir_open(&dir, path, ptemp) != APR_SUCCESS)) { + return APR_EGENERAL; } - for (n = 0; n < sk_X509_NAME_num(sk); n++) { - X509_NAME *name = sk_X509_NAME_value(sk, n); - - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02209) - "CA certificate: %s", - modssl_X509_NAME_to_string(ptemp, name, 0)); - - /* - * note that SSL_load_client_CA_file() checks for duplicates, - * but since we call it multiple times when reading a directory - * we must also check for duplicates ourselves. - */ - - if (sk_X509_NAME_find(ca_list, name) < 0) { - /* this will be freed when ca_list is */ - sk_X509_NAME_push(ca_list, name); + while ((apr_dir_read(&direntry, finfo_flags, dir)) == APR_SUCCESS) { + const char *file; + if (direntry.filetype == APR_DIR) { + continue; /* don't try to load directories */ } - else { - /* need to free this ourselves, else it will leak */ - X509_NAME_free(name); + file = apr_pstrcat(ptemp, path, "/", direntry.name, NULL); + if (ca_list) { + SSL_add_file_cert_subjects_to_stack(ca_list, file); + } + if (xi_list) { + load_x509_info(ptemp, xi_list, file); } } - sk_X509_NAME_free(sk); + apr_dir_close(dir); + + return APR_SUCCESS; } STACK_OF(X509_NAME) *ssl_init_FindCAList(server_rec *s, @@ -2089,19 +2281,13 @@ STACK_OF(X509_NAME) *ssl_init_FindCAList(server_rec *s, const char *ca_file, const char *ca_path) { - STACK_OF(X509_NAME) *ca_list; - - /* - * Start with a empty stack/list where new - * entries get added in sorted order. - */ - ca_list = sk_X509_NAME_new(ssl_init_FindCAList_X509NameCmp); + STACK_OF(X509_NAME) *ca_list = sk_X509_NAME_new_null();; /* * Process CA certificate bundle file */ if (ca_file) { - ssl_init_PushCAList(ca_list, s, ptemp, ca_file); + SSL_add_file_cert_subjects_to_stack(ca_list, ca_file); /* * If ca_list is still empty after trying to load ca_file * then the file failed to load, and users should hear about that. @@ -2116,37 +2302,15 @@ STACK_OF(X509_NAME) *ssl_init_FindCAList(server_rec *s, /* * Process CA certificate path files */ - if (ca_path) { - apr_dir_t *dir; - apr_finfo_t direntry; - apr_int32_t finfo_flags = APR_FINFO_TYPE|APR_FINFO_NAME; - apr_status_t rv; - - if ((rv = apr_dir_open(&dir, ca_path, ptemp)) != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(02211) - "Failed to open Certificate Path `%s'", - ca_path); - sk_X509_NAME_pop_free(ca_list, X509_NAME_free); - return NULL; - } - - while ((apr_dir_read(&direntry, finfo_flags, dir)) == APR_SUCCESS) { - const char *file; - if (direntry.filetype == APR_DIR) { - continue; /* don't try to load directories */ - } - file = apr_pstrcat(ptemp, ca_path, "/", direntry.name, NULL); - ssl_init_PushCAList(ca_list, s, ptemp, file); - } - - apr_dir_close(dir); + if (ca_path && + ssl_init_ca_cert_path(s, ptemp, + ca_path, ca_list, NULL) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02211) + "Failed to open Certificate Path `%s'", ca_path); + sk_X509_NAME_pop_free(ca_list, X509_NAME_free); + return NULL; } - /* - * Cleanup - */ - (void) sk_X509_NAME_set_cmp_func(ca_list, NULL); - return ca_list; } @@ -2192,10 +2356,11 @@ apr_status_t ssl_init_ModuleKill(void *data) } -#if !MODSSL_USE_OPENSSL_PRE_1_1_API +#if MODSSL_USE_OPENSSL_PRE_1_1_API + free_dh_params(); +#else free_bio_methods(); #endif - free_dh_params(); return APR_SUCCESS; } diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c index 6da8f10..b91f784 100644 --- a/modules/ssl/ssl_engine_io.c +++ b/modules/ssl/ssl_engine_io.c @@ -28,8 +28,7 @@ core keeps dumping.'' -- Unknown */ #include "ssl_private.h" -#include "mod_ssl.h" -#include "mod_ssl_openssl.h" + #include "apr_date.h" APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, proxy_post_handshake, @@ -152,6 +151,9 @@ static int bio_filter_out_flush(BIO *bio) bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio); apr_bucket *e; + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c, + "bio_filter_out_write: flush"); + AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(outctx->bb)); e = apr_bucket_flush_create(outctx->bb->bucket_alloc); @@ -191,6 +193,10 @@ static int bio_filter_destroy(BIO *bio) static int bio_filter_out_read(BIO *bio, char *out, int outl) { /* this is never called */ + bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c, + "BUG: %s() should not be called", "bio_filter_out_read"); + AP_DEBUG_ASSERT(0); return -1; } @@ -208,6 +214,9 @@ static int bio_filter_out_write(BIO *bio, const char *in, int inl) return -1; } + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, outctx->c, + "bio_filter_out_write: %i bytes", inl); + /* Use a transient bucket for the output data - any downstream * filter must setaside if necessary. */ e = apr_bucket_transient_create(in, inl, outctx->bb->bucket_alloc); @@ -287,12 +296,20 @@ static long bio_filter_out_ctrl(BIO *bio, int cmd, long num, void *ptr) static int bio_filter_out_gets(BIO *bio, char *buf, int size) { /* this is never called */ + bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c, + "BUG: %s() should not be called", "bio_filter_out_gets"); + AP_DEBUG_ASSERT(0); return -1; } static int bio_filter_out_puts(BIO *bio, const char *str) { /* this is never called */ + bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c, + "BUG: %s() should not be called", "bio_filter_out_puts"); + AP_DEBUG_ASSERT(0); return -1; } @@ -527,22 +544,46 @@ static int bio_filter_in_read(BIO *bio, char *in, int inlen) static int bio_filter_in_write(BIO *bio, const char *in, int inl) { + bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c, + "BUG: %s() should not be called", "bio_filter_in_write"); + AP_DEBUG_ASSERT(0); return -1; } static int bio_filter_in_puts(BIO *bio, const char *str) { + bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c, + "BUG: %s() should not be called", "bio_filter_in_puts"); + AP_DEBUG_ASSERT(0); return -1; } static int bio_filter_in_gets(BIO *bio, char *buf, int size) { + bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c, + "BUG: %s() should not be called", "bio_filter_in_gets"); + AP_DEBUG_ASSERT(0); return -1; } static long bio_filter_in_ctrl(BIO *bio, int cmd, long num, void *ptr) { - return -1; + bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio); + switch (cmd) { +#ifdef BIO_CTRL_EOF + case BIO_CTRL_EOF: + return inctx->rc == APR_EOF; +#endif + default: + break; + } + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c, + "BUG: bio_filter_in_ctrl() should not be called with cmd=%i", + cmd); + return 0; } #if MODSSL_USE_OPENSSL_PRE_1_1_API @@ -567,7 +608,7 @@ static BIO_METHOD bio_filter_in_method = { bio_filter_in_read, bio_filter_in_puts, /* puts is never called */ bio_filter_in_gets, /* gets is never called */ - bio_filter_in_ctrl, /* ctrl is never called */ + bio_filter_in_ctrl, /* ctrl is called for EOF check */ bio_filter_create, bio_filter_destroy, NULL @@ -846,6 +887,9 @@ static apr_status_t ssl_filter_write(ap_filter_t *f, return APR_EGENERAL; } + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, f->c, + "ssl_filter_write: %"APR_SIZE_T_FMT" bytes", len); + /* We rely on SSL_get_error() after the write, which requires an empty error * queue before the write in order to work properly. */ @@ -934,7 +978,7 @@ static apr_status_t ssl_filter_write(ap_filter_t *f, alloc) /* Custom apr_status_t error code, used when a plain HTTP request is - * recevied on an SSL port. */ + * received on an SSL port. */ #define MODSSL_ERROR_HTTP_ON_HTTPS (APR_OS_START_USERERR + 0) /* Custom apr_status_t error code, used when the proxy cannot @@ -1162,11 +1206,13 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) } server = sslconn->server; - if (sslconn->is_proxy) { + if (c->outgoing) { #ifdef HAVE_TLSEXT apr_ipsubnet_t *ip; #ifdef HAVE_TLS_ALPN const char *alpn_note; + apr_array_header_t *alpn_proposed = NULL; + int alpn_empty_ok = 1; #endif #endif const char *hostname_note = apr_table_get(c->notes, @@ -1182,9 +1228,16 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) #ifdef HAVE_TLS_ALPN alpn_note = apr_table_get(c->notes, "proxy-request-alpn-protos"); if (alpn_note) { - char *protos, *s, *p, *last; + char *protos, *s, *p, *last, *proto; apr_size_t len; + /* Transform the note into a protocol formatted byte array: + * (len-byte proto-char+)* + * We need the remote server to agree on one of these, unless 'http/1.1' + * is also among our proposals. Because pre-ALPN remotes will speak this. + */ + alpn_proposed = apr_array_make(c->pool, 3, sizeof(const char*)); + alpn_empty_ok = 0; s = protos = apr_pcalloc(c->pool, strlen(alpn_note)+1); p = apr_pstrdup(c->pool, alpn_note); while ((p = apr_strtok(p, ", ", &last))) { @@ -1196,6 +1249,11 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, server); return APR_EGENERAL; } + proto = apr_pstrndup(c->pool, p, len); + APR_ARRAY_PUSH(alpn_proposed, const char*) = proto; + if (!strcmp("http/1.1", proto)) { + alpn_empty_ok = 1; + } *s++ = (unsigned char)len; while (len--) { *s++ = *p++; @@ -1211,6 +1269,8 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(03310) "error setting alpn protos from '%s'", alpn_note); ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server); + /* If ALPN was requested and we cannot do it, we must fail */ + return MODSSL_ERROR_BAD_GATEWAY; } } #endif /* defined HAVE_TLS_ALPN */ @@ -1238,7 +1298,7 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server); } } -#endif +#endif /* defined HAVE_TLSEXT */ if ((n = SSL_connect(filter_ctx->pssl)) <= 0) { ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02003) @@ -1267,7 +1327,6 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) ((dc->proxy->ssl_check_peer_cn != FALSE) || (dc->proxy->ssl_check_peer_name == TRUE)) && hostname_note) { - apr_table_unset(c->notes, "proxy-request-hostname"); if (!cert || modssl_X509_match_name(c->pool, cert, hostname_note, TRUE, server) == FALSE) { @@ -1284,7 +1343,6 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) hostname = ssl_var_lookup(NULL, server, c, NULL, "SSL_CLIENT_S_DN_CN"); - apr_table_unset(c->notes, "proxy-request-hostname"); /* Do string match or simplest wildcard match if that * fails. */ @@ -1304,6 +1362,50 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) } } +#ifdef HAVE_TLS_ALPN + /* If we proposed ALPN protocol(s), we need to check if the server + * agreed to one of them. While <https://www.rfc-editor.org/rfc/rfc7301.txt> + * chapter 3.2 says the server SHALL error the handshake in such a case, + * the reality is that some servers fall back to their default, e.g. http/1.1. + * (we also do this right now) + * We need to treat this as an error for security reasons. + */ + if (alpn_proposed && alpn_proposed->nelts > 0) { + const char *selected; + unsigned int slen; + + SSL_get0_alpn_selected(filter_ctx->pssl, (const unsigned char**)&selected, &slen); + if (!selected || !slen) { + /* No ALPN selection reported by the remote server. This could mean + * it does not support ALPN (old server) or that it does not support + * any of our proposals (Apache itself up to 2.4.48 at least did that). */ + if (!alpn_empty_ok) { + ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10273) + "SSL Proxy: Peer did not select any of our ALPN protocols [%s].", + alpn_note); + proxy_ssl_check_peer_ok = FALSE; + } + } + else { + const char *proto; + int i, found = 0; + for (i = 0; !found && i < alpn_proposed->nelts; ++i) { + proto = APR_ARRAY_IDX(alpn_proposed, i, const char *); + found = !strncmp(selected, proto, slen); + } + if (!found) { + /* From a conforming peer, this should never happen, + * but life always finds a way... */ + proto = apr_pstrndup(c->pool, selected, slen); + ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10274) + "SSL Proxy: Peer proposed ALPN protocol %s which is none " + "of our proposals [%s].", proto, alpn_note); + proxy_ssl_check_peer_ok = FALSE; + } + } + } +#endif + if (proxy_ssl_check_peer_ok == TRUE) { /* another chance to fail */ post_handshake_rc = ssl_run_proxy_post_handshake(c, filter_ctx->pssl); @@ -1587,18 +1689,32 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f, } -/* ssl_io_filter_output() produces one SSL/TLS message per bucket +/* ssl_io_filter_output() produces one SSL/TLS record per bucket * passed down the output filter stack. This results in a high - * overhead (network packets) for any output comprising many small - * buckets. SSI page applied through the HTTP chunk filter, for - * example, may produce many brigades containing small buckets - - * [chunk-size CRLF] [chunk-data] [CRLF]. + * overhead (more network packets & TLS processing) for any output + * comprising many small buckets. SSI output passed through the HTTP + * chunk filter, for example, may produce many brigades containing + * small buckets - [chunk-size CRLF] [chunk-data] [CRLF]. * - * The coalescing filter merges many small buckets into larger buckets - * where possible, allowing the SSL I/O output filter to handle them - * more efficiently. */ + * Sending HTTP response headers as a separate TLS record to the + * response body also reveals information to a network observer (the + * size of headers) which can be significant. + * + * The coalescing filter merges data buckets with the aim of producing + * fewer, larger TLS records - without copying/buffering all content + * and introducing unnecessary overhead. + * + * ### This buffering could be probably be done more comprehensively + * ### in ssl_io_filter_output itself. + * + * ### Another possible performance optimisation in particular for the + * ### [HEAP] [FILE] HTTP response case is using a brigade rather than + * ### a char array to buffer; using apr_brigade_write() to append + * ### will use already-allocated memory from the HEAP, reducing # of + * ### copies. + */ -#define COALESCE_BYTES (2048) +#define COALESCE_BYTES (AP_IOBUFSIZE) struct coalesce_ctx { char buffer[COALESCE_BYTES]; @@ -1611,11 +1727,12 @@ static apr_status_t ssl_io_filter_coalesce(ap_filter_t *f, apr_bucket *e, *upto; apr_size_t bytes = 0; struct coalesce_ctx *ctx = f->ctx; + apr_size_t buffered = ctx ? ctx->bytes : 0; /* space used on entry */ unsigned count = 0; /* The brigade consists of zero-or-more small data buckets which - * can be coalesced (the prefix), followed by the remainder of the - * brigade. + * can be coalesced (referred to as the "prefix"), followed by the + * remainder of the brigade. * * Find the last bucket - if any - of that prefix. count gives * the number of buckets in the prefix. The "prefix" must contain @@ -1630,24 +1747,100 @@ static apr_status_t ssl_io_filter_coalesce(ap_filter_t *f, e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_METADATA(e) && e->length != (apr_size_t)-1 - && e->length < COALESCE_BYTES - && (bytes + e->length) < COALESCE_BYTES - && (ctx == NULL - || bytes + ctx->bytes + e->length < COALESCE_BYTES); + && e->length <= COALESCE_BYTES + && (buffered + bytes + e->length) <= COALESCE_BYTES; e = APR_BUCKET_NEXT(e)) { - if (e->length) count++; /* don't count zero-length buckets */ - bytes += e->length; + /* don't count zero-length buckets */ + if (e->length) { + bytes += e->length; + count++; + } + } + + /* If there is room remaining and the next bucket is a data + * bucket, try to include it in the prefix to coalesce. For a + * typical [HEAP] [FILE] HTTP response brigade, this handles + * merging the headers and the start of the body into a single TLS + * record. */ + if (bytes + buffered > 0 + && bytes + buffered < COALESCE_BYTES + && e != APR_BRIGADE_SENTINEL(bb) + && !APR_BUCKET_IS_METADATA(e)) { + apr_status_t rv = APR_SUCCESS; + + /* For an indeterminate length bucket (PIPE/CGI/...), try a + * non-blocking read to have it morph into a HEAP. If the + * read fails with EAGAIN, it is harmless to try a split + * anyway, split is ENOTIMPL for most PIPE-like buckets. */ + if (e->length == (apr_size_t)-1) { + const char *discard; + apr_size_t ignore; + + rv = apr_bucket_read(e, &discard, &ignore, APR_NONBLOCK_READ); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10232) + "coalesce failed to read from %s bucket", + e->type->name); + return AP_FILTER_ERROR; + } + } + + if (rv == APR_SUCCESS) { + /* If the read above made the bucket morph, it may now fit + * entirely within the buffer. Otherwise, split it so it does + * fit. */ + if (e->length > COALESCE_BYTES + || e->length + buffered + bytes > COALESCE_BYTES) { + rv = apr_bucket_split(e, COALESCE_BYTES - (buffered + bytes)); + } + + if (rv == APR_SUCCESS && e->length == 0) { + /* As above, don't count in the prefix if the bucket is + * now zero-length. */ + } + else if (rv == APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, + "coalesce: adding %" APR_SIZE_T_FMT " bytes " + "from split %s bucket, total %" APR_SIZE_T_FMT, + e->length, e->type->name, bytes + buffered); + + count++; + bytes += e->length; + e = APR_BUCKET_NEXT(e); + } + else if (rv != APR_ENOTIMPL) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10233) + "coalesce: failed to split data bucket"); + return AP_FILTER_ERROR; + } + } } + + /* The prefix is zero or more buckets. upto now points to the + * bucket AFTER the end of the prefix, which may be the brigade + * sentinel. */ upto = e; - /* Coalesce the prefix, if: - * a) more than one bucket is found to coalesce, or - * b) the brigade contains only a single data bucket, or - * c) the data bucket is not last but we have buffered data already. + /* Coalesce the prefix, if any of the following are true: + * + * a) the prefix is more than one bucket + * OR + * b) the prefix is the entire brigade, which is a single bucket + * AND the prefix length is smaller than the buffer size, + * OR + * c) the prefix is a single bucket + * AND there is buffered data from a previous pass. + * + * The aim with (b) is to buffer a small bucket so it can be + * coalesced with future invocations of this filter. e.g. three + * calls each with a single 100 byte HEAP bucket should get + * coalesced together. But an invocation with a 8192 byte HEAP + * should pass through untouched. */ if (bytes > 0 && (count > 1 - || (upto == APR_BRIGADE_SENTINEL(bb)) + || (upto == APR_BRIGADE_SENTINEL(bb) + && bytes < COALESCE_BYTES) || (ctx && ctx->bytes > 0))) { /* If coalescing some bytes, ensure a context has been * created. */ @@ -1658,7 +1851,8 @@ static apr_status_t ssl_io_filter_coalesce(ap_filter_t *f, ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, "coalesce: have %" APR_SIZE_T_FMT " bytes, " - "adding %" APR_SIZE_T_FMT " more", ctx->bytes, bytes); + "adding %" APR_SIZE_T_FMT " more (buckets=%u)", + ctx->bytes, bytes, count); /* Iterate through the prefix segment. For non-fatal errors * in this loop it is safe to break out and fall back to the @@ -1673,7 +1867,8 @@ static apr_status_t ssl_io_filter_coalesce(ap_filter_t *f, if (APR_BUCKET_IS_METADATA(e) || e->length == (apr_size_t)-1) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02012) - "unexpected bucket type during coalesce"); + "unexpected %s bucket during coalesce", + e->type->name); break; /* non-fatal error; break out */ } @@ -1945,7 +2140,7 @@ static apr_status_t ssl_io_filter_buffer(ap_filter_t *f, } if (APR_BRIGADE_EMPTY(ctx->bb)) { - /* Suprisingly (and perhaps, wrongly), the request body can be + /* Surprisingly (and perhaps, wrongly), the request body can be * pulled from the input filter stack more than once; a * handler may read it, and ap_discard_request_body() will * attempt to do so again after *every* request. So input @@ -2087,14 +2282,7 @@ void ssl_io_filter_init(conn_rec *c, request_rec *r, SSL *ssl) ssl_io_filter_cleanup, apr_pool_cleanup_null); if (APLOG_CS_IS_LEVEL(c, mySrvFromConn(c), APLOG_TRACE4)) { - BIO *rbio = SSL_get_rbio(ssl), - *wbio = SSL_get_wbio(ssl); - BIO_set_callback(rbio, ssl_io_data_cb); - BIO_set_callback_arg(rbio, (void *)ssl); - if (wbio && wbio != rbio) { - BIO_set_callback(wbio, ssl_io_data_cb); - BIO_set_callback_arg(wbio, (void *)ssl); - } + modssl_set_io_callbacks(ssl); } return; @@ -2123,19 +2311,18 @@ static void ssl_io_data_dump(conn_rec *c, server_rec *s, const char *b, long len) { char buf[256]; - char tmp[64]; - int i, j, rows, trunc; + int i, j, rows, trunc, pos; unsigned char ch; trunc = 0; - for(; (len > 0) && ((b[len-1] == ' ') || (b[len-1] == '\0')); len--) + for (; (len > 0) && ((b[len-1] == ' ') || (b[len-1] == '\0')); len--) trunc++; rows = (len / DUMP_WIDTH); if ((rows * DUMP_WIDTH) < len) rows++; ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s, "+-------------------------------------------------------------------------+"); - for(i = 0 ; i< rows; i++) { + for (i = 0 ; i < rows; i++) { #if APR_CHARSET_EBCDIC char ebcdic_text[DUMP_WIDTH]; j = DUMP_WIDTH; @@ -2146,32 +2333,30 @@ static void ssl_io_data_dump(conn_rec *c, server_rec *s, memcpy(ebcdic_text,(char *)(b) + i * DUMP_WIDTH, j); ap_xlate_proto_from_ascii(ebcdic_text, j); #endif /* APR_CHARSET_EBCDIC */ - apr_snprintf(tmp, sizeof(tmp), "| %04x: ", i * DUMP_WIDTH); - apr_cpystrn(buf, tmp, sizeof(buf)); + pos = 0; + pos += apr_snprintf(buf, sizeof(buf)-pos, "| %04x: ", i * DUMP_WIDTH); for (j = 0; j < DUMP_WIDTH; j++) { if (((i * DUMP_WIDTH) + j) >= len) - apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf)); + pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " "); else { ch = ((unsigned char)*((char *)(b) + i * DUMP_WIDTH + j)) & 0xff; - apr_snprintf(tmp, sizeof(tmp), "%02x%c", ch , j==7 ? '-' : ' '); - apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf)); + pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%02x%c", ch , j==7 ? '-' : ' '); } } - apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf)); + pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " "); for (j = 0; j < DUMP_WIDTH; j++) { if (((i * DUMP_WIDTH) + j) >= len) - apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf)); + pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " "); else { ch = ((unsigned char)*((char *)(b) + i * DUMP_WIDTH + j)) & 0xff; #if APR_CHARSET_EBCDIC - apr_snprintf(tmp, sizeof(tmp), "%c", (ch >= 0x20 && ch <= 0x7F) ? ebcdic_text[j] : '.'); + pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%c", (ch >= 0x20 && ch <= 0x7F) ? ebcdic_text[j] : '.'); #else /* APR_CHARSET_EBCDIC */ - apr_snprintf(tmp, sizeof(tmp), "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.'); + pos += apr_snprintf(buf+pos, sizeof(buf)-pos, "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.'); #endif /* APR_CHARSET_EBCDIC */ - apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf)); } } - apr_cpystrn(buf+strlen(buf), " |", sizeof(buf)-strlen(buf)); + pos += apr_snprintf(buf+pos, sizeof(buf)-pos, " |"); ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s, "%s", buf); } if (trunc > 0) @@ -2179,16 +2364,24 @@ static void ssl_io_data_dump(conn_rec *c, server_rec *s, "| %04ld - <SPACES/NULS>", len + trunc); ap_log_cserror(APLOG_MARK, APLOG_TRACE7, 0, c, s, "+-------------------------------------------------------------------------+"); - return; } -long ssl_io_data_cb(BIO *bio, int cmd, - const char *argp, - int argi, long argl, long rc) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +static long modssl_io_cb(BIO *bio, int cmd, const char *argp, + size_t len, int argi, long argl, int rc, + size_t *processed) +#else +static long modssl_io_cb(BIO *bio, int cmd, const char *argp, + int argi, long argl, long rc) +#endif { SSL *ssl; conn_rec *c; server_rec *s; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + (void)len; + (void)processed; +#endif if ((ssl = (SSL *)BIO_get_callback_arg(bio)) == NULL) return rc; @@ -2210,7 +2403,7 @@ long ssl_io_data_cb(BIO *bio, int cmd, "%s: %s %ld/%d bytes %s BIO#%pp [mem: %pp] %s", MODSSL_LIBRARY_NAME, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"), - rc, argi, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "to" : "from"), + (long)rc, argi, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "to" : "from"), bio, argp, dump); if (*dump != '\0' && argp != NULL) ssl_io_data_dump(c, s, argp, rc); @@ -2225,3 +2418,25 @@ long ssl_io_data_cb(BIO *bio, int cmd, } return rc; } + +static APR_INLINE void set_bio_callback(BIO *bio, void *arg) +{ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIO_set_callback_ex(bio, modssl_io_cb); +#else + BIO_set_callback(bio, modssl_io_cb); +#endif + BIO_set_callback_arg(bio, arg); +} + +void modssl_set_io_callbacks(SSL *ssl) +{ + BIO *rbio = SSL_get_rbio(ssl), + *wbio = SSL_get_wbio(ssl); + if (rbio) { + set_bio_callback(rbio, ssl); + } + if (wbio && wbio != rbio) { + set_bio_callback(wbio, ssl); + } +} diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 81c0f63..fe0496f 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -114,6 +114,45 @@ static int has_buffered_data(request_rec *r) return result; } +/* If a renegotiation is required for the 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 "accept" state, it will reject the SSL packets + * which the client is sending for the request body. + * + * To allow authentication to complete in the hook, the solution used + * here is to fill a (bounded) buffer with the request body, and then + * to reinject that request body later. + * + * This function is called to fill the renegotiation buffer for the + * location as required, or fail. Returns zero on success or HTTP_ + * error code on failure. + */ +static int fill_reneg_buffer(request_rec *r, SSLDirConfigRec *dc) +{ + int rv; + apr_size_t rsize; + + /* ### this is HTTP/1.1 specific, special case for protocol? */ + if (r->expecting_100 || !ap_request_has_body(r)) { + return 0; + } + + rsize = dc->nRenegBufferSize == UNSET ? DEFAULT_RENEG_BUFFER_SIZE : dc->nRenegBufferSize; + if (rsize > 0) { + /* Fill the I/O buffer with the request body if possible. */ + rv = ssl_io_buffer_fill(r, rsize); + } + else { + /* If the reneg buffer size is set to zero, just fail. */ + rv = HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + return rv; +} + #ifdef HAVE_TLSEXT static int ap_array_same_str_set(apr_array_header_t *s1, apr_array_header_t *s2) { @@ -814,41 +853,14 @@ static int ssl_hook_Access_classic(request_rec *r, SSLSrvConfigRec *sc, SSLDirCo } } - /* 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 - * "accept" state, it will reject the SSL packets which the client - * is sending for the request body. - * - * To allow authentication to complete in this auth hook, the - * solution used here is to fill a (bounded) buffer with the - * request body, and then to reinject that request body later. - */ - if (renegotiate && !renegotiate_quick - && !r->expecting_100 - && ap_request_has_body(r)) { - int rv; - apr_size_t rsize; - - rsize = dc->nRenegBufferSize == UNSET ? DEFAULT_RENEG_BUFFER_SIZE : - dc->nRenegBufferSize; - if (rsize > 0) { - /* Fill the I/O buffer with the request body if possible. */ - rv = ssl_io_buffer_fill(r, rsize); - } - else { - /* If the reneg buffer size is set to zero, just fail. */ - rv = HTTP_REQUEST_ENTITY_TOO_LARGE; - } - - if (rv) { + /* Fill reneg buffer if required. */ + if (renegotiate && !renegotiate_quick) { + rc = fill_reneg_buffer(r, dc); + if (rc) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02257) "could not buffer message body to allow " "SSL renegotiation to proceed"); - return rv; + return rc; } } @@ -1132,6 +1144,7 @@ static int ssl_hook_Access_modern(request_rec *r, SSLSrvConfigRec *sc, SSLDirCon } } + /* Fill reneg buffer if required. */ if (change_vmode) { char peekbuf[1]; @@ -1144,7 +1157,16 @@ static int ssl_hook_Access_modern(request_rec *r, SSLSrvConfigRec *sc, SSLDirCon return HTTP_FORBIDDEN; } - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10129) "verify client post handshake"); + rc = fill_reneg_buffer(r, dc); + if (rc) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10228) + "could not buffer message body to allow " + "TLS Post-Handshake Authentication to proceed"); + return rc; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10129) + "verify client post handshake"); SSL_set_verify(ssl, vmode_needed, ssl_callback_SSLVerify); @@ -1154,6 +1176,7 @@ static int ssl_hook_Access_modern(request_rec *r, SSLSrvConfigRec *sc, SSLDirCon ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, r->server); apr_table_setn(r->notes, "error-notes", "Reason: Cannot perform Post-Handshake Authentication.<br />"); + SSL_set_verify(ssl, vmode_inplace, NULL); return HTTP_FORBIDDEN; } @@ -1175,6 +1198,7 @@ static int ssl_hook_Access_modern(request_rec *r, SSLSrvConfigRec *sc, SSLDirCon * Finally check for acceptable renegotiation results */ if (OK != (rc = ssl_check_post_client_verify(r, sc, dc, sslconn, ssl))) { + SSL_set_verify(ssl, vmode_inplace, NULL); return rc; } } @@ -1661,6 +1685,7 @@ const authz_provider ssl_authz_provider_verify_client = ** _________________________________________________________________ */ +#if MODSSL_USE_OPENSSL_PRE_1_1_API /* * Hand out standard DH parameters, based on the authentication strength */ @@ -1706,6 +1731,7 @@ DH *ssl_callback_TmpDH(SSL *ssl, int export, int keylen) return modssl_get_dh_params(keylen); } +#endif /* * This OpenSSL callback function is called when OpenSSL @@ -1723,7 +1749,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) SSLSrvConfigRec *sc = mySrvConfig(s); SSLConnRec *sslconn = myConnConfig(conn); SSLDirConfigRec *dc = r ? myDirConfig(r) : sslconn->dc; - modssl_ctx_t *mctx = myCtxConfig(sslconn, sc); + modssl_ctx_t *mctx = myConnCtxConfig(conn, sc); int crl_check_mode = mctx->crl_check_mask & ~SSL_CRLCHECK_FLAGS; /* Get verify ingredients */ @@ -1747,7 +1773,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) * Check for optionally acceptable non-verifiable issuer situation */ if (dc) { - if (sslconn->is_proxy) { + if (conn->outgoing) { verify = dc->proxy->auth.verify_mode; } else { @@ -1810,8 +1836,8 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) /* * Perform OCSP-based revocation checks */ - if (ok && ((sc->server->ocsp_mask & SSL_OCSPCHECK_CHAIN) || - (errdepth == 0 && (sc->server->ocsp_mask & SSL_OCSPCHECK_LEAF)))) { + if (ok && ((mctx->ocsp_mask & SSL_OCSPCHECK_CHAIN) || + (errdepth == 0 && (mctx->ocsp_mask & SSL_OCSPCHECK_LEAF)))) { /* If there was an optional verification error, it's not * possible to perform OCSP validation since the issuer may be * missing/untrusted. Fail in that case. */ @@ -1859,7 +1885,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx) * Finally check the depth of the certificate verification */ if (dc) { - if (sslconn->is_proxy) { + if (conn->outgoing) { depth = dc->proxy->auth.verify_depth; } else { @@ -1911,7 +1937,7 @@ static void modssl_proxy_info_log(conn_rec *c, *cert = info->x509; \ CRYPTO_add(&(*cert)->references, +1, CRYPTO_LOCK_X509); \ *pkey = info->x_pkey->dec_pkey; \ - CRYPTO_add(&(*pkey)->references, +1, CRYPTO_LOCK_X509_PKEY) + CRYPTO_add(&(*pkey)->references, +1, CRYPTO_LOCK_EVP_PKEY) #else #define modssl_set_cert_info(info, cert, pkey) \ *cert = info->x509; \ @@ -2268,7 +2294,7 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc) /* If the reneg state is to reject renegotiations, check the SSL * state machine and move to ABORT if a Client Hello is being * read. */ - if (!sslconn->is_proxy && + if (!c->outgoing && (where & SSL_CB_HANDSHAKE_START) && sslconn->reneg_state == RENEG_REJECT) { sslconn->reneg_state = RENEG_ABORT; @@ -2290,60 +2316,89 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc) } #ifdef HAVE_TLSEXT + +static apr_status_t set_challenge_creds(conn_rec *c, const char *servername, + SSL *ssl, X509 *cert, EVP_PKEY *key, + const char *cert_pem, const char *key_pem) +{ + SSLConnRec *sslcon = myConnConfig(c); + apr_status_t rv = APR_SUCCESS; + int our_data = 0; + + sslcon->service_unavailable = 1; + if (cert_pem) { + cert = NULL; + key = NULL; + our_data = 1; + + rv = modssl_read_cert(c->pool, cert_pem, key_pem, NULL, NULL, &cert, &key); + if (rv != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10266) + "Failed to parse PEM of challenge certificate %s", + servername); + goto cleanup; + } + } + + if ((SSL_use_certificate(ssl, cert) < 1)) { + ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10086) + "Failed to configure challenge certificate %s", + servername); + rv = APR_EGENERAL; + goto cleanup; + } + + if (!SSL_use_PrivateKey(ssl, key)) { + ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10087) + "error '%s' using Challenge key: %s", + ERR_error_string(ERR_peek_last_error(), NULL), + servername); + rv = APR_EGENERAL; + goto cleanup; + } + + if (SSL_check_private_key(ssl) < 1) { + ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10088) + "Challenge certificate and private key %s " + "do not match", servername); + rv = APR_EGENERAL; + goto cleanup; + } + +cleanup: + if (our_data && cert) X509_free(cert); + if (our_data && key) EVP_PKEY_free(key); + return APR_SUCCESS; +} + /* * This function sets the virtual host from an extended * client hello with a server name indication extension ("SNI", cf. RFC 6066). */ -static apr_status_t init_vhost(conn_rec *c, SSL *ssl) +static apr_status_t init_vhost(conn_rec *c, SSL *ssl, const char *servername) { - const char *servername; - X509 *cert; - EVP_PKEY *key; - if (c) { SSLConnRec *sslcon = myConnConfig(c); - - if (sslcon->server != c->base_server) { - /* already found the vhost */ - return APR_SUCCESS; + + if (sslcon->vhost_found) { + /* already found the vhost? */ + return sslcon->vhost_found > 0 ? APR_SUCCESS : APR_NOTFOUND; } + sslcon->vhost_found = -1; - servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) { + servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + } if (servername) { if (ap_vhost_iterate_given_conn(c, ssl_find_vhost, (void *)servername)) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02043) "SSL virtual host for servername %s found", servername); - + + sslcon->vhost_found = +1; return APR_SUCCESS; } - else if (ssl_is_challenge(c, servername, &cert, &key)) { - - sslcon->service_unavailable = 1; - if ((SSL_use_certificate(ssl, cert) < 1)) { - ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10086) - "Failed to configure challenge certificate %s", - servername); - return APR_EGENERAL; - } - - if (!SSL_use_PrivateKey(ssl, key)) { - ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10087) - "error '%s' using Challenge key: %s", - ERR_error_string(ERR_peek_last_error(), NULL), - servername); - return APR_EGENERAL; - } - - if (SSL_check_private_key(ssl) < 1) { - ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10088) - "Challenge certificate and private key %s " - "do not match", servername); - return APR_EGENERAL; - } - - } else { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02044) "No matching SSL virtual host for servername " @@ -2383,11 +2438,71 @@ static apr_status_t init_vhost(conn_rec *c, SSL *ssl) int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx) { conn_rec *c = (conn_rec *)SSL_get_app_data(ssl); - apr_status_t status = init_vhost(c, ssl); + apr_status_t status = init_vhost(c, ssl, NULL); return (status == APR_SUCCESS)? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_NOACK; } +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) +/* + * This callback function is called when the ClientHello is received. + */ +int ssl_callback_ClientHello(SSL *ssl, int *al, void *arg) +{ + char *servername = NULL; + conn_rec *c = (conn_rec *)SSL_get_app_data(ssl); + const unsigned char *pos; + size_t len, remaining; + (void)arg; + + /* We can't use SSL_get_servername() at this earliest OpenSSL connection + * stage, and there is no SSL_client_hello_get0_servername() provided as + * of OpenSSL 1.1.1. So the code below, that extracts the SNI from the + * ClientHello's TLS extensions, is taken from some test code in OpenSSL, + * i.e. client_hello_select_server_ctx() in "test/handshake_helper.c". + */ + + /* + * The server_name extension was given too much extensibility when it + * was written, so parsing the normal case is a bit complex. + */ + if (!SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &pos, + &remaining) + || remaining <= 2) + goto give_up; + + /* Extract the length of the supplied list of names. */ + len = (*(pos++) << 8); + len += *(pos++); + if (len + 2 != remaining) + goto give_up; + remaining = len; + + /* + * The list in practice only has a single element, so we only consider + * the first one. + */ + if (remaining <= 3 || *pos++ != TLSEXT_NAMETYPE_host_name) + goto give_up; + remaining--; + + /* Now we can finally pull out the byte array with the actual hostname. */ + len = (*(pos++) << 8); + len += *(pos++); + if (len + 2 != remaining) + goto give_up; + + /* Use the SNI to switch to the relevant vhost, should it differ from + * c->base_server. + */ + servername = apr_pstrmemdup(c->pool, (const char *)pos, len); + +give_up: + init_vhost(c, ssl, servername); + return SSL_CLIENT_HELLO_SUCCESS; +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10101000L */ + /* * Find a (name-based) SSL virtual host where either the ServerName * or one of the ServerAliases matches the supplied name (to be used @@ -2407,12 +2522,25 @@ static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s) if (found && (ssl = sslcon->ssl) && (sc = mySrvConfig(s))) { SSL_CTX *ctx = SSL_set_SSL_CTX(ssl, sc->server->ssl_ctx); + /* * SSL_set_SSL_CTX() only deals with the server cert, * so we need to duplicate a few additional settings * from the ctx by hand */ SSL_set_options(ssl, SSL_CTX_get_options(ctx)); +#if OPENSSL_VERSION_NUMBER >= 0x1010007fL \ + && (!defined(LIBRESSL_VERSION_NUMBER) \ + || LIBRESSL_VERSION_NUMBER >= 0x20800000L) + /* + * Don't switch the protocol if none is configured for this vhost, + * the default in this case is still the base server's SSLProtocol. + */ + if (myConnCtxConfig(c, sc)->protocol_set) { + SSL_set_min_proto_version(ssl, SSL_CTX_get_min_proto_version(ctx)); + SSL_set_max_proto_version(ssl, SSL_CTX_get_max_proto_version(ctx)); + } +#endif if ((SSL_get_verify_mode(ssl) == SSL_VERIFY_NONE) || (SSL_num_renegotiations(ssl) == 0)) { /* @@ -2453,6 +2581,7 @@ static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s) sc->server->pks->service_unavailable : 0; ap_update_child_status_from_server(c->sbh, SERVER_BUSY_READ, c, s); + /* * There is one special filter callback, which is set * very early depending on the base_server's log level. @@ -2461,14 +2590,7 @@ static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s) * we need to set that callback here. */ if (APLOGtrace4(s)) { - BIO *rbio = SSL_get_rbio(ssl), - *wbio = SSL_get_wbio(ssl); - BIO_set_callback(rbio, ssl_io_data_cb); - BIO_set_callback_arg(rbio, (void *)ssl); - if (wbio && wbio != rbio) { - BIO_set_callback(wbio, ssl_io_data_cb); - BIO_set_callback_arg(wbio, (void *)ssl); - } + modssl_set_io_callbacks(ssl); } return 1; @@ -2488,14 +2610,17 @@ int ssl_callback_SessionTicket(SSL *ssl, unsigned char *keyname, unsigned char *iv, EVP_CIPHER_CTX *cipher_ctx, - HMAC_CTX *hctx, +#if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX *hmac_ctx, +#else + EVP_MAC_CTX *mac_ctx, +#endif int mode) { conn_rec *c = (conn_rec *)SSL_get_app_data(ssl); server_rec *s = mySrvFromConn(c); SSLSrvConfigRec *sc = mySrvConfig(s); - SSLConnRec *sslconn = myConnConfig(c); - modssl_ctx_t *mctx = myCtxConfig(sslconn, sc); + modssl_ctx_t *mctx = myConnCtxConfig(c, sc); modssl_ticket_key_t *ticket_key = mctx->ticket_key; if (mode == 1) { @@ -2515,7 +2640,13 @@ int ssl_callback_SessionTicket(SSL *ssl, } EVP_EncryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL, ticket_key->aes_key, iv); - HMAC_Init_ex(hctx, ticket_key->hmac_secret, 16, tlsext_tick_md(), NULL); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_Init_ex(hmac_ctx, ticket_key->hmac_secret, 16, + tlsext_tick_md(), NULL); +#else + EVP_MAC_CTX_set_params(mac_ctx, ticket_key->mac_params); +#endif ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02289) "TLS session ticket key for %s successfully set, " @@ -2536,7 +2667,13 @@ int ssl_callback_SessionTicket(SSL *ssl, EVP_DecryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL, ticket_key->aes_key, iv); - HMAC_Init_ex(hctx, ticket_key->hmac_secret, 16, tlsext_tick_md(), NULL); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_Init_ex(hmac_ctx, ticket_key->hmac_secret, 16, + tlsext_tick_md(), NULL); +#else + EVP_MAC_CTX_set_params(mac_ctx, ticket_key->mac_params); +#endif ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02290) "TLS session ticket key for %s successfully set, " @@ -2609,7 +2746,7 @@ int ssl_callback_alpn_select(SSL *ssl, * they callback the SNI. We need to make sure that we know which vhost * we are dealing with so we respect the correct protocols. */ - init_vhost(c, ssl); + init_vhost(c, ssl, NULL); proposed = ap_select_protocol(c, NULL, sslconn->server, client_protos); if (!proposed) { @@ -2635,6 +2772,26 @@ int ssl_callback_alpn_select(SSL *ssl, proposed); return SSL_TLSEXT_ERR_ALERT_FATAL; } + + /* protocol was switched, this could be a challenge protocol such as "acme-tls/1". + * For that to work, we need to allow overrides to our ssl certificate. + * However, exclude challenge checks on our best known traffic protocol. + * (http/1.1 is the default, we never switch to it anyway.) + */ + if (strcmp("h2", proposed)) { + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + X509 *cert; + EVP_PKEY *key; + const char *cert_pem, *key_pem; + + if (ssl_is_challenge(c, servername, &cert, &key, &cert_pem, &key_pem)) { + if (set_challenge_creds(c, servername, ssl, cert, key, + cert_pem, key_pem) != APR_SUCCESS) { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + SSL_set_verify(ssl, SSL_VERIFY_NONE, ssl_callback_SSLVerify); + } + } } return SSL_TLSEXT_ERR_OK; @@ -2676,3 +2833,17 @@ int ssl_callback_SRPServerParams(SSL *ssl, int *ad, void *arg) } #endif /* HAVE_SRP */ + + +#ifdef HAVE_OPENSSL_KEYLOG +/* Callback used with SSL_CTX_set_keylog_callback. */ +void modssl_callback_keylog(const SSL *ssl, const char *line) +{ + conn_rec *conn = SSL_get_app_data(ssl); + SSLSrvConfigRec *sc = mySrvConfig(conn->base_server); + + if (sc && sc->mc->keylog_file) { + apr_file_printf(sc->mc->keylog_file, "%s\n", line); + } +} +#endif diff --git a/modules/ssl/ssl_engine_log.c b/modules/ssl/ssl_engine_log.c index d2f9ed0..3b3ceac 100644 --- a/modules/ssl/ssl_engine_log.c +++ b/modules/ssl/ssl_engine_log.c @@ -78,6 +78,16 @@ apr_status_t ssl_die(server_rec *s) return APR_EGENERAL; } +static APR_INLINE +unsigned long modssl_ERR_peek_error_data(const char **data, int *flags) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + return ERR_peek_error_line_data(NULL, NULL, data, flags); +#else + return ERR_peek_error_data(data, flags); +#endif +} + /* * Prints the SSL library error information. */ @@ -87,7 +97,7 @@ void ssl_log_ssl_error(const char *file, int line, int level, server_rec *s) const char *data; int flags; - while ((e = ERR_peek_error_line_data(NULL, NULL, &data, &flags))) { + while ((e = modssl_ERR_peek_error_data(&data, &flags))) { const char *annotation; char err[256]; @@ -123,10 +133,8 @@ static void ssl_log_cert_error(const char *file, int line, int level, int msglen, n; char *name; - apr_vsnprintf(buf, sizeof buf, format, ap); - - msglen = strlen(buf); - + msglen = apr_vsnprintf(buf, sizeof buf, format, ap); + if (cert) { BIO *bio = BIO_new(BIO_s_mem()); diff --git a/modules/ssl/ssl_engine_ocsp.c b/modules/ssl/ssl_engine_ocsp.c index ef92c37..5e04512 100644 --- a/modules/ssl/ssl_engine_ocsp.c +++ b/modules/ssl/ssl_engine_ocsp.c @@ -86,7 +86,7 @@ static apr_uri_t *determine_responder_uri(SSLSrvConfigRec *sc, X509 *cert, return NULL; } - if (strcasecmp(u->scheme, "http") != 0) { + if (ap_cstr_casecmp(u->scheme, "http") != 0) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(01920) "cannot handle OCSP responder URI '%s'", s); return NULL; @@ -284,6 +284,7 @@ int modssl_verify_ocsp(X509_STORE_CTX *ctx, SSLSrvConfigRec *sc, /* Create a temporary pool to constrain memory use (the passed-in * pool may be e.g. a connection pool). */ apr_pool_create(&vpool, pool); + apr_pool_tag(vpool, "modssl_verify_ocsp"); rv = verify_ocsp_status(cert, ctx, c, sc, s, vpool); diff --git a/modules/ssl/ssl_engine_pphrase.c b/modules/ssl/ssl_engine_pphrase.c index 8c29443..699019f 100644 --- a/modules/ssl/ssl_engine_pphrase.c +++ b/modules/ssl/ssl_engine_pphrase.c @@ -30,6 +30,8 @@ -- Clifford Stoll */ #include "ssl_private.h" +#include <openssl/ui.h> + typedef struct { server_rec *s; apr_pool_t *p; @@ -143,8 +145,6 @@ apr_status_t ssl_load_encrypted_pkey(server_rec *s, apr_pool_t *p, int idx, const char *key_id = asn1_table_vhost_key(mc, p, sc->vhost_id, idx); EVP_PKEY *pPrivateKey = NULL; ssl_asn1_t *asn1; - unsigned char *ucp; - long int length; int nPassPhrase = (*pphrases)->nelts; int nPassPhraseRetry = 0; apr_time_t pkey_mtime = 0; @@ -221,7 +221,7 @@ apr_status_t ssl_load_encrypted_pkey(server_rec *s, apr_pool_t *p, int idx, * is not empty. */ ERR_clear_error(); - pPrivateKey = modssl_read_privatekey(ppcb_arg.pkey_file, NULL, + pPrivateKey = modssl_read_privatekey(ppcb_arg.pkey_file, ssl_pphrase_Handle_CB, &ppcb_arg); /* If the private key was successfully read, nothing more to do here. */ @@ -351,19 +351,12 @@ apr_status_t ssl_load_encrypted_pkey(server_rec *s, apr_pool_t *p, int idx, nPassPhrase++; } - /* - * Insert private key into the global module configuration - * (we convert it to a stand-alone DER byte sequence - * because the SSL library uses static variables inside a - * RSA structure which do not survive DSO reloads!) - */ - length = i2d_PrivateKey(pPrivateKey, NULL); - ucp = ssl_asn1_table_set(mc->tPrivateKey, key_id, length); - (void)i2d_PrivateKey(pPrivateKey, &ucp); /* 2nd arg increments */ + /* Cache the private key in the global module configuration so it + * can be used after subsequent reloads. */ + asn1 = ssl_asn1_table_set(mc->tPrivateKey, key_id, pPrivateKey); if (ppcb_arg.nPassPhraseDialogCur != 0) { /* remember mtime of encrypted keys */ - asn1 = ssl_asn1_table_get(mc->tPrivateKey, key_id); asn1->source_mtime = pkey_mtime; } @@ -614,3 +607,306 @@ int ssl_pphrase_Handle_CB(char *buf, int bufsize, int verify, void *srv) */ return (len); } + +#if MODSSL_HAVE_ENGINE_API + +/* OpenSSL UI implementation for passphrase entry; largely duplicated + * from ssl_pphrase_Handle_CB but adjusted for UI API. TODO: Might be + * worth trying to shift pphrase handling over to the UI API + * completely. */ +static int passphrase_ui_open(UI *ui) +{ + pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui); + SSLSrvConfigRec *sc = mySrvConfig(ppcb->s); + + ppcb->nPassPhraseDialog++; + ppcb->nPassPhraseDialogCur++; + + /* + * Builtin or Pipe dialog + */ + if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN + || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) { + if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) { + if (!readtty) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, + APLOGNO(10143) + "Init: Creating pass phrase dialog pipe child " + "'%s'", sc->server->pphrase_dialog_path); + if (ssl_pipe_child_create(ppcb->p, + sc->server->pphrase_dialog_path) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb->s, + APLOGNO(10144) + "Init: Failed to create pass phrase pipe '%s'", + sc->server->pphrase_dialog_path); + return 0; + } + } + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10145) + "Init: Requesting pass phrase via piped dialog"); + } + else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */ +#ifdef WIN32 + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb->s, APLOGNO(10146) + "Init: Failed to create pass phrase pipe '%s'", + sc->server->pphrase_dialog_path); + return 0; +#else + /* + * stderr has already been redirected to the error_log. + * rather than attempting to temporarily rehook it to the terminal, + * we print the prompt to stdout before EVP_read_pw_string turns + * off tty echo + */ + apr_file_open_stdout(&writetty, ppcb->p); + + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10147) + "Init: Requesting pass phrase via builtin terminal " + "dialog"); +#endif + } + + /* + * The first time display a header to inform the user about what + * program he actually speaks to, which module is responsible for + * this terminal dialog and why to the hell he has to enter + * something... + */ + if (ppcb->nPassPhraseDialog == 1) { + apr_file_printf(writetty, "%s mod_ssl (Pass Phrase Dialog)\n", + AP_SERVER_BASEVERSION); + apr_file_printf(writetty, + "A pass phrase is required to access the private key.\n"); + } + if (ppcb->bPassPhraseDialogOnce) { + ppcb->bPassPhraseDialogOnce = FALSE; + apr_file_printf(writetty, "\n"); + apr_file_printf(writetty, "Private key %s (%s)\n", + ppcb->key_id, ppcb->pkey_file); + } + } + + return 1; +} + +static int passphrase_ui_read(UI *ui, UI_STRING *uis) +{ + pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui); + SSLSrvConfigRec *sc = mySrvConfig(ppcb->s); + const char *prompt; + int i; + int bufsize; + int len; + char *buf; + + prompt = UI_get0_output_string(uis); + if (prompt == NULL) { + prompt = "Enter pass phrase:"; + } + + /* + * Get the maximum expected size and allocate the buffer + */ + bufsize = UI_get_result_maxsize(uis); + buf = apr_pcalloc(ppcb->p, bufsize); + + if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN + || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) { + /* + * Get the pass phrase through a callback. + * Empty input is not accepted. + */ + for (;;) { + if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) { + i = pipe_get_passwd_cb(buf, bufsize, "", FALSE); + } + else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */ + i = EVP_read_pw_string(buf, bufsize, "", FALSE); + } + if (i != 0) { + OPENSSL_cleanse(buf, bufsize); + return 0; + } + len = strlen(buf); + if (len < 1){ + apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase" + "empty (needs to be at least 1 character).\n"); + apr_file_puts(prompt, writetty); + } + else { + break; + } + } + } + /* + * Filter program + */ + else if (sc->server->pphrase_dialog_type == SSL_PPTYPE_FILTER) { + const char *cmd = sc->server->pphrase_dialog_path; + const char **argv = apr_palloc(ppcb->p, sizeof(char *) * 3); + char *result; + + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10148) + "Init: Requesting pass phrase from dialog filter " + "program (%s)", cmd); + + argv[0] = cmd; + argv[1] = ppcb->key_id; + argv[2] = NULL; + + result = ssl_util_readfilter(ppcb->s, ppcb->p, cmd, argv); + apr_cpystrn(buf, result, bufsize); + len = strlen(buf); + } + + /* + * Ok, we now have the pass phrase, so give it back + */ + ppcb->cpPassPhraseCur = apr_pstrdup(ppcb->p, buf); + UI_set_result(ui, uis, buf); + + /* Clear sensitive data. */ + OPENSSL_cleanse(buf, bufsize); + return 1; +} + +static int passphrase_ui_write(UI *ui, UI_STRING *uis) +{ + pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui); + SSLSrvConfigRec *sc; + const char *prompt; + + sc = mySrvConfig(ppcb->s); + + if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN + || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) { + prompt = UI_get0_output_string(uis); + apr_file_puts(prompt, writetty); + } + + return 1; +} + +static int passphrase_ui_close(UI *ui) +{ + /* + * Close the pipes if they were opened + */ + if (readtty) { + apr_file_close(readtty); + apr_file_close(writetty); + readtty = writetty = NULL; + } + return 1; +} + +static apr_status_t pp_ui_method_cleanup(void *uip) +{ + UI_METHOD *uim = uip; + + UI_destroy_method(uim); + + return APR_SUCCESS; +} + +static UI_METHOD *get_passphrase_ui(apr_pool_t *p) +{ + UI_METHOD *ui_method = UI_create_method("Passphrase UI"); + + UI_method_set_opener(ui_method, passphrase_ui_open); + UI_method_set_reader(ui_method, passphrase_ui_read); + UI_method_set_writer(ui_method, passphrase_ui_write); + UI_method_set_closer(ui_method, passphrase_ui_close); + + apr_pool_cleanup_register(p, ui_method, pp_ui_method_cleanup, + pp_ui_method_cleanup); + + return ui_method; +} +#endif + + +apr_status_t modssl_load_engine_keypair(server_rec *s, apr_pool_t *p, + const char *vhostid, + const char *certid, const char *keyid, + X509 **pubkey, EVP_PKEY **privkey) +{ +#if MODSSL_HAVE_ENGINE_API + const char *c, *scheme; + ENGINE *e; + UI_METHOD *ui_method = get_passphrase_ui(p); + pphrase_cb_arg_t ppcb; + + memset(&ppcb, 0, sizeof ppcb); + ppcb.s = s; + ppcb.p = p; + ppcb.bPassPhraseDialogOnce = TRUE; + ppcb.key_id = vhostid; + ppcb.pkey_file = keyid; + + c = ap_strchr_c(keyid, ':'); + if (!c || c == keyid) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10131) + "Init: Unrecognized private key identifier `%s'", + keyid); + return ssl_die(s); + } + + scheme = apr_pstrmemdup(p, keyid, c - keyid); + if (!(e = ENGINE_by_id(scheme))) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10132) + "Init: Failed to load engine for private key %s", + keyid); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); + return ssl_die(s); + } + + if (!ENGINE_init(e)) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10149) + "Init: Failed to initialize engine %s for private key %s", + scheme, keyid); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); + return ssl_die(s); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "Init: Initialized engine %s for private key %s", + scheme, keyid); + + if (APLOGdebug(s)) { + ENGINE_ctrl_cmd_string(e, "VERBOSE", NULL, 0); + } + + if (certid) { + struct { + const char *cert_id; + X509 *cert; + } params = { certid, NULL }; + + if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, ¶ms, NULL, 1)) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10136) + "Init: Unable to get the certificate"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); + return ssl_die(s); + } + + *pubkey = params.cert; + } + + *privkey = ENGINE_load_private_key(e, keyid, ui_method, &ppcb); + if (*privkey == NULL) { + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10133) + "Init: Unable to get the private key"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); + return ssl_die(s); + } + + ENGINE_finish(e); + ENGINE_free(e); + + return APR_SUCCESS; +#else + return APR_ENOTIMPL; +#endif +} diff --git a/modules/ssl/ssl_engine_vars.c b/modules/ssl/ssl_engine_vars.c index 5724f18..418d849 100644 --- a/modules/ssl/ssl_engine_vars.c +++ b/modules/ssl/ssl_engine_vars.c @@ -65,10 +65,10 @@ static SSLConnRec *ssl_get_effective_config(conn_rec *c) return sslconn; } -static int ssl_is_https(conn_rec *c) +static int ssl_conn_is_ssl(conn_rec *c) { - SSLConnRec *sslconn = ssl_get_effective_config(c); - return sslconn && sslconn->ssl; + const SSLConnRec *sslconn = ssl_get_effective_config(c); + return (sslconn && sslconn->ssl)? OK : DECLINED; } static const char var_interface[] = "mod_ssl/" AP_SERVER_BASEREVISION; @@ -137,7 +137,7 @@ void ssl_var_register(apr_pool_t *p) { char *cp, *cp2; - APR_REGISTER_OPTIONAL_FN(ssl_is_https); + ap_hook_ssl_conn_is_ssl(ssl_conn_is_ssl, NULL, NULL, APR_HOOK_MIDDLE); APR_REGISTER_OPTIONAL_FN(ssl_var_lookup); APR_REGISTER_OPTIONAL_FN(ssl_ext_list); @@ -460,18 +460,13 @@ static char *ssl_var_lookup_ssl_cert_dn_oneline(apr_pool_t *p, request_rec *r, } else { BIO* bio; - int n; unsigned long flags = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB; + if ((bio = BIO_new(BIO_s_mem())) == NULL) return NULL; X509_NAME_print_ex(bio, xsname, 0, flags); - n = BIO_pending(bio); - if (n > 0) { - result = apr_palloc(p, n+1); - n = BIO_read(bio, result, n); - result[n] = NUL; - } - BIO_free(bio); + + result = modssl_bio_free_read(p, bio); } return result; } @@ -635,7 +630,8 @@ static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var) { - int type, numlen; + int type; + apr_size_t numlen; const char *onf = NULL; apr_array_header_t *entries; @@ -678,19 +674,13 @@ static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var) static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm) { - char *result; BIO* bio; - int n; if ((bio = BIO_new(BIO_s_mem())) == NULL) return NULL; ASN1_TIME_print(bio, tm); - n = BIO_pending(bio); - result = apr_pcalloc(p, n+1); - n = BIO_read(bio, result, n); - result[n] = NUL; - BIO_free(bio); - return result; + + return modssl_bio_free_read(p, bio); } #define DIGIT2NUM(x) (((x)[0] - '0') * 10 + (x)[1] - '0') @@ -739,19 +729,13 @@ static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm) static char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs) { - char *result; BIO *bio; - int n; if ((bio = BIO_new(BIO_s_mem())) == NULL) return NULL; i2a_ASN1_INTEGER(bio, X509_get_serialNumber(xs)); - n = BIO_pending(bio); - result = apr_pcalloc(p, n+1); - n = BIO_read(bio, result, n); - result[n] = NUL; - BIO_free(bio); - return result; + + return modssl_bio_free_read(p, bio); } static char *ssl_var_lookup_ssl_cert_chain(apr_pool_t *p, STACK_OF(X509) *sk, char *var) @@ -806,19 +790,13 @@ static char *ssl_var_lookup_ssl_cert_rfc4523_cea(apr_pool_t *p, SSL *ssl) static char *ssl_var_lookup_ssl_cert_PEM(apr_pool_t *p, X509 *xs) { - char *result; BIO *bio; - int n; if ((bio = BIO_new(BIO_s_mem())) == NULL) return NULL; PEM_write_bio_X509(bio, xs); - n = BIO_pending(bio); - result = apr_pcalloc(p, n+1); - n = BIO_read(bio, result, n); - result[n] = NUL; - BIO_free(bio); - return result; + + return modssl_bio_free_read(p, bio); } static char *ssl_var_lookup_ssl_cert_verify(apr_pool_t *p, SSLConnRec *sslconn) diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index f46814d..859e932 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -27,6 +27,7 @@ */ /** Apache headers */ +#include "ap_config.h" #include "httpd.h" #include "http_config.h" #include "http_core.h" @@ -35,6 +36,7 @@ #include "http_connection.h" #include "http_request.h" #include "http_protocol.h" +#include "http_ssl.h" #include "http_vhost.h" #include "util_script.h" #include "util_filter.h" @@ -81,13 +83,13 @@ #include "ap_expr.h" -/* OpenSSL headers */ -#include <openssl/opensslv.h> -#if (OPENSSL_VERSION_NUMBER >= 0x10001000) -/* must be defined before including ssl.h */ -#define OPENSSL_NO_SSL_INTERN +/* keep first for compat API */ +#ifndef OPENSSL_API_COMPAT +#define OPENSSL_API_COMPAT 0x10101000 /* for ENGINE_ API */ #endif -#include <openssl/ssl.h> +#include "mod_ssl_openssl.h" + +/* OpenSSL headers */ #include <openssl/err.h> #include <openssl/x509.h> #include <openssl/pem.h> @@ -97,12 +99,23 @@ #include <openssl/x509v3.h> #include <openssl/x509_vfy.h> #include <openssl/ocsp.h> +#include <openssl/dh.h> +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#include <openssl/core_names.h> +#endif /* Avoid tripping over an engine build installed globally and detected * when the user points at an explicit non-engine flavor of OpenSSL */ -#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT) +#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT) \ + && (OPENSSL_VERSION_NUMBER < 0x30000000 \ + || (defined(OPENSSL_API_LEVEL) && OPENSSL_API_LEVEL < 30000)) \ + && !defined(OPENSSL_NO_ENGINE) #include <openssl/engine.h> +#define MODSSL_HAVE_ENGINE_API 1 +#endif +#ifndef MODSSL_HAVE_ENGINE_API +#define MODSSL_HAVE_ENGINE_API 0 #endif #if (OPENSSL_VERSION_NUMBER < 0x0090801f) @@ -132,18 +145,25 @@ SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, version, NULL) #define SSL_CTX_set_max_proto_version(ctx, version) \ SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MAX_PROTO_VERSION, version, NULL) -#elif LIBRESSL_VERSION_NUMBER < 0x2070000f +#endif /* LIBRESSL_VERSION_NUMBER < 0x2060000f */ /* LibreSSL before 2.7 declares OPENSSL_VERSION_NUMBER == 2.0 but does not * include most changes from OpenSSL >= 1.1 (new functions, macros, * deprecations, ...), so we have to work around this... */ -#define MODSSL_USE_OPENSSL_PRE_1_1_API (1) -#endif /* LIBRESSL_VERSION_NUMBER < 0x2060000f */ +#if LIBRESSL_VERSION_NUMBER < 0x2070000f +#define MODSSL_USE_OPENSSL_PRE_1_1_API 1 +#else +#define MODSSL_USE_OPENSSL_PRE_1_1_API 0 +#endif #else /* defined(LIBRESSL_VERSION_NUMBER) */ -#define MODSSL_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define MODSSL_USE_OPENSSL_PRE_1_1_API 1 +#else +#define MODSSL_USE_OPENSSL_PRE_1_1_API 0 #endif +#endif /* defined(LIBRESSL_VERSION_NUMBER) */ -#if defined(OPENSSL_FIPS) +#if defined(OPENSSL_FIPS) || OPENSSL_VERSION_NUMBER >= 0x30000000L #define HAVE_FIPS #endif @@ -207,7 +227,10 @@ #endif /* Secure Remote Password */ -#if !defined(OPENSSL_NO_SRP) && defined(SSL_CTRL_SET_TLS_EXT_SRP_USERNAME_CB) +#if !defined(OPENSSL_NO_SRP) \ + && (OPENSSL_VERSION_NUMBER < 0x30000000L \ + || (defined(OPENSSL_API_LEVEL) && OPENSSL_API_LEVEL < 30000)) \ + && defined(SSL_CTRL_SET_TLS_EXT_SRP_USERNAME_CB) #define HAVE_SRP #include <openssl/srp.h> #endif @@ -250,6 +273,28 @@ void free_bio_methods(void); #endif #endif +/* those may be deprecated */ +#ifndef X509_get_notBefore +#define X509_get_notBefore X509_getm_notBefore +#endif +#ifndef X509_get_notAfter +#define X509_get_notAfter X509_getm_notAfter +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) +#define HAVE_OPENSSL_KEYLOG +#endif + +#ifdef HAVE_FIPS +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#define modssl_fips_is_enabled() EVP_default_properties_is_fips_enabled(NULL) +#define modssl_fips_enable(to) EVP_default_properties_enable_fips(NULL, (to)) +#else +#define modssl_fips_is_enabled() FIPS_mode() +#define modssl_fips_enable(to) FIPS_mode_set((to)) +#endif +#endif /* HAVE_FIPS */ + /* mod_ssl headers */ #include "ssl_util_ssl.h" @@ -305,8 +350,8 @@ APLOG_USE_MODULE(ssl); ((SSLSrvConfigRec *)ap_get_module_config(srv->module_config, &ssl_module)) #define myDirConfig(req) \ ((SSLDirConfigRec *)ap_get_module_config(req->per_dir_config, &ssl_module)) -#define myCtxConfig(sslconn, sc) \ - (sslconn->is_proxy ? sslconn->dc->proxy : sc->server) +#define myConnCtxConfig(c, sc) \ + (c->outgoing ? myConnConfig(c)->dc->proxy : sc->server) #define myModConfig(srv) mySrvConfig((srv))->mc #define mySrvFromConn(c) myConnConfig(c)->server #define myDirConfigFromConn(c) myConnConfig(c)->dc @@ -527,7 +572,6 @@ typedef struct { const char *verify_info; const char *verify_error; int verify_depth; - int is_proxy; int disabled; enum { NON_SSL_OK = 0, /* is SSL request, or error handling completed */ @@ -554,6 +598,7 @@ typedef struct { const char *cipher_suite; /* cipher suite used in last reneg */ int service_unavailable; /* thouugh we negotiate SSL, no requests will be served */ + int vhost_found; /* whether we found vhost from SNI already */ } SSLConnRec; /* BIG FAT WARNING: SSLModConfigRec has unusual memory lifetime: it is @@ -607,9 +652,7 @@ typedef struct { * index), for example the string "vhost.example.com:443:0". */ apr_hash_t *tPrivateKey; -#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT) - const char *szCryptoDevice; -#endif + const char *szCryptoDevice; /* ENGINE device (if available) */ #ifdef HAVE_OCSP_STAPLING const ap_socache_provider_t *stapling_cache; @@ -617,6 +660,14 @@ typedef struct { apr_global_mutex_t *stapling_cache_mutex; apr_global_mutex_t *stapling_refresh_mutex; #endif +#ifdef HAVE_OPENSSL_KEYLOG + /* Used for logging if SSLKEYLOGFILE is set at startup. */ + apr_file_t *keylog_file; +#endif + +#ifdef HAVE_FIPS + BOOL fips; +#endif } SSLModConfigRec; /** Structure representing configured filenames for certs and keys for @@ -640,10 +691,13 @@ typedef struct { const char *cert_file; const char *cert_path; const char *ca_cert_file; - STACK_OF(X509_INFO) *certs; /* Contains End Entity certs */ - STACK_OF(X509) **ca_certs; /* Contains ONLY chain certs for - * each item in certs. - * (ptr to array of ptrs) */ + /* certs is a stack of configured cert, key pairs. */ + STACK_OF(X509_INFO) *certs; + /* ca_certs contains ONLY chain certs for each item in certs. + * ca_certs[n] is a pointer to the (STACK_OF(X509) *) stack which + * holds the cert chain for the 'n'th cert in the certs stack, or + * NULL if no chain is configured. */ + STACK_OF(X509) **ca_certs; } modssl_pk_proxy_t; /** stuff related to authentication that can also be per-dir */ @@ -668,7 +722,11 @@ typedef struct { typedef struct { const char *file_path; unsigned char key_name[16]; +#if OPENSSL_VERSION_NUMBER < 0x30000000L unsigned char hmac_secret[16]; +#else + OSSL_PARAM mac_params[3]; +#endif unsigned char aes_key[16]; } modssl_ticket_key_t; #endif @@ -765,9 +823,6 @@ struct SSLSrvConfigRec { #ifdef HAVE_TLSEXT ssl_enabled_t strict_sni_vhost_check; #endif -#ifdef HAVE_FIPS - BOOL fips; -#endif #ifndef OPENSSL_NO_COMP BOOL compression; #endif @@ -928,9 +983,20 @@ void ssl_callback_Info(const SSL *, int, int); #ifdef HAVE_TLSEXT int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *); #endif +#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) +int ssl_callback_ClientHello(SSL *, int *, void *); +#endif #ifdef HAVE_TLS_SESSION_TICKETS -int ssl_callback_SessionTicket(SSL *, unsigned char *, unsigned char *, - EVP_CIPHER_CTX *, HMAC_CTX *, int); +int ssl_callback_SessionTicket(SSL *ssl, + unsigned char *keyname, + unsigned char *iv, + EVP_CIPHER_CTX *cipher_ctx, +#if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX *hmac_ctx, +#else + EVP_MAC_CTX *mac_ctx, +#endif + int mode); #endif #ifdef HAVE_TLS_ALPN @@ -970,10 +1036,15 @@ int ssl_stapling_init_cert(server_rec *, apr_pool_t *, apr_pool_t *, int ssl_callback_SRPServerParams(SSL *, int *, void *); #endif +#ifdef HAVE_OPENSSL_KEYLOG +/* Callback used with SSL_CTX_set_keylog_callback. */ +void modssl_callback_keylog(const SSL *ssl, const char *line); +#endif + /** I/O */ void ssl_io_filter_init(conn_rec *, request_rec *r, SSL *); void ssl_io_filter_register(apr_pool_t *); -long ssl_io_data_cb(BIO *, int, const char *, int, long, long); +void modssl_set_io_callbacks(SSL *ssl); /* ssl_io_buffer_fill fills the setaside buffering of the HTTP request * to allow an SSL renegotiation to take place. */ @@ -1002,21 +1073,32 @@ BOOL ssl_util_vhost_matches(const char *servername, server_rec *s); apr_status_t ssl_load_encrypted_pkey(server_rec *, apr_pool_t *, int, const char *, apr_array_header_t **); +/* Load public and/or private key from the configured ENGINE. Private + * key returned as *pkey. certid can be NULL, in which case *pubkey + * is not altered. Errors logged on failure. */ +apr_status_t modssl_load_engine_keypair(server_rec *s, apr_pool_t *p, + const char *vhostid, + const char *certid, const char *keyid, + X509 **pubkey, EVP_PKEY **privkey); + /** Diffie-Hellman Parameter Support */ -DH *ssl_dh_GetParamFromFile(const char *); +#if OPENSSL_VERSION_NUMBER < 0x30000000L +DH *modssl_dh_from_file(const char *); +#else +EVP_PKEY *modssl_dh_pkey_from_file(const char *); +#endif #ifdef HAVE_ECC -EC_GROUP *ssl_ec_GetParamFromFile(const char *); +EC_GROUP *modssl_ec_group_from_file(const char *); #endif -unsigned char *ssl_asn1_table_set(apr_hash_t *table, - const char *key, - long int length); - -ssl_asn1_t *ssl_asn1_table_get(apr_hash_t *table, - const char *key); - -void ssl_asn1_table_unset(apr_hash_t *table, - const char *key); +/* Store the EVP_PKEY key (serialized into DER) in the hash table with + * key, returning the ssl_asn1_t structure pointer. */ +ssl_asn1_t *ssl_asn1_table_set(apr_hash_t *table, const char *key, + EVP_PKEY *pkey); +/* Retrieve the ssl_asn1_t structure with given key from the hash. */ +ssl_asn1_t *ssl_asn1_table_get(apr_hash_t *table, const char *key); +/* Remove and free the ssl_asn1_t structure with given key. */ +void ssl_asn1_table_unset(apr_hash_t *table, const char *key); /** Mutex Support */ int ssl_mutex_init(server_rec *, apr_pool_t *); @@ -1096,10 +1178,12 @@ void ssl_init_ocsp_certificates(server_rec *s, modssl_ctx_t *mctx); #endif +#if MODSSL_USE_OPENSSL_PRE_1_1_API /* Retrieve DH parameters for given key length. Return value should * be treated as unmutable, since it is stored in process-global * memory. */ DH *modssl_get_dh_params(unsigned keylen); +#endif /* Returns non-zero if the request was made over SSL/TLS. If sslconn * is non-NULL and the request is using SSL/TLS, sets *sslconn to the @@ -1107,7 +1191,12 @@ DH *modssl_get_dh_params(unsigned keylen); int modssl_request_is_tls(const request_rec *r, SSLConnRec **sslconn); int ssl_is_challenge(conn_rec *c, const char *servername, - X509 **pcert, EVP_PKEY **pkey); + X509 **pcert, EVP_PKEY **pkey, + const char **pcert_file, const char **pkey_file); + +/* Returns non-zero if the cert/key filename should be handled through + * the configured ENGINE. */ +int modssl_is_engine_id(const char *name); #endif /* SSL_PRIVATE_H */ /** @} */ diff --git a/modules/ssl/ssl_scache.c b/modules/ssl/ssl_scache.c index 7b4a203..c0a0950 100644 --- a/modules/ssl/ssl_scache.c +++ b/modules/ssl/ssl_scache.c @@ -59,7 +59,7 @@ apr_status_t ssl_scache_init(server_rec *s, apr_pool_t *p) hints.expiry_interval = 300; rv = mc->stapling_cache->init(mc->stapling_cache_context, - "mod_ssl-stapling", &hints, s, p); + "mod_ssl-staple", &hints, s, p); if (rv) { ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01872) "Could not initialize stapling cache. Exiting."); @@ -84,7 +84,7 @@ apr_status_t ssl_scache_init(server_rec *s, apr_pool_t *p) hints.avg_id_len = 30; hints.expiry_interval = 30; - rv = mc->sesscache->init(mc->sesscache_context, "mod_ssl-session", &hints, s, p); + rv = mc->sesscache->init(mc->sesscache_context, "mod_ssl-sess", &hints, s, p); if (rv) { ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01874) "Could not initialize session cache. Exiting."); diff --git a/modules/ssl/ssl_util.c b/modules/ssl/ssl_util.c index 0d23465..87ddfa7 100644 --- a/modules/ssl/ssl_util.c +++ b/modules/ssl/ssl_util.c @@ -192,45 +192,37 @@ BOOL ssl_util_path_check(ssl_pathcheck_t pcm, const char *path, apr_pool_t *p) return TRUE; } -/* - * certain key data needs to survive restarts, - * which are stored in the user data table of s->process->pool. - * to prevent "leaking" of this data, we use malloc/free - * rather than apr_palloc and these wrappers to help make sure - * we do not leak the malloc-ed data. - */ -unsigned char *ssl_asn1_table_set(apr_hash_t *table, - const char *key, - long int length) +/* Decrypted private keys are cached to survive restarts. The cached + * data must have lifetime of the process (hence malloc/free rather + * than pools), and uses raw DER since the EVP_PKEY structure + * internals may not survive across a module reload. */ +ssl_asn1_t *ssl_asn1_table_set(apr_hash_t *table, const char *key, + EVP_PKEY *pkey) { apr_ssize_t klen = strlen(key); ssl_asn1_t *asn1 = apr_hash_get(table, key, klen); + apr_size_t length = i2d_PrivateKey(pkey, NULL); + unsigned char *p; - /* - * if a value for this key already exists, - * reuse as much of the already malloc-ed data - * as possible. - */ + /* Re-use structure if cached previously. */ if (asn1) { if (asn1->nData != length) { - free(asn1->cpData); /* XXX: realloc? */ - asn1->cpData = NULL; + asn1->cpData = ap_realloc(asn1->cpData, length); } } else { asn1 = ap_malloc(sizeof(*asn1)); asn1->source_mtime = 0; /* used as a note for encrypted private keys */ - asn1->cpData = NULL; - } - - asn1->nData = length; - if (!asn1->cpData) { asn1->cpData = ap_malloc(length); + + apr_hash_set(table, key, klen, asn1); } - apr_hash_set(table, key, klen, asn1); + asn1->nData = length; + p = asn1->cpData; + i2d_PrivateKey(pkey, &p); /* increases p by length */ - return asn1->cpData; /* caller will assign a value to this */ + return asn1; } ssl_asn1_t *ssl_asn1_table_get(apr_hash_t *table, @@ -306,6 +298,7 @@ static struct CRYPTO_dynlock_value *ssl_dyn_create_function(const char *file, * away in the destruction callback. */ apr_pool_create(&p, dynlockpool); + apr_pool_tag(p, "modssl_dynlock_value"); ap_log_perror(file, line, APLOG_MODULE_INDEX, APLOG_TRACE1, 0, p, "Creating dynamic lock"); @@ -480,3 +473,13 @@ void ssl_util_thread_id_setup(apr_pool_t *p) } #endif /* #if APR_HAS_THREADS && MODSSL_USE_OPENSSL_PRE_1_1_API */ + +int modssl_is_engine_id(const char *name) +{ +#if MODSSL_HAVE_ENGINE_API + /* ### Can handle any other special ENGINE key names here? */ + return strncmp(name, "pkcs11:", 7) == 0; +#else + return 0; +#endif +} diff --git a/modules/ssl/ssl_util_ocsp.c b/modules/ssl/ssl_util_ocsp.c index b66e151..a202a72 100644 --- a/modules/ssl/ssl_util_ocsp.c +++ b/modules/ssl/ssl_util_ocsp.c @@ -46,6 +46,7 @@ static BIO *serialize_request(OCSP_REQUEST *req, const apr_uri_t *uri, BIO_printf(bio, "%s%s%s HTTP/1.0\r\n" "Host: %s:%d\r\n" "Content-Type: application/ocsp-request\r\n" + "Connection: close\r\n" "Content-Length: %d\r\n" "\r\n", uri->path ? uri->path : "/", @@ -369,8 +370,11 @@ static STACK_OF(X509) *modssl_read_ocsp_certificates(const char *file) while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) { if (!other_certs) { other_certs = sk_X509_new_null(); - if (!other_certs) + if (!other_certs) { + X509_free(x509); + BIO_free(bio); return NULL; + } } if (!sk_X509_push(other_certs, x509)) { diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c index b7f0eca..44930b7 100644 --- a/modules/ssl/ssl_util_ssl.c +++ b/modules/ssl/ssl_util_ssl.c @@ -74,7 +74,7 @@ void modssl_set_app_data2(SSL *ssl, void *arg) ** _________________________________________________________________ */ -EVP_PKEY *modssl_read_privatekey(const char* filename, EVP_PKEY **key, pem_password_cb *cb, void *s) +EVP_PKEY *modssl_read_privatekey(const char *filename, pem_password_cb *cb, void *s) { EVP_PKEY *rc; BIO *bioS; @@ -83,7 +83,7 @@ EVP_PKEY *modssl_read_privatekey(const char* filename, EVP_PKEY **key, pem_passw /* 1. try PEM (= DER+Base64+headers) */ if ((bioS=BIO_new_file(filename, "r")) == NULL) return NULL; - rc = PEM_read_bio_PrivateKey(bioS, key, cb, s); + rc = PEM_read_bio_PrivateKey(bioS, NULL, cb, s); BIO_free(bioS); if (rc == NULL) { @@ -107,41 +107,9 @@ EVP_PKEY *modssl_read_privatekey(const char* filename, EVP_PKEY **key, pem_passw BIO_free(bioS); } } - if (rc != NULL && key != NULL) { - if (*key != NULL) - EVP_PKEY_free(*key); - *key = rc; - } return rc; } -typedef struct { - const char *pass; - int pass_len; -} pass_ctx; - -static int provide_pass(char *buf, int size, int rwflag, void *baton) -{ - pass_ctx *ctx = baton; - if (ctx->pass_len > 0) { - if (ctx->pass_len < size) { - size = (int)ctx->pass_len; - } - memcpy(buf, ctx->pass, size); - } - return ctx->pass_len; -} - -EVP_PKEY *modssl_read_encrypted_pkey(const char *filename, EVP_PKEY **key, - const char *pass, apr_size_t pass_len) -{ - pass_ctx ctx; - - ctx.pass = pass; - ctx.pass_len = pass_len; - return modssl_read_privatekey(filename, key, provide_pass, &ctx); -} - /* _________________________________________________________________ ** ** Smart shutdown @@ -156,7 +124,7 @@ int modssl_smart_shutdown(SSL *ssl) /* * Repeat the calls, because SSL_shutdown internally dispatches through a - * little state machine. Usually only one or two interation should be + * little state machine. Usually only one or two iterations should be * needed, so we restrict the total number of restrictions in order to * avoid process hangs in case the client played bad with the socket * connection and OpenSSL cannot recognize it. @@ -166,7 +134,7 @@ int modssl_smart_shutdown(SSL *ssl) for (i = 0; i < 4 /* max 2x pending + 2x data = 4 */; i++) { rc = SSL_shutdown(ssl); if (rc >= 0 && flush && (SSL_get_shutdown(ssl) & SSL_SENT_SHUTDOWN)) { - /* Once the close notity is sent through the output filters, + /* Once the close notify is sent through the output filters, * ensure it is flushed through the socket. */ if (BIO_flush(SSL_get_wbio(ssl)) <= 0) { @@ -217,14 +185,27 @@ BOOL modssl_X509_getBC(X509 *cert, int *ca, int *pathlen) return TRUE; } +char *modssl_bio_free_read(apr_pool_t *p, BIO *bio) +{ + int len = BIO_pending(bio); + char *result = NULL; + + if (len > 0) { + result = apr_palloc(p, len+1); + len = BIO_read(bio, result, len); + result[len] = NUL; + } + BIO_free(bio); + return result; +} + /* Convert ASN.1 string to a pool-allocated char * string, escaping * control characters. If raw is zero, convert to UTF-8, otherwise * unchanged from the character set. */ static char *asn1_string_convert(apr_pool_t *p, ASN1_STRING *asn1str, int raw) { - char *result = NULL; BIO *bio; - int len, flags = ASN1_STRFLGS_ESC_CTRL; + int flags = ASN1_STRFLGS_ESC_CTRL; if ((bio = BIO_new(BIO_s_mem())) == NULL) return NULL; @@ -232,14 +213,8 @@ static char *asn1_string_convert(apr_pool_t *p, ASN1_STRING *asn1str, int raw) if (!raw) flags |= ASN1_STRFLGS_UTF8_CONVERT; ASN1_STRING_print_ex(bio, asn1str, flags); - len = BIO_pending(bio); - if (len > 0) { - result = apr_palloc(p, len+1); - len = BIO_read(bio, result, len); - result[len] = NUL; - } - BIO_free(bio); - return result; + + return modssl_bio_free_read(p, bio); } #define asn1_string_to_utf8(p, a) asn1_string_convert(p, a, 0) @@ -489,29 +464,52 @@ BOOL modssl_X509_match_name(apr_pool_t *p, X509 *x509, const char *name, ** _________________________________________________________________ */ -DH *ssl_dh_GetParamFromFile(const char *file) +#if OPENSSL_VERSION_NUMBER < 0x30000000L +DH *modssl_dh_from_file(const char *file) { - DH *dh = NULL; + DH *dh; BIO *bio; if ((bio = BIO_new_file(file, "r")) == NULL) return NULL; dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); - return (dh); + + return dh; +} +#else +EVP_PKEY *modssl_dh_pkey_from_file(const char *file) +{ + EVP_PKEY *pkey; + BIO *bio; + + if ((bio = BIO_new_file(file, "r")) == NULL) + return NULL; + pkey = PEM_read_bio_Parameters(bio, NULL); + BIO_free(bio); + + return pkey; } +#endif #ifdef HAVE_ECC -EC_GROUP *ssl_ec_GetParamFromFile(const char *file) +EC_GROUP *modssl_ec_group_from_file(const char *file) { - EC_GROUP *group = NULL; + EC_GROUP *group; BIO *bio; if ((bio = BIO_new_file(file, "r")) == NULL) return NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L group = PEM_read_bio_ECPKParameters(bio, NULL, NULL, NULL); +#else + group = PEM_ASN1_read_bio((void *)d2i_ECPKParameters, + PEM_STRING_ECPARAMETERS, bio, + NULL, NULL, NULL); +#endif BIO_free(bio); - return (group); + + return group; } #endif @@ -536,3 +534,81 @@ char *modssl_SSL_SESSION_id2sz(IDCONST unsigned char *id, int idlen, return str; } + +/* _________________________________________________________________ +** +** Certificate/Key Stuff +** _________________________________________________________________ +*/ + +apr_status_t modssl_read_cert(apr_pool_t *p, + const char *cert_pem, const char *key_pem, + pem_password_cb *cb, void *ud, + X509 **pcert, EVP_PKEY **pkey) +{ + BIO *in; + X509 *x = NULL; + EVP_PKEY *key = NULL; + apr_status_t rv = APR_SUCCESS; + + in = BIO_new_mem_buf(cert_pem, -1); + if (in == NULL) { + rv = APR_ENOMEM; + goto cleanup; + } + + x = PEM_read_bio_X509(in, NULL, cb, ud); + if (x == NULL) { + rv = APR_ENOENT; + goto cleanup; + } + + BIO_free(in); + in = BIO_new_mem_buf(key_pem? key_pem : cert_pem, -1); + if (in == NULL) { + rv = APR_ENOMEM; + goto cleanup; + } + key = PEM_read_bio_PrivateKey(in, NULL, cb, ud); + if (key == NULL) { + rv = APR_ENOENT; + goto cleanup; + } + +cleanup: + if (rv == APR_SUCCESS) { + *pcert = x; + *pkey = key; + } + else { + *pcert = NULL; + *pkey = NULL; + if (x) X509_free(x); + if (key) EVP_PKEY_free(key); + } + if (in != NULL) BIO_free(in); + return rv; +} + +apr_status_t modssl_cert_get_pem(apr_pool_t *p, + X509 *cert1, X509 *cert2, + const char **ppem) +{ + apr_status_t rv = APR_ENOMEM; + BIO *bio; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) goto cleanup; + if (PEM_write_bio_X509(bio, cert1) != 1) goto cleanup; + if (cert2 && PEM_write_bio_X509(bio, cert2) != 1) goto cleanup; + rv = APR_SUCCESS; + +cleanup: + if (rv != APR_SUCCESS) { + *ppem = NULL; + if (bio) BIO_free(bio); + } + else { + *ppem = modssl_bio_free_read(p, bio); + } + return rv; +} diff --git a/modules/ssl/ssl_util_ssl.h b/modules/ssl/ssl_util_ssl.h index c67dacf..443c1b7 100644 --- a/modules/ssl/ssl_util_ssl.h +++ b/modules/ssl/ssl_util_ssl.h @@ -64,8 +64,11 @@ void modssl_init_app_data2_idx(void); void *modssl_get_app_data2(SSL *); void modssl_set_app_data2(SSL *, void *); -EVP_PKEY *modssl_read_privatekey(const char *, EVP_PKEY **, pem_password_cb *, void *); -EVP_PKEY *modssl_read_encrypted_pkey(const char *, EVP_PKEY **, const char *, apr_size_t); + +/* Read private key from filename in either PEM or raw base64(DER) + * format, using password entry callback cb and userdata. */ +EVP_PKEY *modssl_read_privatekey(const char *filename, pem_password_cb *cb, void *ud); + int modssl_smart_shutdown(SSL *ssl); BOOL modssl_X509_getBC(X509 *, int *, int *); char *modssl_X509_NAME_ENTRY_to_string(apr_pool_t *p, X509_NAME_ENTRY *xsne, @@ -75,6 +78,30 @@ BOOL modssl_X509_getSAN(apr_pool_t *, X509 *, int, const char *, int, apr BOOL modssl_X509_match_name(apr_pool_t *, X509 *, const char *, BOOL, server_rec *); char *modssl_SSL_SESSION_id2sz(IDCONST unsigned char *, int, char *, int); +/* Reads the remaining data in BIO, if not empty, and copies it into a + * pool-allocated string. If empty, returns NULL. BIO_free(bio) is + * called for both cases. */ +char *modssl_bio_free_read(apr_pool_t *p, BIO *bio); + +/* Read a single certificate and its private key from the given string in PEM format. + * If `key_pem` is NULL, it will expect the key in `cert_pem`. + */ +apr_status_t modssl_read_cert(apr_pool_t *p, + const char *cert_pem, const char *key_pem, + pem_password_cb *cb, void *ud, + X509 **pcert, EVP_PKEY **pkey); + +/* Convert a certificate (and optionally a second) into a PEM string. + * @param p pool for allocations + * @param cert1 the certificate to convert + * @param cert2 a second cert to add to the PEM afterwards or NULL. + * @param ppem the certificate(s) in PEM format, NUL-terminated. + * @return APR_SUCCESS if ppem is valid. + */ +apr_status_t modssl_cert_get_pem(apr_pool_t *p, + X509 *cert1, X509 *cert2, + const char **ppem); + #endif /* __SSL_UTIL_SSL_H__ */ /** @} */ diff --git a/modules/ssl/ssl_util_stapling.c b/modules/ssl/ssl_util_stapling.c index c3e2cfa..563de55 100644 --- a/modules/ssl/ssl_util_stapling.c +++ b/modules/ssl/ssl_util_stapling.c @@ -29,16 +29,32 @@ -- Alexei Sayle */ #include "ssl_private.h" + #include "ap_mpm.h" #include "apr_thread_mutex.h" +APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, init_stapling_status, + (server_rec *s, apr_pool_t *p, + X509 *cert, X509 *issuer), + (s, p, cert, issuer), + DECLINED, DECLINED) + +APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ssl, SSL, int, get_stapling_status, + (unsigned char **pder, int *pderlen, + conn_rec *c, server_rec *s, X509 *cert), + (pder, pderlen, c, s, cert), + DECLINED, DECLINED) + + #ifdef HAVE_OCSP_STAPLING static int stapling_cache_mutex_on(server_rec *s); static int stapling_cache_mutex_off(server_rec *s); +static int stapling_cb(SSL *ssl, void *arg); + /** - * Maxiumum OCSP stapling response size. This should be the response for a + * Maximum OCSP stapling response size. This should be the response for a * single certificate and will typically include the responder certificate chain * so 10K should be more than enough. * @@ -101,8 +117,10 @@ static X509 *stapling_get_issuer(modssl_ctx_t *mctx, X509 *x) } inctx = X509_STORE_CTX_new(); - if (!X509_STORE_CTX_init(inctx, st, NULL, NULL)) + if (!X509_STORE_CTX_init(inctx, st, NULL, NULL)) { + X509_STORE_CTX_free(inctx); return 0; + } if (X509_STORE_CTX_get1_issuer(&issuer, inctx, x) <= 0) issuer = NULL; X509_STORE_CTX_cleanup(inctx); @@ -118,10 +136,51 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, X509 *issuer = NULL; OCSP_CERTID *cid = NULL; STACK_OF(OPENSSL_STRING) *aia = NULL; + const char *pem = NULL; + int rv = 1; /* until further notice */ - if ((x == NULL) || (X509_digest(x, EVP_sha1(), idx, NULL) != 1)) + if (x == NULL) return 0; + if (!(issuer = stapling_get_issuer(mctx, x))) { + /* In Apache pre 2.4.40, we use to come here only when mod_ssl stapling + * was enabled. With the new hooks, we give other modules the chance + * to provide stapling status. However, we do not want to log ssl errors + * where we did not do so in the past. */ + if (mctx->stapling_enabled == TRUE) { + ssl_log_xerror(SSLLOG_MARK, APLOG_ERR, 0, ptemp, s, x, APLOGNO(02217) + "ssl_stapling_init_cert: can't retrieve issuer " + "certificate!"); + return 0; + } + return 1; + } + + if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) { + rv = 0; + goto cleanup; + } + + if (modssl_cert_get_pem(ptemp, x, issuer, &pem) != APR_SUCCESS) { + rv = 0; + goto cleanup; + } + + if (ap_ssl_ocsp_prime(s, p, (const char*)idx, sizeof(idx), pem) == APR_SUCCESS + || ssl_run_init_stapling_status(s, p, x, issuer) == OK) { + /* Someone's taken over or mod_ssl's own implementation is not enabled */ + if (mctx->stapling_enabled != TRUE) { + SSL_CTX_set_tlsext_status_cb(mctx->ssl_ctx, stapling_cb); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10177) "OCSP stapling added via hook"); + } + goto cleanup; + } + + if (mctx->stapling_enabled != TRUE) { + /* mod_ssl's own implementation is not enabled */ + goto cleanup; + } + cinf = apr_hash_get(stapling_certinfo, idx, sizeof(idx)); if (cinf) { /* @@ -134,25 +193,18 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, APLOGNO(02814) "ssl_stapling_init_cert: no OCSP URI " "in certificate and no SSLStaplingForceURL " "configured for server %s", mctx->sc->vhost_id); - return 0; + rv = 0; } - return 1; - } - - if (!(issuer = stapling_get_issuer(mctx, x))) { - ssl_log_xerror(SSLLOG_MARK, APLOG_ERR, 0, ptemp, s, x, APLOGNO(02217) - "ssl_stapling_init_cert: can't retrieve issuer " - "certificate!"); - return 0; + goto cleanup; } cid = OCSP_cert_to_id(NULL, x, issuer); - X509_free(issuer); if (!cid) { ssl_log_xerror(SSLLOG_MARK, APLOG_ERR, 0, ptemp, s, x, APLOGNO(02815) "ssl_stapling_init_cert: can't create CertID " "for OCSP request"); - return 0; + rv = 0; + goto cleanup; } aia = X509_get1_ocsp(x); @@ -161,7 +213,8 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, ssl_log_xerror(SSLLOG_MARK, APLOG_ERR, 0, ptemp, s, x, APLOGNO(02218) "ssl_stapling_init_cert: no OCSP URI " "in certificate and no SSLStaplingForceURL set"); - return 0; + rv = 0; + goto cleanup; } /* At this point, we have determined that there's something to store */ @@ -183,19 +236,16 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, apr_hash_set(stapling_certinfo, cinf->idx, sizeof(cinf->idx), cinf); - return 1; +cleanup: + X509_free(issuer); + return rv; } -static certinfo *stapling_get_certinfo(server_rec *s, modssl_ctx_t *mctx, - SSL *ssl) +static certinfo *stapling_get_certinfo(server_rec *s, UCHAR *idx, apr_size_t idx_len, + modssl_ctx_t *mctx, SSL *ssl) { certinfo *cinf; - X509 *x; - UCHAR idx[SHA_DIGEST_LENGTH]; - x = SSL_get_certificate(ssl); - if ((x == NULL) || (X509_digest(x, EVP_sha1(), idx, NULL) != 1)) - return NULL; - cinf = apr_hash_get(stapling_certinfo, idx, sizeof(idx)); + cinf = apr_hash_get(stapling_certinfo, idx, idx_len); if (cinf && cinf->cid) return cinf; ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01926) @@ -397,7 +447,7 @@ static int stapling_check_response(server_rec *s, modssl_ctx_t *mctx, rv = SSL_TLSEXT_ERR_NOACK; } - if (status != V_OCSP_CERTSTATUS_GOOD) { + if (status != V_OCSP_CERTSTATUS_GOOD && pok) { char snum[MAX_STRING_LEN] = { '\0' }; BIO *bio = BIO_new(BIO_s_mem()); @@ -418,12 +468,6 @@ static int stapling_check_response(server_rec *s, modssl_ctx_t *mctx, (reason != OCSP_REVOKED_STATUS_NOSTATUS) ? OCSP_crl_reason_str(reason) : "n/a", snum[0] ? snum : "[n/a]"); - - if (mctx->stapling_return_errors == FALSE) { - if (pok) - *pok = FALSE; - rv = SSL_TLSEXT_ERR_NOACK; - } } } @@ -482,6 +526,7 @@ static BOOL stapling_renew_response(server_rec *s, modssl_ctx_t *mctx, SSL *ssl, /* Create a temporary pool to constrain memory use */ apr_pool_create(&vpool, conn->pool); + apr_pool_tag(vpool, "modssl_stapling_renew"); if (apr_uri_parse(vpool, ocspuri, &uri) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01939) @@ -732,6 +777,23 @@ static int get_and_check_cached_response(server_rec *s, modssl_ctx_t *mctx, return 0; } +typedef struct { + unsigned char *data; + apr_size_t len; +} ocsp_resp; + +static void copy_ocsp_resp(const unsigned char *der, apr_size_t der_len, void *userdata) +{ + ocsp_resp *resp = userdata; + + resp->len = 0; + resp->data = der? OPENSSL_malloc(der_len) : NULL; + if (resp->data) { + memcpy(resp->data, der, der_len); + resp->len = der_len; + } +} + /* Certificate Status callback. This is called when a client includes a * certificate status request extension. * @@ -744,24 +806,53 @@ static int stapling_cb(SSL *ssl, void *arg) conn_rec *conn = (conn_rec *)SSL_get_app_data(ssl); server_rec *s = mySrvFromConn(conn); SSLSrvConfigRec *sc = mySrvConfig(s); - SSLConnRec *sslconn = myConnConfig(conn); - modssl_ctx_t *mctx = myCtxConfig(sslconn, sc); + modssl_ctx_t *mctx = myConnCtxConfig(conn, sc); + UCHAR idx[SHA_DIGEST_LENGTH]; + ocsp_resp resp; certinfo *cinf = NULL; OCSP_RESPONSE *rsp = NULL; int rv; BOOL ok = TRUE; + X509 *x; + int rspderlen, provided = 0; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01951) + "stapling_cb: OCSP Stapling callback called"); + + x = SSL_get_certificate(ssl); + if (x == NULL) { + return SSL_TLSEXT_ERR_NOACK; + } + + if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) { + return SSL_TLSEXT_ERR_NOACK; + } + + if (ap_ssl_ocsp_get_resp(s, conn, (const char*)idx, sizeof(idx), + copy_ocsp_resp, &resp) == APR_SUCCESS) { + provided = 1; + } + else if (ssl_run_get_stapling_status(&resp.data, &rspderlen, conn, s, x) == APR_SUCCESS) { + resp.len = (apr_size_t)rspderlen; + provided = 1; + } + if (provided) { + /* a hook handles stapling for this certificate and determines the response */ + if (resp.data == NULL || resp.len == 0) { + return SSL_TLSEXT_ERR_NOACK; + } + SSL_set_tlsext_status_ocsp_resp(ssl, resp.data, (int)resp.len); + return SSL_TLSEXT_ERR_OK; + } + if (sc->server->stapling_enabled != TRUE) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01950) "stapling_cb: OCSP Stapling disabled"); return SSL_TLSEXT_ERR_NOACK; } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01951) - "stapling_cb: OCSP Stapling callback called"); - - cinf = stapling_get_certinfo(s, mctx, ssl); - if (cinf == NULL) { + if ((cinf = stapling_get_certinfo(s, idx, sizeof(idx), mctx, ssl)) == NULL) { return SSL_TLSEXT_ERR_NOACK; } @@ -818,15 +909,21 @@ static int stapling_cb(SSL *ssl, void *arg) if (rsp && ((ok == TRUE) || (mctx->stapling_return_errors == TRUE))) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01956) "stapling_cb: setting response"); - if (!stapling_set_response(ssl, rsp)) - return SSL_TLSEXT_ERR_ALERT_FATAL; - return SSL_TLSEXT_ERR_OK; + if (!stapling_set_response(ssl, rsp)) { + rv = SSL_TLSEXT_ERR_ALERT_FATAL; + } + else { + rv = SSL_TLSEXT_ERR_OK; + } } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01957) - "stapling_cb: no suitable response available"); - - return SSL_TLSEXT_ERR_NOACK; + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01957) + "stapling_cb: no suitable response available"); + rv = SSL_TLSEXT_ERR_NOACK; + } + OCSP_RESPONSE_free(rsp); /* NULL safe */ + return rv; } apr_status_t modssl_init_stapling(server_rec *s, apr_pool_t *p, @@ -864,9 +961,10 @@ apr_status_t modssl_init_stapling(server_rec *s, apr_pool_t *p, if (mctx->stapling_responder_timeout == UNSET) { mctx->stapling_responder_timeout = 10 * APR_USEC_PER_SEC; } + SSL_CTX_set_tlsext_status_cb(ctx, stapling_cb); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01960) "OCSP stapling initialized"); - + return APR_SUCCESS; } |