diff --git a/CHANGES b/CHANGES index b444557f8a..66c13a0b0e 100644 --- a/CHANGES +++ b/CHANGES @@ -30,7 +30,8 @@ Changes with Apache 2.5.1 available for an SSL module like mod_ssl. - ap_ssl_answer_challenge() to enable other modules like mod_md to provide a certificate as used in the RFC 8555 'tls-alpn-01' challenge - for the ACME protocol for an SSL module like mod_ssl. + for the ACME protocol for an SSL module like mod_ssl. The function + and its hook provide PEM encoded data instead of file names. - Hooks for 'ssl_add_cert_files', 'ssl_add_fallback_cert_files' and 'ssl_answer_challenge' where modules like mod_md can provide providers to the above mentioned functions. diff --git a/include/http_protocol.h b/include/http_protocol.h index c4f064a7c8..30faa131a9 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -1174,23 +1174,34 @@ AP_DECLARE(apr_status_t) ap_ssl_add_fallback_cert_files(server_rec *s, apr_pool_ apr_array_header_t *key_files); -/** - * On TLS connections that do not relate to a configured virtual host, - * allow modules to provide a certificate and key to - * be used on the connection. +/** + * On TLS connections that do not relate to a configured virtual host + * allow modules to provide a certificate and key to be used on the connection. + * + * A Certificate PEM added must be accompanied by a private key PEM. The private + * key PEM may be given by a NULL pointer, in which case it is expected to be found in + * the certificate PEM string. */ -AP_DECLARE_HOOK(int, ssl_answer_challenge, (conn_rec *c, const char *server_name, - const char **pcert_file, const char **pkey_file)) +AP_DECLARE_HOOK(int, ssl_answer_challenge, (conn_rec *c, const char *server_name, + const char **pcert_pem, const char **pkey_pem)) /** * Returns != 0 iff the connection is a challenge to the server, for example * as defined in RFC 8555 for the 'tls-alpn-01' domain verification, and needs * a specific certificate as answer in the handshake. + * * ALPN protocol negotiation via the hooks 'protocol_propose' and 'protocol_switch' * need to have run before this call is made. + * + * Certificate PEMs added must be accompanied by a private key PEM. The private + * key PEM may be given by a NULL pointer, in which case it is expected to be found in + * the certificate PEM string. + * + * A certificate provided this way needs to replace any other certificates selected + * by configuration or 'ssl_add_cert_pems` on this connection. */ -AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name, - const char **pcert_file, const char **pkey_file); +AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name, + const char **pcert_pem, const char **pkey_pem); #ifdef __cplusplus diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index 3dbdda6544..6ecc5df69b 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -199,12 +199,12 @@ static void ssl_add_version_components(apr_pool_t *ptemp, apr_pool_t *pconf, int ssl_is_challenge(conn_rec *c, const char *servername, X509 **pcert, EVP_PKEY **pkey, - const char **pcert_file, const char **pkey_file) + const char **pcert_pem, const char **pkey_pem) { *pcert = NULL; *pkey = NULL; - *pcert_file = *pkey_file = NULL; - if (ap_ssl_answer_challenge(c, servername, pcert_file, pkey_file)) { + *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)) { diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 30dd5ee338..9df2b9600b 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -2317,33 +2317,32 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc) static apr_status_t set_challenge_creds(conn_rec *c, const char *servername, SSL *ssl, X509 *cert, EVP_PKEY *key, - const char *cert_file, const char *key_file) + 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_file) { - if (SSL_use_certificate_file(ssl, cert_file, SSL_FILETYPE_PEM) < 1) { - ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10264) - "Failed to configure challenge certificate %s", + 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() + "Failed to parse PEM of challenge certificate %s", servername); - return APR_EGENERAL; + goto cleanup; } - if (key_file == NULL) key_file = cert_file; - if (SSL_use_PrivateKey_file(ssl, key_file, SSL_FILETYPE_PEM) < 1) { - ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(10265) - "Failed to configure challenge private key %s", - servername); - return APR_EGENERAL; - } - goto check; } 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; + rv = APR_EGENERAL; goto cleanup; } if (!SSL_use_PrivateKey(ssl, key)) { @@ -2351,16 +2350,19 @@ static apr_status_t set_challenge_creds(conn_rec *c, const char *servername, "error '%s' using Challenge key: %s", ERR_error_string(ERR_peek_last_error(), NULL), servername); - return APR_EGENERAL; + rv = APR_EGENERAL; goto cleanup; } -check: 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; + rv = APR_EGENERAL; goto cleanup; } + +cleanup: + if (our_data && cert) X509_free(cert); + if (our_data && key) EVP_PKEY_free(key); return APR_SUCCESS; } @@ -2370,10 +2372,6 @@ check: */ static apr_status_t init_vhost(conn_rec *c, SSL *ssl, const char *servername) { - X509 *cert; - EVP_PKEY *key; - const char *cert_file, *key_file; - if (c) { SSLConnRec *sslcon = myConnConfig(c); @@ -2396,16 +2394,6 @@ static apr_status_t init_vhost(conn_rec *c, SSL *ssl, const char *servername) sslcon->vhost_found = +1; return APR_SUCCESS; } - else if (ssl_is_challenge(c, servername, &cert, &key, &cert_file, &key_file)) { - /* With ACMEv1 we can have challenge connections to a unknown domains - * that need to be answered with a special certificate and will - * otherwise not answer any requests. */ - if (set_challenge_creds(c, servername, ssl, cert, key, - cert_file, key_file) != APR_SUCCESS) { - return APR_EGENERAL; - } - SSL_set_verify(ssl, SSL_VERIFY_NONE, ssl_callback_SSLVerify); - } else { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02044) "No matching SSL virtual host for servername " @@ -2792,11 +2780,11 @@ int ssl_callback_alpn_select(SSL *ssl, const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); X509 *cert; EVP_PKEY *key; - const char *cert_file, *key_file; + const char *cert_pem, *key_pem; - if (ssl_is_challenge(c, servername, &cert, &key, &cert_file, &key_file)) { + if (ssl_is_challenge(c, servername, &cert, &key, &cert_pem, &key_pem)) { if (set_challenge_creds(c, servername, ssl, cert, key, - cert_file, key_file) != APR_SUCCESS) { + cert_pem, key_pem) != APR_SUCCESS) { return SSL_TLSEXT_ERR_ALERT_FATAL; } SSL_set_verify(ssl, SSL_VERIFY_NONE, ssl_callback_SSLVerify); diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c index 2209ef44b2..d6448957f6 100644 --- a/modules/ssl/ssl_util_ssl.c +++ b/modules/ssl/ssl_util_ssl.c @@ -527,3 +527,54 @@ void modssl_set_reneg_state(SSLConnRec *sslconn, modssl_reneg_state state) sslconn->reneg_state = state; #endif } + +/* _________________________________________________________________ +** +** Certficate/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; +} diff --git a/modules/ssl/ssl_util_ssl.h b/modules/ssl/ssl_util_ssl.h index ec89185b1b..335d6fdde7 100644 --- a/modules/ssl/ssl_util_ssl.h +++ b/modules/ssl/ssl_util_ssl.h @@ -82,7 +82,15 @@ char *modssl_SSL_SESSION_id2sz(IDCONST unsigned char *, int, char *, int); * 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 give 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); + #endif /* __SSL_UTIL_SSL_H__ */ /** @} */ diff --git a/server/protocol.c b/server/protocol.c index 4ce2b6172a..d18ca01b95 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -2717,9 +2717,9 @@ AP_DECLARE(apr_status_t) ap_ssl_add_fallback_cert_files(server_rec *s, apr_pool_ } AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name, - const char **pcert_file, const char **pkey_file) + const char **pcert_pem, const char **pkey_pem) { - return (ap_run_ssl_answer_challenge(c, server_name, pcert_file, pkey_file) == OK); + return (ap_run_ssl_answer_challenge(c, server_name, pcert_pem, pkey_pem) == OK); } AP_IMPLEMENT_HOOK_VOID(pre_read_request, @@ -2761,6 +2761,6 @@ AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_add_fallback_cert_files, apr_array_header_t *cert_files, apr_array_header_t *key_files), (s, p, cert_files, key_files), OK, DECLINED) AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_answer_challenge, - (conn_rec *c, const char *server_name, const char **pcert_file, const char **pkey_file), - (c, server_name, pcert_file, pkey_file), DECLINED) + (conn_rec *c, const char *server_name, const char **pcert_pem, const char **pkey_pem), + (c, server_name, pcert_pem, pkey_pem), DECLINED)