From: Stefan Eissing Date: Wed, 12 May 2021 10:14:42 +0000 (+0000) Subject: Merge of [r1886840, r1887085, r1887087, r1887134, r1887151, r1887152, X-Git-Tag: 2.4.48~19 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c0cff48bccf6607333a6841f723660c0083929f4;p=thirdparty%2Fapache%2Fhttpd.git Merge of [r1886840, r1887085, r1887087, r1887134, r1887151, r1887152, r1887337, r1887340, r1887342, r1887343, r1887360, r1887364, r1887923, r1887965, r1887993, r1888006, r1888083, r1888084, r1888723, r1888724, r1888726, r1888729, r1889788] from trunk: *) core/mod_ssl/mod_md: - adding new ap_ssl_*() functions for a backward compatible replacement of the major optional mod_ssl functions. This allows other ssl modules to work without impersonating mod_ssl and also allows different ssl modules being active on separate ports. - latest mod_md with support for multiple certificates per domain and ECDSA certificates. Removed ACMEv1 support. - Interworking mod_md and ssl modules changed to exchange PEM strings instead of file paths for ACME challenges. - core/mod_ssl/mod_md: adding OCSP response provisioning as core feature. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1889793 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..2970e540bd0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,367 @@ +# global +.deps +.libs +*.swp +*.o +*.a +*.lo +*.la +*.slo +*.so +*.vcproj +*.vcproj.* +*.x +*.aps +*.plg +*.dep +*.rc +*.stc +*.stt +*.sto +*.mak +Makefile +modules.mk +BuildLog.htm +Debug +Release + +# / +/config.nice +/configure +/missing +/install-sh +/mkinstalldirs +/aclocal.m4 +/generated_lists +/buildmk.stamp +/config.log +/libtool +/shlibtool +/config.status +/modules.c +/config.cache +/httpd +/httpd.exe +/LibD +/LibR +/Apache.suo +/Apache.ncb +/Apache.opt +/apachecore.dll +/Apache.sln +/autom4te.cache +/httpd.spec +/tags +/TAGS +/*.kdev4 +/.kdev_include_paths +/.cproject +/.project + +# /built/ +/built/ + +# /cmake-build-debug/ +/cmake-build-debug/ + +# /build/ +/build/rules.mk +/build/config_vars.mk +/build/apr_common.m4 +/build/find_apr.m4 +/build/find_apu.m4 +/build/ltconfig +/build/ltmain.sh +/build/PrintPath +/build/config.sub +/build/config.guess +/build/config_vars.sh + +# /build/pkg/ +/build/pkg/pkginfo + +# /build/win32/ +!/build/win32/httpd.rc + +# /docs/ +/docs/dox + +# /docs/conf/ +/docs/conf/httpd-std.conf +/docs/conf/ssl-std.conf +/docs/conf/httpd.conf + +# /docs/conf/extra/ +/docs/conf/extra/*.conf + +# /docs/log-message-tags/ +/docs/log-message-tags/list + +# /docs/manual/ +/docs/manual/_chm +/docs/manual/_off +/docs/manual/_tools +/docs/manual/_dist +/docs/manual/build +/docs/manual/*.tex +/docs/manual/*.aux +/docs/manual/*.out +/docs/manual/*.log +/docs/manual/*.pdf +/docs/manual/*.toc +/docs/manual/.outdated* + +# /docs/manual/developer/ +/docs/manual/developer/*.tex +/docs/manual/developer/*.aux + +# /docs/manual/faq/ +/docs/manual/faq/*.tex +/docs/manual/faq/*.aux + +# /docs/manual/howto/ +/docs/manual/howto/*.aux +/docs/manual/howto/*.tex +/docs/manual/howto/Working-Copy.txt + +# /docs/manual/misc/ +/docs/manual/misc/*.tex +/docs/manual/misc/*.aux + +# /docs/manual/mod/ +/docs/manual/mod/*.tex +/docs/manual/mod/*.aux +/docs/manual/mod/.translated.* + +# /docs/manual/platform/ +/docs/manual/platform/*.tex +/docs/manual/platform/*.aux + +# /docs/manual/programs/ +/docs/manual/programs/*.tex +/docs/manual/programs/*.aux + +# /docs/manual/rewrite/ +/docs/manual/rewrite/_chm +/docs/manual/rewrite/_off +/docs/manual/rewrite/_tools +/docs/manual/rewrite/_dist +/docs/manual/rewrite/build +/docs/manual/rewrite/*.tex +/docs/manual/rewrite/*.aux +/docs/manual/rewrite/*.out +/docs/manual/rewrite/*.log +/docs/manual/rewrite/*.pdf +/docs/manual/rewrite/*.toc +/docs/manual/rewrite/.outdated* + +# /docs/manual/ssl/ +/docs/manual/ssl/*.tex +/docs/manual/ssl/*.aux + +# /docs/manual/style/ +/docs/manual/style/_generated + +# /docs/manual/vhosts/ +/docs/manual/vhosts/*.tex +/docs/manual/vhosts/*.aux + +# /include/ +/include/ap_config_auto.h +/include/ap_config_auto.h.in +/include/ap_config_layout.h +/include/ap_ldap.h +/include/mod_cgi.h +/include/mod_dav.h +/include/mod_include.h +/include/mod_proxy.h +/include/mod_so.h +/include/mpm.h +/include/mpm_default.h +/include/os.h + +# /modules/aaa/ + +# /modules/apreq/ + +# /modules/arch/ + +# /modules/arch/unix/ + +# /modules/arch/win32/ + +# /modules/cache/ + +# /modules/cluster/ + +# /modules/core/ + +# /modules/core/test/ +/modules/core/test/out + +# /modules/database/ + +# /modules/dav/ + +# /modules/dav/fs/ + +# /modules/dav/lock/ + +# /modules/dav/main/ + +# /modules/debugging/ + +# /modules/echo/ + +# /modules/examples/ + +# /modules/experimental/ + +# /modules/filters/ + +# /modules/generators/ + +# /modules/http/ + +# /modules/http2/ + +# /modules/ldap/ + +# /modules/loggers/ + +# /modules/lua/ + +# /modules/lua/test/ +/modules/lua/test/cycle +/modules/lua/test/httpd.conf + +# /modules/mappers/ + +# /modules/md/ +/modules/md/a2md + +# /modules/metadata/ + +# /modules/proxy/ + +# /modules/proxy/balancers/ + +# /modules/proxy/examples/ + +# /modules/session/ + +# /modules/slotmem/ + +# /modules/ssl/ + +# /modules/test/ + +# /os/ + +# /os/bs2000/ + +# /os/os2/ + +# /os/unix/ + +# /os/win32/ +/os/win32/*.mdp +/os/win32/*.ncb +/os/win32/*.opt +/os/win32/*.dsw +/os/win32/mod_*D +/os/win32/mod_*R + +# /server/ +/server/test_char.h +/server/gen_test_char +/server/gen_test_char.exe +/server/gen_test_char.exe.manifest +/server/export_files +/server/exports.c +/server/export_vars.h +/server/ApacheCoreOS2.def +/server/httpd.exp +/server/buildmarked.c + +# /server/mpm/ + +# /server/mpm/event/ + +# /server/mpm/motorz/ + +# /server/mpm/mpmt_os2/ + +# /server/mpm/prefork/ + +# /server/mpm/simple/ + +# /server/mpm/winnt/ + +# /server/mpm/worker/ + +# /srclib/ +/srclib/pth +/srclib/apr +/srclib/apr-util +/srclib/apr-iconv +/srclib/distcache +/srclib/lua +/srclib/pcre +/srclib/openssl +/srclib/zlib + +# /support/ +/support/rotatelogs +/support/htpasswd +/support/htdbm +/support/htdigest +/support/htcacheclean +/support/unescape +/support/inc2shtml +/support/httpd_monitor +/support/suexec +/support/logresolve +/support/ab +/support/apxs +/support/apachectl +/support/checkgid +/support/dbmmanage +/support/envvars-std +/support/log_server_status +/support/logresolve.pl +/support/split-logfile +/support/phf_abuse_log.cgi +/support/httxt2dbm +/support/fcgistarter +/support/firehose +/support/*.exe + +# /support/win32/ +!/support/win32/ApacheMonitor.rc +/support/win32/ApacheMonitorVer.rc + +# /test/ +/test/a.out +/test/time-FCNTL +/test/time-FLOCK +/test/time-SYSVSEM +/test/time-SYSVSEM2 +/test/time-PTHREAD +/test/time-USLOCK +/test/zb +/test/test-writev +/test/test_date +/test/test_select +/test/dbu +/test/sni +/test/httpdunit +/test/httpdunit.cases + +# /test/unit/ +/test/unit/*.tests + +# Intellij +/.idea/ + + diff --git a/CHANGES b/CHANGES index 2ce1bd582a5..5dbf3af2807 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,119 @@ Changes with Apache 2.4.48 *) core: Fix a regression that stripped the ETag header from 304 responses. PR 61820 [Ruediger Pluem, Roy T. Fielding] + *) core: Adding SSL related inquiry functions to the server API. + These function are always available, even when no module providing + SSL is loaded. They provide their own "shadowing" implementation for + the optional functions of similar name that mod_ssl and impersonators + of mod_ssl provide. + This enables loading of several SSL providing modules when all but + one of them registers itself into the new hooks. Two old-style SSL + modules will not work, as they replace the others optional functions + with their own. + Modules using the old-style optional functions will continue to work + as core supplies its own versions of those. + The following has been added so far: + - ap_ssl_conn_is_ssl() to query if a connection is using SSL. + - ap_ssl_var_lookup() to query SSL related variables for a + server/connection/request. + - Hooks for 'ssl_conn_is_ssl' and 'ssl_var_lookup' where modules + providing SSL can install their own value supplying functions. + - ap_ssl_add_cert_files() to enable other modules like mod_md to provide + certificate and keys for an SSL module like mod_ssl. + - ap_ssl_add_fallback_cert_files() to enable other modules like mod_md to + provide a fallback certificate in case no 'proper' certificate is + 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. 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. + - These functions reside in the new 'http_ssl.h' header file. + [Stefan Eissing] + + *) core/mod_ssl/mod_md: adding OCSP response provisioning as core feature. This + allows modules to access and provide OCSP response data without being tied + of each other. The data is exchanged in standard, portable formats (PEM encoded + certificates and DER encoded responses), so that the actual SSL/crypto + implementations used by the modules are independant of each other. + Registration and retrieval happen in the context of a server (server_rec) + which modules may use to decide if they are configured for this or not. + The area of changes: + 1. core: defines 2 functions in include/http_ssl.h, so that modules may + register a certificate, together with its issuer certificate for OCSP + response provisioning and ask for current response data (DER bytes) later. + Also, 2 hooks are defined that allow modules to implement this OCSP + provisioning. + 2. mod_ssl uses the new functions, in addition to what it did already, to + register its certificates this way. If no one is interested in providing + OCSP, it falls back to its own (if configured) stapling implementation. + 3. mod_md registers itself at the core hooks for OCSP provisioning. Depending + on configuration, it will accept registrations of its own certificates only, + all certificates or none. + [Stefan Eissing] + + *) mod_md: v2.4.0 with improvements and bugfixes + - MDPrivateKeys allows the specification of several types. Beside "RSA" plus + optional key lengths elliptic curves can be configured. This means you can + have multiple certificates for a Managed Domain with different key types. + With ```MDPrivateKeys secp384r1 rsa2048``` you get one ECDSA and one RSA + certificate and all modern client will use the shorter ECDSA, while older + client will get the RSA certificate. + Many thanks to @tlhackque who pushed and helped on this. + - Support added for MDomains consisting of a wildcard. Configuring + ```MDomain *.host.net``` will match all virtual hosts matching that pattern + and obtain one certificate for it (assuming you have 'dns-01' challenge + support configured). Addresses #239. + - Removed support for ACMEv1 servers. The only known installation used to + be Let's Encrypt which has disabled that version more than a year ago for + new accounts. + - Andreas Ulm () implemented the + ```renewing``` call to ```MDMessageCmd``` that can deny a certificate + renewal attempt. This is useful in clustered installations, as + discussed in #233). + - New event ```challenge-setup::```, triggered when the + challenge data for a domain has been created. This is invoked before the + ACME server is told to check for it. The type is one of the ACME challenge + types. This is invoked for every DNS name in a MDomain. + - The max delay for retries has been raised to daily (this is like all + retries jittered somewhat to avoid repeats at fixed time of day). + - Certain error codes reported by the ACME server that indicate a problem + with the configured data now immediately switch to daily retries. For + example: if the ACME server rejects a contact email or a domain name, + frequent retries will most likely not solve the problem. But daily retries + still make sense as there might be an error at the server and un-supervised + certificate renewal is the goal. Refs #222. + - Test case and work around for domain names > 64 octets. Fixes #227. + When the first DNS name of an MD is longer than 63 octets, the certificate + request will not contain a CN field, but leave it up to the CA to choose one. + Currently, Lets Encrypt looks for a shorter name in the SAN list given and + fails the request if none is found. But it is really up to the CA (and what + browsers/libs accept here) and may change over the years. That is why + the decision is best made at the CA. + - Retry delays now have a random +/-[0-50]% modification applied to let + retries from several servers spread out more, should they have been + restarted at the same time of day. + - Fixed several places where the 'badNonce' return code from an ACME server + was not handled correctly. The test server 'pebble' simulates this behaviour + by default and helps nicely in verifying this behaviour. Thanks, pebble! + - Set the default `MDActivationDelay` to 0. This was confusing to users that + new certificates were deemed not usably before a day of delay. When clocks are + correct, using a new certificate right away should not pose a problem. + - When handling ACME authorization resources, the module no longer requires + the server to return a "Location" header, as was necessary in ACMEv1. + Fixes #216. + - Fixed a theoretical uninitialized read when testing for JSON error responses + from the ACME CA. Reported at . + - ACME problem reports from CAs that include parameters in the Content-Type + header are handled correctly. (Previously, the problem text would not be + reported and retries could exceed CA limits.) + - Account Update transactions to V2 CAs now use the correct POST-AS-GET method. + Previously, an empty JSON object was sent - which apparently LE accepted, + but others reject. + [Stefan Eissing, @tlhackque, Andreas Ulm] + Changes with Apache 2.4.47 *) mod_dav_fs: Improve logging output when failing to open files for diff --git a/CMakeLists.txt b/CMakeLists.txt index a7b18419ddf..dfef19e8e48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -471,7 +471,7 @@ SET(mod_md_extra_libs ${OPENSSL_LIBRARIES} ${CURL_LIBRARIES} ${JA SET(mod_md_extra_sources modules/md/md_acme.c modules/md/md_acme_acct.c modules/md/md_acme_authz.c modules/md/md_acme_drive.c - modules/md/md_acmev1_drive.c modules/md/md_acmev2_drive.c + modules/md/md_acmev2_drive.c modules/md/md_event.c modules/md/md_acme_order.c modules/md/md_core.c modules/md/md_curl.c modules/md/md_crypt.c modules/md/md_http.c modules/md/md_json.c @@ -482,7 +482,7 @@ SET(mod_md_extra_sources modules/md/md_ocsp.c modules/md/md_util.c modules/md/mod_md_config.c modules/md/mod_md_drive.c modules/md/mod_md_os.c modules/md/mod_md_status.c - modules/md/mod_md_ocsp.c + modules/md/mod_md_ocsp.c ) SET(mod_optional_hook_export_extra_defines AP_DECLARE_EXPORT) # bogus reuse of core API prefix SET(mod_proxy_extra_defines PROXY_DECLARE_EXPORT) @@ -661,6 +661,7 @@ SET(LIBHTTPD_SOURCES server/protocol.c server/provider.c server/request.c + server/ssl.c server/scoreboard.c server/util.c server/util_cfgtree.c diff --git a/build/nw_export.inc b/build/nw_export.inc index a97a18191b6..f0a26f567e5 100644 --- a/build/nw_export.inc +++ b/build/nw_export.inc @@ -42,6 +42,7 @@ #include "http_main.h" #include "http_protocol.h" #include "http_request.h" +#include "http_ssl.h" #include "http_vhost.h" #include "mpm_common.h" #include "ap_regex.h" diff --git a/include/ap_mmn.h b/include/ap_mmn.h index a204e438bd5..7a6c7c68e06 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -554,6 +554,11 @@ * AP_REQUEST_STRONG_ETAG, AP_REQUEST_GET_BNOTE, * AP_REQUEST_SET_BNOTE and AP_REQUEST_IS_STRONG_ETAG * in httpd.h. + * 20120211.102 (2.4.47-dev) Add ap_ssl_conn_is_ssl()/ap_ssl_var_lookup() and hooks + * 20120211.103 (2.4.47-dev) Add ap_ssl_add_cert_files, ap_ssl_add_fallback_cert_files + * and ap_ssl_answer_challenge and hooks. + * 20120211.104 (2.4.47-dev) Move ap_ssl_* into new http_ssl.h header file + * 20120211.105 (2.4.47-dev) Add ap_ssl_ocsp* hooks and functions to http_ssl.h. */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ @@ -561,7 +566,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20120211 #endif -#define MODULE_MAGIC_NUMBER_MINOR 101 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 105 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/http_protocol.h b/include/http_protocol.h index 0047f4762b3..c01c8a67e97 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -1062,6 +1062,7 @@ AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub_r); AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers); + #ifdef __cplusplus } #endif diff --git a/include/http_ssl.h b/include/http_ssl.h new file mode 100644 index 00000000000..556a58bdb73 --- /dev/null +++ b/include/http_ssl.h @@ -0,0 +1,280 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file http_ssl.h + * @brief SSL protocol handling + * + * @defgroup APACHE_CORE_PROTO SSL Protocol Handling + * @ingroup APACHE_CORE + * @{ + */ + +#ifndef APACHE_HTTP_SSL_H +#define APACHE_HTTP_SSL_H + +#include "httpd.h" +#include "apr_portable.h" +#include "apr_mmap.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This hook allows modules that manage SSL connection to register their + * inquiry function for checking if a connection is using SSL from them. + * @param c The current connection + * @return OK if the connection is using SSL, DECLINED if not. + * @ingroup hooks + */ +AP_DECLARE_HOOK(int,ssl_conn_is_ssl,(conn_rec *c)) + +/** + * Return != 0 iff the connection is encrypted with SSL. + * @param c the connection + */ +AP_DECLARE(int) ap_ssl_conn_is_ssl(conn_rec *c); + +/** + * This hook allows modules to look up SSL related variables for a + * server/connection/request, depending on what they inquire. Some + * variables will only be available for a connection/request, for example. + * @param p The pool to allocate a returned value in, MUST be provided + * @param s The server to inquire a value for, maybe NULL + * @param c The current connection, maybe NULL + * @param r The current request, maybe NULL + * @param name The name of the variable to retrieve, MUST be provided + * @return value or the variable or NULL if not provided/available + * @ingroup hooks + */ +AP_DECLARE_HOOK(const char *,ssl_var_lookup, + (apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name)) + +/** + * Lookup an SSL related variable for the server/connection/request or a global + * value when all those parameters are set to NULL. Pool and name must always be + * provided and the returned value (if not NULL) will be allocated fromt he pool. + * @param p The pool to allocate a returned value in, MUST be provided + * @param s The server to inquire a value for, maybe NULL + * @param c The current connection, maybe NULL + * @param r The current request, maybe NULL + * @param name The name of the variable to retrieve, MUST be provided + * @return value or the variable or NULL if not provided/available + */ +AP_DECLARE(const char *) ap_ssl_var_lookup(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, + const char *name); + +/** + * Register to provide certificate/key files for servers. Certificate files are + * exepcted to contain the certificate chain, beginning with the server's certificate, + * excluding the trust anchor, in PEM format. + * They must be accompanied by a private key file, also in PEM format. + * + * @param s the server certificates are collected for + * @param p the pool to use for allocations + * @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 + * @return OK if files were added, DECLINED if not, or other for error. + */ + +AP_DECLARE_HOOK(int, ssl_add_cert_files, (server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files)) + +/** + * Collect certificate/key files from all providers registered. This includes + * providers registered at the global 'ssl_add_cert_files', as well as those + * installed in the OPTIONAL 'ssl_add_cert_files' hook as may be provided by + * ssl modules. + * + * @param s the server certificates are collected for + * @param p the pool to use for allocations + * @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 + */ +AP_DECLARE(apr_status_t) ap_ssl_add_cert_files(server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files); + + +/** + * Register to provide 'fallback' certificates in case no 'real' certificates + * have been configured/added by other providers. Modules using these certificates + * are encouraged to answer requests to this server with a 503 response code. + * + * @param s the server certificates are collected for + * @param p the pool to use for allocations + * @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 + * @return OK if files were added, DECLINED if not, or other for error. + */ +AP_DECLARE_HOOK(int, ssl_add_fallback_cert_files, (server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files)) + +/** + * Collect 'fallback' certificate/key files from all registered providers, either + * in the global 'ssl_add_fallback_cert_files' hook or the optional one of similar + * name as provided by mod_ssl and sorts. + * Certificates obtained this way are commonly self signed, temporary crutches. + * To be used to the time it takes to retrieve a 'read', trusted certificate. + * A module using fallbacks is encouraged to answer all requests with a 503. + * + * @param s the server certificates are collected for + * @param p the pool to use for allocations + * @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 + */ +AP_DECLARE(apr_status_t) ap_ssl_add_fallback_cert_files(server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + 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. + * + * 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_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_pem, const char **pkey_pem); + + +/** + * Setup optional functions for ssl related queries so that functions + * registered by old-style SSL module functions are interrogated by the + * the new ap_is_ssl() and friends. Installs own optional functions, so that + * old modules looking for these find one and get the correct results (shadowing). + * + * Needs to run in core's very early POST_CONFIG hook. + * Modules providing such functions register their own optionals during + * register_hooks(). Modules using such functions retrieve them often + * in their own post-config or in the even later retrieval hook. When shadowing + * other modules functions, core's early post-config is a good time. + * @param pool The pool to use for allocations + */ +AP_DECLARE(void) ap_setup_ssl_optional_fns(apr_pool_t *pool); + +/** + * Providers of OCSP status responses register at this hook. Installed hooks returning OK + * are expected to provide later OCSP responses via a 'ap_ssl_ocsp_get_resp_hook'. + * @param s the server being configured + * @params p a memory pool to use + * @param id opaque data uniquely identifying the certificate, provided by caller + * @param pem PEM data of certificate first, followed by PEM of issuer cert + * @return OK iff stapling is being provided + */ +AP_DECLARE_HOOK(int, ssl_ocsp_prime_hook, (server_rec *s, apr_pool_t *p, + const char *id, apr_size_t id_len, + const char *pem)) + +/** + * Registering a certificate for Provisioning of OCSP responses. It is the caller's + * responsibility to provide a global (apache instance) unique id for the certificate + * that is then used later in retrieving the OCSP response. + * A certificate can be primed this way more than once, however the same identifier + * has to be provided each time (byte-wise same, not pointer same). + * The memory pointed to by `id` and `pem` is only valid for the duration of the call. + * + * @param s the server being configured + * @params p a memory pool to use + * @param id opaque data uniquely identifying the certificate, provided by caller + * @param pem PEM data of certificate first, followed by chain certs, at least the issuer + * @return APR_SUCCESS iff OCSP responses will be provided. + * APR_ENOENT when no provided was found or took responsibility. + */ +AP_DECLARE(apr_status_t) ap_ssl_ocsp_prime(server_rec *s, apr_pool_t *p, + const char *id, apr_size_t id_len, + const char *pem); + +/** + * Callback to copy over the OCSP response data. If OCSP response data is not + * available, this will be called with NULL, 0 parameters! + * + * Memory allocation methods and lifetime of data will vary per module and + * SSL library used. The caller requesting OCSP data will need to make a copy + * for his own use. + * Any passed data may only be valid for the duration of the call. + */ +typedef void ap_ssl_ocsp_copy_resp(const unsigned char *der, apr_size_t der_len, void *userdata); + +/** + * Asking for OCSP response DER data for a certificate formerly primed. + * @param s the (SNI selected) server of the connection + * @param c the connection + * @param id identifier for the certifate, as used in ocsp_stapling_prime() + * @param cb callback to invoke when response data is available + * @param userdata caller supplied data passed to callback + * @return OK iff response data has been provided, DECLINED otherwise + */ +AP_DECLARE_HOOK(int, ssl_ocsp_get_resp_hook, + (server_rec *s, conn_rec *c, const char *id, apr_size_t id_len, + ap_ssl_ocsp_copy_resp *cb, void *userdata)) + +/** + * Retrieve the OCSP response data for a previously primed certificate. The id needs + * to be byte-wise identical to the one used on priming. If the call return ARP_SUCCESS, + * the callback has been invoked with the OCSP response DER data. + * Otherwise, a different status code must be returned. Callers in SSL connection + * handshakes are encouraged to continue the handshake without OCSP data for + * server reliability. The decision to accept or reject a handshake with missing + * OCSP stapling data needs to be done by the client. + * For similar reasons, providers of responses might return seemingly expired ones + * if they were unable to refresh a response in time. + * + * The memory pointed to by `id` is only valid for the duration of the call. + * Also, the DER data passed to the callback is only valid for the duration + * of the call. + * + * @param s the (SNI selected) server of the connection + * @param c the connection + * @param id identifier for the certifate, as used in ocsp_stapling_prime() + * @param cb callback to invoke when response data is available + * @param userdata caller supplied data passed to callback + * @return APR_SUCCESS iff data has been provided + */ +AP_DECLARE(apr_status_t) ap_ssl_ocsp_get_resp(server_rec *s, conn_rec *c, + const char *id, apr_size_t id_len, + ap_ssl_ocsp_copy_resp *cb, void *userdata); + +#ifdef __cplusplus +} +#endif + +#endif /* !APACHE_HTTP_SSL_H */ +/** @} */ diff --git a/libhttpd.dsp b/libhttpd.dsp index a1f7e92d4e3..579c5f86327 100644 --- a/libhttpd.dsp +++ b/libhttpd.dsp @@ -193,6 +193,10 @@ SOURCE=.\include\http_request.h # End Source File # Begin Source File +SOURCE=.\include\http_ssl.h +# End Source File +# Begin Source File + SOURCE=.\include\http_vhost.h # End Source File # Begin Source File @@ -510,6 +514,10 @@ SOURCE=.\server\request.c # End Source File # Begin Source File +SOURCE=.\server\ssl.c +# End Source File +# Begin Source File + SOURCE=.\server\vhost.c # End Source File # End Group diff --git a/modules/filters/mod_deflate.c b/modules/filters/mod_deflate.c index 99aa0a5ed35..46148cd1ea0 100644 --- a/modules/filters/mod_deflate.c +++ b/modules/filters/mod_deflate.c @@ -43,10 +43,11 @@ #include "apr_general.h" #include "util_filter.h" #include "apr_buckets.h" +#include "http_protocol.h" #include "http_request.h" +#include "http_ssl.h" #define APR_WANT_STRFUNC #include "apr_want.h" -#include "mod_ssl.h" #include "zlib.h" @@ -94,8 +95,6 @@ static const char deflate_magic[2] = { '\037', '\213' }; #define DEFAULT_MEMLEVEL 9 #define DEFAULT_BUFFERSIZE 8096 -static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *mod_deflate_ssl_var = NULL; - /* Check whether a request is gzipped, so we can un-gzip it. * If a request has multiple encodings, we need the gzip * to be the outermost non-identity encoding. @@ -514,10 +513,8 @@ static int check_ratio(request_rec *r, deflate_ctx *ctx, static int have_ssl_compression(request_rec *r) { const char *comp; - if (mod_deflate_ssl_var == NULL) - return 0; - comp = mod_deflate_ssl_var(r->pool, r->server, r->connection, r, - "SSL_COMPRESS_METHOD"); + comp = ap_ssl_var_lookup(r->pool, r->server, r->connection, r, + "SSL_COMPRESS_METHOD"); if (comp == NULL || *comp == '\0' || strcmp(comp, "NULL") == 0) return 0; return 1; @@ -1879,7 +1876,6 @@ static apr_status_t inflate_out_filter(ap_filter_t *f, static int mod_deflate_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { - mod_deflate_ssl_var = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); return OK; } diff --git a/modules/http2/h2_alt_svc.c b/modules/http2/h2_alt_svc.c index 2c3253c2cca..c22f7eba836 100644 --- a/modules/http2/h2_alt_svc.c +++ b/modules/http2/h2_alt_svc.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "h2_private.h" @@ -98,7 +99,7 @@ static int h2_alt_svc_handler(request_rec *r) */ const char *alt_svc = ""; const char *svc_ma = ""; - int secure = h2_h2_is_tls(r->connection); + int secure = ap_ssl_conn_is_ssl(r->connection); int ma = h2_config_rgeti(r, H2_CONF_ALT_SVC_MAX_AGE); if (ma >= 0) { svc_ma = apr_psprintf(r->pool, "; ma=%d", ma); diff --git a/modules/http2/h2_conn_io.c b/modules/http2/h2_conn_io.c index 5f17e85951a..d0e099b421f 100644 --- a/modules/http2/h2_conn_io.c +++ b/modules/http2/h2_conn_io.c @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include #include "h2_private.h" #include "h2_bucket_eos.h" @@ -132,7 +134,7 @@ apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, server_rec *s) { io->c = c; io->output = apr_brigade_create(c->pool, c->bucket_alloc); - io->is_tls = h2_h2_is_tls(c); + io->is_tls = ap_ssl_conn_is_ssl(c); io->buffer_output = io->is_tls; io->flush_threshold = (apr_size_t)h2_config_sgeti64(s, H2_CONF_STREAM_MAX_MEM); diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c index c05b93093d0..1f0a5df7d38 100644 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@ -26,10 +26,9 @@ #include #include #include +#include #include -#include "mod_ssl.h" - #include "mod_http2.h" #include "h2_private.h" @@ -57,13 +56,6 @@ const char *h2_clear_protos[] = { const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; -/******************************************************************************* - * The optional mod_ssl functions we need. - */ -static APR_OPTIONAL_FN_TYPE(ssl_is_https) *opt_ssl_is_https; -static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *opt_ssl_var_lookup; - - /******************************************************************************* * HTTP/2 error stuff */ @@ -445,27 +437,14 @@ apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s) { (void)pool; ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "h2_h2, child_init"); - opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); - opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); - - if (!opt_ssl_is_https || !opt_ssl_var_lookup) { - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, - APLOGNO(02951) "mod_ssl does not seem to be enabled"); - } - cipher_init(pool); return APR_SUCCESS; } -int h2_h2_is_tls(conn_rec *c) -{ - return opt_ssl_is_https && opt_ssl_is_https(c); -} - int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all) { - int is_tls = h2_h2_is_tls(c); + int is_tls = ap_ssl_conn_is_ssl(c); if (is_tls && h2_config_cgeti(c, H2_CONF_MODERN_TLS_ONLY) > 0) { /* Check TLS connection for modern TLS parameters, as defined in @@ -473,16 +452,11 @@ int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all) */ apr_pool_t *pool = c->pool; server_rec *s = c->base_server; - char *val; + const char *val; - if (!opt_ssl_var_lookup) { - /* unable to check */ - return 0; - } - /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2 */ - val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_PROTOCOL"); + val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL"); if (val && *val) { if (strncmp("TLS", val, 3) || !strcmp("TLSv1", val) @@ -501,7 +475,7 @@ int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all) /* Check TLS cipher blacklist */ - val = opt_ssl_var_lookup(pool, s, c, NULL, (char*)"SSL_CIPHER"); + val = ap_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER"); if (val && *val) { const char *source; if (cipher_is_blacklisted(val, &source)) { @@ -522,7 +496,7 @@ int h2_is_acceptable_connection(conn_rec *c, request_rec *r, int require_all) static int h2_allows_h2_direct(conn_rec *c) { - int is_tls = h2_h2_is_tls(c); + int is_tls = ap_ssl_conn_is_ssl(c); const char *needed_protocol = is_tls? "h2" : "h2c"; int h2_direct = h2_config_cgeti(c, H2_CONF_DIRECT); @@ -535,7 +509,7 @@ static int h2_allows_h2_direct(conn_rec *c) int h2_allows_h2_upgrade(request_rec *r) { int h2_upgrade = h2_config_rgeti(r, H2_CONF_UPGRADE); - return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(r->connection)); + return h2_upgrade > 0 || (h2_upgrade < 0 && !ap_ssl_conn_is_ssl(r->connection)); } /******************************************************************************* @@ -631,7 +605,7 @@ int h2_h2_process_conn(conn_rec* c) if (!ctx) { ctx = h2_ctx_get(c, 1); } - h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c"); + h2_ctx_protocol_set(ctx, ap_ssl_conn_is_ssl(c)? "h2" : "h2c"); } else if (APLOGctrace2(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, diff --git a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h index 339e898a108..8cfb9864fe8 100644 --- a/modules/http2/h2_h2.h +++ b/modules/http2/h2_h2.h @@ -41,10 +41,6 @@ const char *h2_h2_err_description(unsigned int h2_error); */ apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s); -/* Is the connection a TLS connection? - */ -int h2_h2_is_tls(conn_rec *c); - /* Register apache hooks for h2 protocol */ void h2_h2_register_hooks(void); diff --git a/modules/http2/h2_switch.c b/modules/http2/h2_switch.c index 9ec658b8e17..eb050150c9f 100644 --- a/modules/http2/h2_switch.c +++ b/modules/http2/h2_switch.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "h2_private.h" @@ -52,7 +53,7 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r, apr_array_header_t *proposals) { int proposed = 0; - int is_tls = h2_h2_is_tls(c); + int is_tls = ap_ssl_conn_is_ssl(c); const char **protos = is_tls? h2_tls_protos : h2_clear_protos; if (!h2_mpm_supported()) { @@ -127,7 +128,7 @@ static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, const char *protocol) { int found = 0; - const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos; + const char **protos = ap_ssl_conn_is_ssl(c)? h2_tls_protos : h2_clear_protos; const char **p = protos; (void)s; diff --git a/modules/lua/mod_lua.c b/modules/lua/mod_lua.c index 53612f9bb7d..23114304b35 100644 --- a/modules/lua/mod_lua.c +++ b/modules/lua/mod_lua.c @@ -24,7 +24,6 @@ #include "lua_apr.h" #include "lua_config.h" #include "apr_optional.h" -#include "mod_ssl.h" #include "mod_auth.h" #include "util_mutex.h" @@ -53,8 +52,6 @@ APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ap_lua, AP_LUA, int, lua_open, APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ap_lua, AP_LUA, int, lua_request, (lua_State *L, request_rec *r), (L, r), OK, DECLINED) -static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *lua_ssl_val = NULL; -static APR_OPTIONAL_FN_TYPE(ssl_is_https) *lua_ssl_is_https = NULL; module AP_MODULE_DECLARE_DATA lua_module; @@ -1688,15 +1685,12 @@ static const char *register_lua_root(cmd_parms *cmd, void *_cfg, const char *ap_lua_ssl_val(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *var) { - if (lua_ssl_val) { - return (const char *)lua_ssl_val(p, s, c, r, (char *)var); - } - return NULL; + return ap_ssl_var_lookup(p, s, c, r, var); } int ap_lua_ssl_is_https(conn_rec *c) { - return lua_ssl_is_https ? lua_ssl_is_https(c) : 0; + return ap_ssl_conn_is_ssl(c); } /*******************************/ @@ -2002,9 +1996,6 @@ static int lua_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t **pool; apr_status_t rs; - lua_ssl_val = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); - lua_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); - if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) return OK; diff --git a/modules/lua/mod_lua.h b/modules/lua/mod_lua.h index 46395da7841..33807fbdb56 100644 --- a/modules/lua/mod_lua.h +++ b/modules/lua/mod_lua.h @@ -26,6 +26,7 @@ #include "http_request.h" #include "http_log.h" #include "http_protocol.h" +#include "http_ssl.h" #include "ap_regex.h" #include "ap_config.h" diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c index 989244d43e0..102a93c4335 100644 --- a/modules/mappers/mod_rewrite.c +++ b/modules/mappers/mod_rewrite.c @@ -93,11 +93,10 @@ #include "http_core.h" #include "http_log.h" #include "http_protocol.h" +#include "http_ssl.h" #include "http_vhost.h" #include "util_mutex.h" -#include "mod_ssl.h" - #include "mod_rewrite.h" #include "ap_expr.h" @@ -421,8 +420,6 @@ static apr_global_mutex_t *rewrite_mapr_lock_acquire = NULL; static const char *rewritemap_mutex_type = "rewrite-map"; /* Optional functions imported from mod_ssl when loaded: */ -static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *rewrite_ssl_lookup = NULL; -static APR_OPTIONAL_FN_TYPE(ssl_is_https) *rewrite_is_https = NULL; static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus); /* @@ -1866,8 +1863,8 @@ static char *lookup_variable(char *var, rewrite_ctx *ctx) result = getenv(var); } } - else if (var[4] && !strncasecmp(var, "SSL", 3) && rewrite_ssl_lookup) { - result = rewrite_ssl_lookup(r->pool, r->server, r->connection, r, + else if (var[4] && !strncasecmp(var, "SSL", 3)) { + result = ap_ssl_var_lookup(r->pool, r->server, r->connection, r, var + 4); } } @@ -1965,7 +1962,7 @@ static char *lookup_variable(char *var, rewrite_ctx *ctx) case 5: if (!strcmp(var, "HTTPS")) { - int flag = rewrite_is_https && rewrite_is_https(r->connection); + int flag = ap_ssl_conn_is_ssl(r->connection); return apr_pstrdup(r->pool, flag ? "on" : "off"); } break; @@ -4525,9 +4522,6 @@ static int post_config(apr_pool_t *p, } } - rewrite_ssl_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); - rewrite_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); - return OK; } diff --git a/modules/md/config2.m4 b/modules/md/config2.m4 index 9184a3995de..bcce501633a 100644 --- a/modules/md/config2.m4 +++ b/modules/md/config2.m4 @@ -248,12 +248,12 @@ md_acme.lo dnl md_acme_acct.lo dnl md_acme_authz.lo dnl md_acme_drive.lo dnl -md_acmev1_drive.lo dnl md_acmev2_drive.lo dnl md_acme_order.lo dnl md_core.lo dnl md_curl.lo dnl md_crypt.lo dnl +md_event.lo dnl md_http.lo dnl md_json.lo dnl md_jws.lo dnl diff --git a/modules/md/md.h b/modules/md/md.h index 182d00b484d..78b7ef863be 100644 --- a/modules/md/md.h +++ b/modules/md/md.h @@ -17,6 +17,8 @@ #ifndef mod_md_md_h #define mod_md_md_h +#include + #include "md_time.h" #include "md_version.h" @@ -80,18 +82,18 @@ struct md_t { md_require_t require_https; /* Iff https: is required for this MD */ int renew_mode; /* mode of obtaining credentials */ - struct md_pkey_spec_t *pkey_spec;/* specification for generating new private keys */ + struct md_pkeys_spec_t *pks; /* specification for generating private keys */ int must_staple; /* certificates should set the OCSP Must Staple extension */ - md_timeslice_t *renew_window; /* time before expiration that starts renewal */ - md_timeslice_t *warn_window; /* time before expiration that warnings are sent out */ + md_timeslice_t *renew_window; /* time before expiration that starts renewal */ + md_timeslice_t *warn_window; /* time before expiration that warnings are sent out */ const char *ca_url; /* url of CA certificate service */ const char *ca_proto; /* protocol used vs CA (e.g. ACME) */ const char *ca_account; /* account used at CA */ const char *ca_agreement; /* accepted agreement uri between CA and user */ struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */ - const char *cert_file; /* != NULL iff pubcert file explicitly configured */ - const char *pkey_file; /* != NULL iff privkey file explicitly configured */ + struct apr_array_header_t *cert_files; /* != NULL iff pubcerts explicitly configured */ + struct apr_array_header_t *pkey_files; /* != NULL iff privkeys explicitly configured */ md_state_t state; /* state of this MD */ @@ -116,7 +118,7 @@ struct md_t { #define MD_KEY_CA "ca" #define MD_KEY_CA_URL "ca-url" #define MD_KEY_CERT "cert" -#define MD_KEY_CERT_FILE "cert-file" +#define MD_KEY_CERT_FILES "cert-files" #define MD_KEY_CERTIFICATE "certificate" #define MD_KEY_CHALLENGE "challenge" #define MD_KEY_CHALLENGES "challenges" @@ -125,6 +127,7 @@ struct md_t { #define MD_KEY_CONTACT "contact" #define MD_KEY_CONTACTS "contacts" #define MD_KEY_CSR "csr" +#define MD_KEY_CURVE "curve" #define MD_KEY_DETAIL "detail" #define MD_KEY_DISABLED "disabled" #define MD_KEY_DIR "dir" @@ -155,12 +158,13 @@ struct md_t { #define MD_KEY_NAME "name" #define MD_KEY_NEXT_RUN "next-run" #define MD_KEY_NOTIFIED "notified" +#define MD_KEY_NOTIFIED_RENEWED "notified-renewed" #define MD_KEY_OCSP "ocsp" #define MD_KEY_OCSPS "ocsps" #define MD_KEY_ORDERS "orders" #define MD_KEY_PERMANENT "permanent" #define MD_KEY_PKEY "privkey" -#define MD_KEY_PKEY_FILE "pkey-file" +#define MD_KEY_PKEY_FILES "pkey-files" #define MD_KEY_PROBLEM "problem" #define MD_KEY_PROTO "proto" #define MD_KEY_READY "ready" @@ -281,6 +285,9 @@ md_t *md_from_json(struct md_json_t *json, apr_pool_t *p); int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t* alt_names); +/* how many certificates this domain has/will eventually have. */ +int md_cert_count(const md_t *md); + #define LE_ACMEv1_PROD "https://acme-v01.api.letsencrypt.org/directory" #define LE_ACMEv1_STAGING "https://acme-staging.api.letsencrypt.org/directory" diff --git a/modules/md/md_acme.c b/modules/md/md_acme.c index c085ba35184..52fab0af1ea 100644 --- a/modules/md/md_acme.c +++ b/modules/md/md_acme.c @@ -43,29 +43,30 @@ static const char *base_product= "-"; typedef struct acme_problem_status_t acme_problem_status_t; struct acme_problem_status_t { - const char *type; - apr_status_t rv; + const char *type; /* the ACME error string */ + apr_status_t rv; /* what Apache status code we give it */ + int input_related; /* if error indicates wrong input value */ }; static acme_problem_status_t Problems[] = { - { "acme:error:badCSR", APR_EINVAL }, - { "acme:error:badNonce", APR_EAGAIN }, - { "acme:error:badSignatureAlgorithm", APR_EINVAL }, - { "acme:error:invalidContact", APR_BADARG }, - { "acme:error:unsupportedContact", APR_EGENERAL }, - { "acme:error:malformed", APR_EINVAL }, - { "acme:error:rateLimited", APR_BADARG }, - { "acme:error:rejectedIdentifier", APR_BADARG }, - { "acme:error:serverInternal", APR_EGENERAL }, - { "acme:error:unauthorized", APR_EACCES }, - { "acme:error:unsupportedIdentifier", APR_BADARG }, - { "acme:error:userActionRequired", APR_EAGAIN }, - { "acme:error:badRevocationReason", APR_EINVAL }, - { "acme:error:caa", APR_EGENERAL }, - { "acme:error:dns", APR_EGENERAL }, - { "acme:error:connection", APR_EGENERAL }, - { "acme:error:tls", APR_EGENERAL }, - { "acme:error:incorrectResponse", APR_EGENERAL }, + { "acme:error:badCSR", APR_EINVAL, 1 }, + { "acme:error:badNonce", APR_EAGAIN, 0 }, + { "acme:error:badSignatureAlgorithm", APR_EINVAL, 1 }, + { "acme:error:invalidContact", APR_BADARG, 1 }, + { "acme:error:unsupportedContact", APR_EGENERAL, 1 }, + { "acme:error:malformed", APR_EINVAL, 1 }, + { "acme:error:rateLimited", APR_BADARG, 0 }, + { "acme:error:rejectedIdentifier", APR_BADARG, 1 }, + { "acme:error:serverInternal", APR_EGENERAL, 0 }, + { "acme:error:unauthorized", APR_EACCES, 0 }, + { "acme:error:unsupportedIdentifier", APR_BADARG, 1 }, + { "acme:error:userActionRequired", APR_EAGAIN, 0 }, + { "acme:error:badRevocationReason", APR_EINVAL, 1 }, + { "acme:error:caa", APR_EGENERAL, 0 }, + { "acme:error:dns", APR_EGENERAL, 0 }, + { "acme:error:connection", APR_EGENERAL, 0 }, + { "acme:error:tls", APR_EGENERAL, 0 }, + { "acme:error:incorrectResponse", APR_EGENERAL, 0 }, }; static apr_status_t problem_status_get(const char *type) { @@ -86,6 +87,25 @@ static apr_status_t problem_status_get(const char *type) { return APR_EGENERAL; } +int md_acme_problem_is_input_related(const char *problem) { + size_t i; + + if (!problem) return 0; + if (strstr(problem, "urn:ietf:params:") == problem) { + problem += strlen("urn:ietf:params:"); + } + else if (strstr(problem, "urn:") == problem) { + problem += strlen("urn:"); + } + + for(i = 0; i < (sizeof(Problems)/sizeof(Problems[0])); ++i) { + if (!apr_strnatcasecmp(problem, Problems[i].type)) { + return Problems[i].input_related; + } + } + return 0; +} + /**************************************************************************************************/ /* acme requests */ @@ -101,13 +121,7 @@ static void req_update_nonce(md_acme_t *acme, apr_table_t *hdrs) static apr_status_t http_update_nonce(const md_http_response_t *res, void *data) { - md_acme_t *acme = data; - if (res->headers) { - const char *nonce = apr_table_get(res->headers, "Replay-Nonce"); - if (nonce) { - acme->nonce = apr_pstrdup(acme->p, nonce); - } - } + req_update_nonce(data, res->headers); return APR_SUCCESS; } @@ -143,11 +157,6 @@ static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, co return req; } -static apr_status_t acmev1_new_nonce(md_acme_t *acme) -{ - return md_http_HEAD_perform(acme->http, acme->api.v1.new_reg, NULL, http_update_nonce, acme); -} - static apr_status_t acmev2_new_nonce(md_acme_t *acme) { return md_http_HEAD_perform(acme->http, acme->api.v2.new_nonce, NULL, http_update_nonce, acme); @@ -163,13 +172,15 @@ apr_status_t md_acme_init(apr_pool_t *p, const char *base, int init_ssl) static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t *res) { const char *ctype; - md_json_t *problem; - + md_json_t *problem = NULL; + apr_status_t rv; + ctype = apr_table_get(req->resp_hdrs, "content-type"); + ctype = md_util_parse_ct(res->req->pool, ctype); if (ctype && !strcmp(ctype, "application/problem+json")) { /* RFC 7807 */ - md_json_read_http(&problem, req->p, res); - if (problem) { + rv = md_json_read_http(&problem, req->p, res); + if (rv == APR_SUCCESS && problem) { const char *ptype, *pdetail; req->resp_json = problem; @@ -213,30 +224,6 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t /**************************************************************************************************/ /* ACME requests with nonce handling */ -static apr_status_t acmev1_req_init(md_acme_req_t *req, md_json_t *jpayload) -{ - md_data_t payload; - - if (!req->acme->acct) { - return APR_EINVAL; - } - if (jpayload) { - payload.data = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT); - if (!payload.data) { - return APR_EINVAL; - } - } - else { - payload.data = ""; - } - - payload.len = strlen(payload.data); - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p, - "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload.len, payload.data); - return md_jws_sign(&req->req_json, req->p, &payload, - req->prot_hdrs, req->acme->acct_key, NULL); -} - static apr_status_t acmev2_req_init(md_acme_req_t *req, md_json_t *jpayload) { md_data_t payload; @@ -366,8 +353,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req) if (APR_SUCCESS != rv) goto leave; } - if (!strcmp("GET", req->method) && !req->on_init && !req->req_json - && MD_ACME_VERSION_MAJOR(acme->version) > 1) { + if (!strcmp("GET", req->method) && !req->on_init && !req->req_json) { /* See * and * and @@ -376,6 +362,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req) * our HTTP client. */ req->method = "POST"; req->on_init = acmev2_GET_as_POST_init; + /*req->max_retries = 0; don't do retries on these "GET"s */ } /* Besides GET/HEAD, we always need a fresh nonce */ @@ -391,9 +378,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req) } apr_table_set(req->prot_hdrs, "nonce", acme->nonce); - if (MD_ACME_VERSION_MAJOR(acme->version) > 1) { - apr_table_set(req->prot_hdrs, "url", req->url); - } + apr_table_set(req->prot_hdrs, "url", req->url); acme->nonce = NULL; } @@ -573,7 +558,7 @@ apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store, rv = md_acme_acct_validate(acme, store, p); } else { - /* account is from a nother server or, more likely, from another + /* account is from another server or, more likely, from another * protocol endpoint on the same server */ rv = APR_ENOENT; } @@ -586,17 +571,7 @@ apr_status_t md_acme_save_acct(md_acme_t *acme, apr_pool_t *p, md_store_t *store return md_acme_acct_save(store, p, acme, &acme->acct_id, acme->acct, acme->acct_key); } -static apr_status_t acmev1_POST_new_account(md_acme_t *acme, - md_acme_req_init_cb *on_init, - md_acme_req_json_cb *on_json, - md_acme_req_res_cb *on_res, - md_acme_req_err_cb *on_err, - void *baton) -{ - return md_acme_POST(acme, acme->api.v1.new_reg, on_init, on_json, on_res, on_err, baton); -} - -static apr_status_t acmev2_POST_new_account(md_acme_t *acme, +static apr_status_t acmev2_POST_new_account(md_acme_t *acme, md_acme_req_init_cb *on_init, md_acme_req_json_cb *on_json, md_acme_req_res_cb *on_res, @@ -620,7 +595,7 @@ apr_status_t md_acme_POST_new_account(md_acme_t *acme, /* ACME setup */ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url, - const char *proxy_url) + const char *proxy_url, const char *ca_file) { md_acme_t *acme; const char *err = NULL; @@ -644,8 +619,9 @@ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url, acme->user_agent = apr_psprintf(p, "%s mod_md/%s", base_product, MOD_MD_VERSION); acme->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL; - acme->max_retries = 3; - + acme->max_retries = 99; + acme->ca_file = ca_file; + if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: %s", url); return APR_EINVAL; @@ -706,28 +682,21 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data) } /* What have we got? */ - if ((s = md_json_dups(acme->p, json, "new-authz", NULL))) { - acme->api.v1.new_authz = s; - acme->api.v1.new_cert = md_json_dups(acme->p, json, "new-cert", NULL); - acme->api.v1.new_reg = md_json_dups(acme->p, json, "new-reg", NULL); - acme->api.v1.revoke_cert = md_json_dups(acme->p, json, "revoke-cert", NULL); - if (acme->api.v1.new_authz && acme->api.v1.new_cert - && acme->api.v1.new_reg && acme->api.v1.revoke_cert) { - acme->version = MD_ACME_VERSION_1; - } - acme->ca_agreement = md_json_dups(acme->p, json, "meta", "terms-of-service", NULL); - acme->new_nonce_fn = acmev1_new_nonce; - acme->req_init_fn = acmev1_req_init; - acme->post_new_account_fn = acmev1_POST_new_account; - } - else if ((s = md_json_dups(acme->p, json, "newAccount", NULL))) { + if ((s = md_json_dups(acme->p, json, "newAccount", NULL))) { acme->api.v2.new_account = s; acme->api.v2.new_order = md_json_dups(acme->p, json, "newOrder", NULL); acme->api.v2.revoke_cert = md_json_dups(acme->p, json, "revokeCert", NULL); acme->api.v2.key_change = md_json_dups(acme->p, json, "keyChange", NULL); acme->api.v2.new_nonce = md_json_dups(acme->p, json, "newNonce", NULL); - if (acme->api.v2.new_account && acme->api.v2.new_order - && acme->api.v2.revoke_cert && acme->api.v2.key_change + /* RFC 8555 only requires "directory" and "newNonce" resources. + * mod_md uses "newAccount" and "newOrder" so check for them. + * But mod_md does not use the "revokeCert" or "keyChange" + * resources, so tolerate the absense of those keys. In the + * future if mod_md implements revocation or key rollover then + * the use of those features should be predicated on the + * server's advertised capabilities. */ + if (acme->api.v2.new_account + && acme->api.v2.new_order && acme->api.v2.new_nonce) { acme->version = MD_ACME_VERSION_2; } @@ -736,7 +705,19 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data) acme->req_init_fn = acmev2_req_init; acme->post_new_account_fn = acmev2_POST_new_account; } - + else if ((s = md_json_dups(acme->p, json, "new-authz", NULL))) { + acme->api.v1.new_authz = s; + acme->api.v1.new_cert = md_json_dups(acme->p, json, "new-cert", NULL); + acme->api.v1.new_reg = md_json_dups(acme->p, json, "new-reg", NULL); + acme->api.v1.revoke_cert = md_json_dups(acme->p, json, "revoke-cert", NULL); + if (acme->api.v1.new_authz && acme->api.v1.new_cert + && acme->api.v1.new_reg && acme->api.v1.revoke_cert) { + acme->version = MD_ACME_VERSION_1; + } + acme->ca_agreement = md_json_dups(acme->p, json, "meta", "terms-of-service", NULL); + /* we init that far, but will not use the v1 api */ + } + if (MD_ACME_VERSION_UNKNOWN == acme->version) { md_result_printf(result, APR_EINVAL, "Unable to understand ACME server response from <%s>. " @@ -766,6 +747,7 @@ apr_status_t md_acme_setup(md_acme_t *acme, md_result_t *result) md_http_set_timeout_default(acme->http, apr_time_from_sec(10 * 60)); md_http_set_connect_timeout_default(acme->http, apr_time_from_sec(30)); md_http_set_stalling_default(acme->http, 10, apr_time_from_sec(30)); + md_http_set_ca_file(acme->http, acme->ca_file); md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url); diff --git a/modules/md/md_acme.h b/modules/md/md_acme.h index f6af75fb049..004e7867ea4 100644 --- a/modules/md/md_acme.h +++ b/modules/md/md_acme.h @@ -97,6 +97,7 @@ struct md_acme_t { apr_pool_t *p; const char *user_agent; const char *proxy_url; + const char *ca_file; const char *acct_id; /* local storage id account was loaded from or NULL */ struct md_acme_acct_t *acct; /* account at ACME server to use for requests */ @@ -104,7 +105,7 @@ struct md_acme_t { int version; /* as detected from the server */ union { - struct { + struct { /* obsolete */ const char *new_authz; const char *new_cert; const char *new_reg; @@ -149,7 +150,7 @@ apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version, int init_s * @param proxy_url optional url of a HTTP(S) proxy to use */ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url, - const char *proxy_url); + const char *proxy_url, const char *ca_file); /** * Contact the ACME server and retrieve its directory information. @@ -288,4 +289,11 @@ apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *jpayloa apr_status_t md_acme_protos_add(struct apr_hash_t *protos, apr_pool_t *p); +/** + * Return != 0 iff the given problem identifier is an ACME error string + * indicating something is wrong with the input values, e.g. from our + * configuration. + */ +int md_acme_problem_is_input_related(const char *problem); + #endif /* md_acme_h */ diff --git a/modules/md/md_acme_acct.c b/modules/md/md_acme_acct.c index 98443d2fd8d..3c0f4536059 100644 --- a/modules/md/md_acme_acct.c +++ b/modules/md/md_acme_acct.c @@ -406,18 +406,8 @@ typedef struct { static apr_status_t on_init_acct_upd(md_acme_req_t *req, void *baton) { - md_json_t *jpayload; - (void)baton; - jpayload = md_json_create(req->p); - switch (MD_ACME_VERSION_MAJOR(req->acme->version)) { - case 1: - md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL); - break; - default: - break; - } - return md_acme_req_body_init(req, jpayload); + return md_acme_req_body_init(req, NULL); } static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p, @@ -495,23 +485,10 @@ static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton) md_json_t *jpayload; jpayload = md_json_create(req->p); - - switch (MD_ACME_VERSION_MAJOR(req->acme->version)) { - case 1: - md_json_sets("new-reg", jpayload, MD_KEY_RESOURCE, NULL); - md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL); - if (ctx->agreement) { - md_json_sets(ctx->agreement, jpayload, MD_KEY_AGREEMENT, NULL); - } - break; - default: - md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL); - if (ctx->agreement) { - md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL); - } - break; + md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL); + if (ctx->agreement) { + md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL); } - return md_acme_req_body_init(req, jpayload); } @@ -616,15 +593,7 @@ static apr_status_t on_init_acct_del(md_acme_req_t *req, void *baton) (void)baton; jpayload = md_json_create(req->p); - switch (MD_ACME_VERSION_MAJOR(req->acme->version)) { - case 1: - md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL); - md_json_setb(1, jpayload, "delete", NULL); - break; - default: - md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL); - break; - } + md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL); return md_acme_req_body_init(req, jpayload); } @@ -653,16 +622,8 @@ static apr_status_t on_init_agree_tos(md_acme_req_t *req, void *baton) md_json_t *jpayload; jpayload = md_json_create(req->p); - switch (MD_ACME_VERSION_MAJOR(req->acme->version)) { - case 1: - md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL); - md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL); - break; - default: - if (ctx->acme->acct->agreement) { - md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL); - } - break; + if (ctx->acme->acct->agreement) { + md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL); } return md_acme_req_body_init(req, jpayload); } diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c index b76a5a6ac6e..883e319b48a 100644 --- a/modules/md/md_acme_authz.c +++ b/modules/md/md_acme_authz.c @@ -76,57 +76,6 @@ static void authz_req_ctx_init(authz_req_ctx *ctx, md_acme_t *acme, ctx->authz = authz; } -static apr_status_t on_init_authz(md_acme_req_t *req, void *baton) -{ - authz_req_ctx *ctx = baton; - md_json_t *jpayload; - - jpayload = md_json_create(req->p); - md_json_sets("new-authz", jpayload, MD_KEY_RESOURCE, NULL); - md_json_sets("dns", jpayload, MD_KEY_IDENTIFIER, MD_KEY_TYPE, NULL); - md_json_sets(ctx->domain, jpayload, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL); - - return md_acme_req_body_init(req, jpayload); -} - -static apr_status_t authz_created(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs, - md_json_t *body, void *baton) -{ - authz_req_ctx *ctx = baton; - const char *location = apr_table_get(hdrs, "location"); - apr_status_t rv = APR_SUCCESS; - - (void)acme; - (void)p; - if (location) { - ctx->authz = md_acme_authz_create(ctx->p); - ctx->authz->domain = apr_pstrdup(ctx->p, ctx->domain); - ctx->authz->url = apr_pstrdup(ctx->p, location); - ctx->authz->resource = md_json_clone(ctx->p, body); - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "authz_new at %s", location); - } - else { - rv = APR_EINVAL; - md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ctx->p, "new authz, no location header"); - } - return rv; -} - -apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, md_acme_t *acme, - const char *domain, apr_pool_t *p) -{ - apr_status_t rv; - authz_req_ctx ctx; - - authz_req_ctx_init(&ctx, acme, domain, NULL, p); - - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "create new authz"); - rv = md_acme_POST(acme, acme->api.v1.new_authz, on_init_authz, authz_created, NULL, NULL, &ctx); - - *pauthz = (APR_SUCCESS == rv)? ctx.authz : NULL; - return rv; -} - /**************************************************************************************************/ /* Update an existing authorization */ @@ -244,17 +193,10 @@ static md_acme_authz_cha_t *cha_from_json(apr_pool_t *p, size_t index, md_json_t static apr_status_t on_init_authz_resp(md_acme_req_t *req, void *baton) { - authz_req_ctx *ctx = baton; md_json_t *jpayload; + (void)baton; jpayload = md_json_create(req->p); - if (MD_ACME_VERSION_MAJOR(req->acme->version) <= 1) { - md_json_sets(MD_KEY_CHALLENGE, jpayload, MD_KEY_RESOURCE, NULL); - } - if (ctx->challenge->key_authz) { - md_json_sets(ctx->challenge->key_authz, jpayload, MD_KEY_KEYAUTHZ, NULL); - } - return md_acme_req_body_init(req, jpayload); } @@ -298,19 +240,21 @@ static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *a return rv; } -static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, +static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, - md_pkey_spec_t *key_spec, - apr_array_header_t *acme_tls_1_domains, - apr_table_t *env, apr_pool_t *p) + md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const char *mdomain, + apr_table_t *env, md_result_t *result, apr_pool_t *p) { const char *data; apr_status_t rv; int notify_server; - (void)key_spec; + (void)key_specs; (void)env; (void)acme_tls_1_domains; + (void)mdomain; + if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) { goto out; } @@ -318,14 +262,20 @@ static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01, MD_SV_TEXT, (void**)&data, p); if ((APR_SUCCESS == rv && strcmp(cha->key_authz, data)) || APR_STATUS_IS_ENOENT(rv)) { + const char *content = apr_psprintf(p, "%s\n", cha->key_authz); rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01, - MD_SV_TEXT, (void*)cha->key_authz, 0); + MD_SV_TEXT, (void*)content, 0); notify_server = 1; } if (APR_SUCCESS == rv && notify_server) { authz_req_ctx ctx; + const char *event; + /* Raise event that challenge data has been set up before we tell the + ACME server. Clusters might want to distribute it. */ + event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_HTTP01, authz->domain); + md_result_holler(result, event, p); /* challenge is setup or was changed from previous data, tell ACME server * so it may (re)try verification */ authz_req_ctx_init(&ctx, acme, NULL, authz, p); @@ -336,20 +286,26 @@ out: return rv; } +void tls_alpn01_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn ) +{ + *keyfn = apr_pstrcat(p, "acme-tls-alpn-01-", md_pkey_filename(kspec, p), NULL); + *certfn = apr_pstrcat(p, "acme-tls-alpn-01-", md_chain_filename(kspec, p), NULL); +} + static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, - md_pkey_spec_t *key_spec, - apr_array_header_t *acme_tls_1_domains, - apr_table_t *env, apr_pool_t *p) + md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const char *mdomain, + apr_table_t *env, md_result_t *result, apr_pool_t *p) { - md_cert_t *cha_cert; - md_pkey_t *cha_key; const char *acme_id, *token; apr_status_t rv; int notify_server; md_data_t data; - + int i; + (void)env; + (void)mdomain; if (md_array_str_index(acme_tls_1_domains, authz->domain, 0, 0) < 0) { rv = APR_ENOTIMPL; md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, @@ -360,48 +316,69 @@ static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_auth if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) { goto out; } - rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_CERT, - MD_SV_CERT, (void**)&cha_cert, p); - if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, authz->domain)) - || APR_STATUS_IS_ENOENT(rv)) { - - if (APR_SUCCESS != (rv = md_pkey_gen(&cha_key, p, key_spec))) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 challenge key", - authz->domain); - goto out; - } - /* Create a "tls-alpn-01" certificate for the domain we want to authenticate. - * The server will need to answer a TLS connection with SNI == authz->domain - * and ALPN procotol "acme-tls/1" with this certificate. - */ - MD_DATA_SET_STR(&data, cha->key_authz); - rv = md_crypt_sha256_digest_hex(&token, p, &data); - if (APR_SUCCESS != rv) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 cert", - authz->domain); - goto out; - } - - acme_id = apr_psprintf(p, "critical,DER:04:20:%s", token); - if (APR_SUCCESS != (rv = md_cert_make_tls_alpn_01(&cha_cert, authz->domain, acme_id, cha_key, - apr_time_from_sec(7 * MD_SECS_PER_DAY), p))) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 cert", - authz->domain); - goto out; - } + /* Create a "tls-alpn-01" certificate for the domain we want to authenticate. + * The server will need to answer a TLS connection with SNI == authz->domain + * and ALPN procotol "acme-tls/1" with this certificate. + */ + MD_DATA_SET_STR(&data, cha->key_authz); + rv = md_crypt_sha256_digest_hex(&token, p, &data); + if (APR_SUCCESS != rv) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 validation token", + authz->domain); + goto out; + } + acme_id = apr_psprintf(p, "critical,DER:04:20:%s", token); + + /* Each configured key type must be generated to ensure: + * that any fallback certs already given to mod_ssl are replaced. + * We expect that the validation client (at the CA) can deal with at + * least one of them. + */ + + for (i = 0; i < md_pkeys_spec_count(key_specs); ++i) { + char *kfn, *cfn; + md_cert_t *cha_cert; + md_pkey_t *cha_key; + md_pkey_spec_t *key_spec; + + key_spec = md_pkeys_spec_get(key_specs, i); + tls_alpn01_fnames(p, key_spec, &kfn, &cfn); + + rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, cfn, + MD_SV_CERT, (void**)&cha_cert, p); + if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, authz->domain)) + || APR_STATUS_IS_ENOENT(rv)) { + if (APR_SUCCESS != (rv = md_pkey_gen(&cha_key, p, key_spec))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 %s challenge key", + authz->domain, md_pkey_spec_name(key_spec)); + goto out; + } + + if (APR_SUCCESS != (rv = md_cert_make_tls_alpn_01(&cha_cert, authz->domain, acme_id, cha_key, + apr_time_from_sec(7 * MD_SECS_PER_DAY), p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 %s challenge cert", + authz->domain, md_pkey_spec_name(key_spec)); + goto out; + } - if (APR_SUCCESS == (rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_PKEY, - MD_SV_PKEY, (void*)cha_key, 0))) { - rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_CERT, - MD_SV_CERT, (void*)cha_cert, 0); + if (APR_SUCCESS == (rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, kfn, + MD_SV_PKEY, (void*)cha_key, 0))) { + rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, cfn, + MD_SV_CERT, (void*)cha_cert, 0); + } + ++notify_server; } - notify_server = 1; } if (APR_SUCCESS == rv && notify_server) { authz_req_ctx ctx; + const char *event; + /* Raise event that challenge data has been set up before we tell the + ACME server. Clusters might want to distribute it. */ + event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_TLSALPN01, authz->domain); + md_result_holler(result, event, p); /* challenge is setup or was changed from previous data, tell ACME server * so it may (re)try verification */ authz_req_ctx_init(&ctx, acme, NULL, authz, p); @@ -414,9 +391,9 @@ out: static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, - md_pkey_spec_t *key_spec, - apr_array_header_t *acme_tls_1_domains, - apr_table_t *env, apr_pool_t *p) + md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const char *mdomain, + apr_table_t *env, md_result_t *result, apr_pool_t *p) { const char *token; const char * const *argv; @@ -425,11 +402,12 @@ static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t * int exit_code, notify_server; authz_req_ctx ctx; md_data_t data; - + const char *event; + (void)store; - (void)key_spec; + (void)key_specs; (void)acme_tls_1_domains; - + dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01); if (!dns01_cmd) { rv = APR_ENOTIMPL; @@ -445,29 +423,35 @@ static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t * MD_DATA_SET_STR(&data, cha->key_authz); rv = md_crypt_sha256_digest64(&token, p, &data); if (APR_SUCCESS != rv) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create dns-01 token", - authz->domain); + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create dns-01 token for %s", + mdomain, authz->domain); goto out; } cmdline = apr_psprintf(p, "%s setup %s %s", dns01_cmd, authz->domain, token); md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 setup command: %s", authz->domain, cmdline); + apr_tokenize_to_argv(cmdline, (char***)&argv, p); - if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, &exit_code))) { + if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code))) { md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, - "%s: dns-01 setup command failed to execute", authz->domain); + "%s: dns-01 setup command failed to execute for %s", mdomain, authz->domain); goto out; } if (exit_code) { rv = APR_EGENERAL; md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, - "%s: dns-01 setup command returns %d", authz->domain, exit_code); + "%s: dns-01 setup command returns %d for %s", mdomain, exit_code, authz->domain); goto out; } - /* challenge is setup, tell ACME server so it may (re)try verification */ - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 setup succeeded", authz->domain); + /* Raise event that challenge data has been set up before we tell the + ACME server. Clusters might want to distribute it. */ + event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_DNS01, authz->domain); + md_result_holler(result, event, p); + /* challenge is setup, tell ACME server so it may (re)try verification */ + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 setup succeeded for %s", + mdomain, authz->domain); authz_req_ctx_init(&ctx, acme, NULL, authz, p); ctx.challenge = cha; rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx); @@ -476,7 +460,7 @@ out: return rv; } -static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, +static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, const char *mdomain, apr_table_t *env, apr_pool_t *p) { const char * const *argv; @@ -489,35 +473,37 @@ static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01); if (!dns01_cmd) { rv = APR_ENOTIMPL; - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 command not set", domain); + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 command not set for %s", + mdomain, domain); goto out; } cmdline = apr_psprintf(p, "%s teardown %s", dns01_cmd, domain); apr_tokenize_to_argv(cmdline, (char***)&argv, p); - if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, &exit_code)) || exit_code) { + if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code)) || exit_code) { md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, - "%s: dns-01 teardown command failed (exit code=%d)", - domain, exit_code); + "%s: dns-01 teardown command failed (exit code=%d) for %s", + mdomain, exit_code, domain); } out: return rv; } -static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, +static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, const char *mdomain, apr_table_t *env, apr_pool_t *p) { (void)env; + (void)mdomain; return md_store_purge(store, p, MD_SG_CHALLENGES, domain); } typedef apr_status_t cha_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, - md_pkey_spec_t *key_spec, - apr_array_header_t *acme_tls_1_domains, - apr_table_t *env, apr_pool_t *p); + md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const char *mdomain, + apr_table_t *env, md_result_t *result, apr_pool_t *p); -typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, +typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, const char *mdomain, apr_table_t *env, apr_pool_t *p); typedef struct { @@ -565,8 +551,8 @@ static apr_status_t find_type(void *baton, size_t index, md_json_t *json) } apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, - apr_array_header_t *challenges, md_pkey_spec_t *key_spec, - apr_array_header_t *acme_tls_1_domains, + apr_array_header_t *challenges, md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const char *mdomain, apr_table_t *env, apr_pool_t *p, const char **psetup_token, md_result_t *result) { @@ -603,18 +589,18 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) { md_result_activity_printf(result, "Setting up challenge '%s' for domain %s", fctx.accepted->type, authz->domain); - rv = CHA_TYPES[i].setup(fctx.accepted, authz, acme, store, key_spec, - acme_tls_1_domains, env, p); + rv = CHA_TYPES[i].setup(fctx.accepted, authz, acme, store, key_specs, + acme_tls_1_domains, mdomain, env, result, p); if (APR_SUCCESS == rv) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, - "%s: set up challenge '%s'", - authz->domain, fctx.accepted->type); - challenge_setup = CHA_TYPES[i].name; + "%s: set up challenge '%s' for %s", + authz->domain, fctx.accepted->type, mdomain); + challenge_setup = CHA_TYPES[i].name; goto out; } - md_result_printf(result, rv, "error setting up challenge '%s', " + md_result_printf(result, rv, "error setting up challenge '%s' for %s, " "for domain %s, looking for other option", - fctx.accepted->type, authz->domain); + fctx.accepted->type, authz->domain, mdomain); md_result_log(result, MD_LOG_INFO); } } @@ -648,8 +634,8 @@ out: return rv; } -apr_status_t md_acme_authz_teardown(struct md_store_t *store, - const char *token, apr_table_t *env, apr_pool_t *p) +apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *token, + const char *mdomain, apr_table_t *env, apr_pool_t *p) { char *challenge, *domain; int i; @@ -661,7 +647,7 @@ apr_status_t md_acme_authz_teardown(struct md_store_t *store, for (i = 0; i < (int)CHA_TYPES_LEN; ++i) { if (!apr_strnatcasecmp(CHA_TYPES[i].name, challenge)) { if (CHA_TYPES[i].teardown) { - return CHA_TYPES[i].teardown(store, domain, env, p); + return CHA_TYPES[i].teardown(store, domain, mdomain, env, p); } break; } diff --git a/modules/md/md_acme_authz.h b/modules/md/md_acme_authz.h index fe1abe55283..90193af14c5 100644 --- a/modules/md/md_acme_authz.h +++ b/modules/md/md_acme_authz.h @@ -56,31 +56,24 @@ struct md_acme_authz_t { }; #define MD_FN_HTTP01 "acme-http-01.txt" -#define MD_FN_TLSSNI01_CERT "acme-tls-sni-01.cert.pem" -#define MD_FN_TLSSNI01_PKEY "acme-tls-sni-01.key.pem" -#define MD_FN_TLSALPN01_CERT "acme-tls-alpn-01.cert.pem" -#define MD_FN_TLSALPN01_PKEY "acme-tls-alpn-01.key.pem" +void tls_alpn01_fnames(apr_pool_t *p, struct md_pkey_spec_t *kspec, char **keyfn, char **certfn ); md_acme_authz_t *md_acme_authz_create(apr_pool_t *p); -/* authz interaction with ACME server */ -apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, struct md_acme_t *acme, - const char *domain, apr_pool_t *p); - -apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url, +apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url, md_acme_authz_t **pauthz); apr_status_t md_acme_authz_update(md_acme_authz_t *authz, struct md_acme_t *acme, apr_pool_t *p); apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, struct md_acme_t *acme, struct md_store_t *store, apr_array_header_t *challenges, - struct md_pkey_spec_t *key_spec, - apr_array_header_t *acme_tls_1_domains, + struct md_pkeys_spec_t *key_spec, + apr_array_header_t *acme_tls_1_domains, const char *mdomain, struct apr_table_t *env, apr_pool_t *p, const char **setup_token, struct md_result_t *result); apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *setup_token, - struct apr_table_t *env, apr_pool_t *p); + const char *mdomain, struct apr_table_t *env, apr_pool_t *p); #endif /* md_acme_authz_h */ diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c index b88da757c51..4bdaf6bf652 100644 --- a/modules/md/md_acme_drive.c +++ b/modules/md/md_acme_drive.c @@ -40,7 +40,6 @@ #include "md_acme_order.h" #include "md_acme_drive.h" -#include "md_acmev1_drive.h" #include "md_acmev2_drive.h" /**************************************************************************************************/ @@ -140,7 +139,7 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result) /* ACMEv1 allowed registration of accounts without accepted Terms-of-Service. * ACMEv2 requires it. Fail early in this case with a meaningful error message. */ - if (!md->ca_agreement && MD_ACME_VERSION_MAJOR(ad->acme->version) > 1) { + if (!md->ca_agreement) { md_result_printf(result, APR_EINVAL, "the CA requires you to accept the terms-of-service " "as specified in <%s>. " @@ -187,10 +186,10 @@ static void get_up_link(md_proto_driver_t *d, apr_table_t *headers) { md_acme_driver_t *ad = d->baton; - ad->next_up_link = md_link_find_relation(headers, d->p, "up"); - if (ad->next_up_link) { + ad->chain_up_link = md_link_find_relation(headers, d->p, "up"); + if (ad->chain_up_link) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, - "server reports up link as %s", ad->next_up_link); + "server reports up link as %s", ad->chain_up_link); } } @@ -201,6 +200,9 @@ static apr_status_t add_http_certs(apr_array_header_t *chain, apr_pool_t *p, const char *ct; ct = apr_table_get(res->headers, "Content-Type"); + ct = md_util_parse_ct(res->req->pool, ct); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p, + "parse certs from %s -> %d (%s)", res->req->url, res->status, ct); if (ct && !strcmp("application/x-pkcs7-mime", ct)) { /* this looks like a root cert and we do not want those in our chain */ goto out; @@ -225,10 +227,10 @@ static apr_status_t on_add_cert(md_acme_t *acme, const md_http_response_t *res, int count; (void)acme; - count = ad->certs->nelts; - if (APR_SUCCESS == (rv = add_http_certs(ad->certs, d->p, res))) { + count = ad->cred->chain->nelts; + if (APR_SUCCESS == (rv = add_http_certs(ad->cred->chain, d->p, res))) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%d certs parsed", - ad->certs->nelts - count); + ad->cred->chain->nelts - count); get_up_link(d, res->headers); } return rv; @@ -276,9 +278,6 @@ static apr_status_t on_init_csr_req(md_acme_req_t *req, void *baton) md_json_t *jpayload; jpayload = md_json_create(req->p); - if (MD_ACME_VERSION_MAJOR(req->acme->version) == 1) { - md_json_sets("new-cert", jpayload, MD_KEY_RESOURCE, NULL); - } md_json_sets(ad->csr_der_64, jpayload, MD_KEY_CSR, NULL); return md_acme_req_body_init(req, jpayload); @@ -308,11 +307,11 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void } /* Check if it already was sent with this response */ - ad->next_up_link = NULL; + ad->chain_up_link = NULL; if (APR_SUCCESS == (rv = md_cert_read_http(&cert, d->p, res))) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "cert parsed"); - apr_array_clear(ad->certs); - APR_ARRAY_PUSH(ad->certs, md_cert_t*) = cert; + apr_array_clear(ad->cred->chain); + APR_ARRAY_PUSH(ad->cred->chain, md_cert_t*) = cert; get_up_link(d, res->headers); } else if (APR_STATUS_IS_ENOENT(rv)) { @@ -329,6 +328,7 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void /** * Pre-Req: all domains have been validated by the ACME server, e.g. all have AUTHZ * resources that have status 'valid' + * - acme_driver->cred keeps the credentials to setup (key spec) * - Setup private key, if not already there * - Generate a CSR with org, contact, etc * - Optionally enable must-staple OCSP extension @@ -339,39 +339,39 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void * - GET cert chain * - store cert chain */ -apr_status_t md_acme_drive_setup_certificate(md_proto_driver_t *d, md_result_t *result) +apr_status_t md_acme_drive_setup_cred_chain(md_proto_driver_t *d, md_result_t *result) { md_acme_driver_t *ad = d->baton; + md_pkey_spec_t *spec; md_pkey_t *privkey; apr_status_t rv; md_result_activity_printf(result, "Finalizing order for %s", ad->md->name); - - rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, &privkey, d->p); + + assert(ad->cred); + spec = ad->cred->spec; + + rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, spec, &privkey, d->p); if (APR_STATUS_IS_ENOENT(rv)) { - if (APR_SUCCESS == (rv = md_pkey_gen(&privkey, d->p, d->md->pkey_spec))) { - rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, d->md->name, privkey, 1); + if (APR_SUCCESS == (rv = md_pkey_gen(&privkey, d->p, spec))) { + rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, d->md->name, spec, privkey, 1); } - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate privkey", d->md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, + "%s: generate %s privkey", d->md->name, md_pkey_spec_name(spec)); } if (APR_SUCCESS != rv) goto leave; - md_result_activity_printf(result, "Creating CSR for %s", d->md->name); + md_result_activity_printf(result, "Creating %s CSR", md_pkey_spec_name(spec)); rv = md_cert_req_create(&ad->csr_der_64, d->md->name, ad->domains, ad->md->must_staple, privkey, d->p); - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create CSR", d->md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create %s CSR", + d->md->name, md_pkey_spec_name(spec)); if (APR_SUCCESS != rv) goto leave; + + md_result_activity_printf(result, "Submitting %s CSR to CA", md_pkey_spec_name(spec)); + assert(ad->order->finalize); + rv = md_acme_POST(ad->acme, ad->order->finalize, on_init_csr_req, NULL, csr_req, NULL, d); - md_result_activity_printf(result, "Submitting CSR to CA for %s", d->md->name); - switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) { - case 1: - rv = md_acme_POST(ad->acme, ad->acme->api.v1.new_cert, on_init_csr_req, NULL, csr_req, NULL, d); - break; - default: - assert(ad->order->finalize); - rv = md_acme_POST(ad->acme, ad->order->finalize, on_init_csr_req, NULL, csr_req, NULL, d); - break; - } leave: md_acme_report_result(ad->acme, rv, result); return rv; @@ -389,12 +389,13 @@ static apr_status_t on_add_chain(md_acme_t *acme, const md_http_response_t *res, (void)acme; ct = apr_table_get(res->headers, "Content-Type"); + ct = md_util_parse_ct(res->req->pool, ct); if (ct && !strcmp("application/x-pkcs7-mime", ct)) { /* root cert most likely, end it here */ return APR_SUCCESS; } - if (APR_SUCCESS == (rv = add_http_certs(ad->certs, d->p, res))) { + if (APR_SUCCESS == (rv = add_http_certs(ad->cred->chain, d->p, res))) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain cert parsed"); get_up_link(d, res->headers); } @@ -408,21 +409,26 @@ static apr_status_t get_chain(void *baton, int attempt) const char *prev_link = NULL; apr_status_t rv = APR_SUCCESS; - while (APR_SUCCESS == rv && ad->certs->nelts < 10) { - int nelts = ad->certs->nelts; + while (APR_SUCCESS == rv && ad->cred->chain->nelts < 10) { + int nelts = ad->cred->chain->nelts; - if (ad->next_up_link && (!prev_link || strcmp(prev_link, ad->next_up_link))) { - prev_link = ad->next_up_link; + if (ad->chain_up_link && (!prev_link || strcmp(prev_link, ad->chain_up_link))) { + prev_link = ad->chain_up_link; md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, - "next chain cert at %s", ad->next_up_link); - rv = md_acme_GET(ad->acme, ad->next_up_link, NULL, NULL, on_add_chain, NULL, d); + "next chain cert at %s", ad->chain_up_link); + rv = md_acme_GET(ad->acme, ad->chain_up_link, NULL, NULL, on_add_chain, NULL, d); - if (APR_SUCCESS == rv && nelts == ad->certs->nelts) { + if (APR_SUCCESS == rv && nelts == ad->cred->chain->nelts) { break; } + else if (APR_SUCCESS != rv) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, + "error retrieving certificate from %s", ad->chain_up_link); + return rv; + } } - else if (ad->certs->nelts <= 1) { + else if (ad->cred->chain->nelts <= 1) { /* This cannot be the complete chain (no one signs new web certs with their root) * and we did not see a "Link: ...rel=up", so we do not know how to continue. */ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, @@ -436,7 +442,7 @@ static apr_status_t get_chain(void *baton, int attempt) } } md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, - "got chain with %d certs (%d. attempt)", ad->certs->nelts, attempt); + "got chain with %d certs (%d. attempt)", ad->cred->chain->nelts, attempt); return rv; } @@ -446,16 +452,16 @@ static apr_status_t ad_chain_retrieve(md_proto_driver_t *d) apr_status_t rv; /* This may be called repeatedly and needs to progress. The relevant state is in - * ad->certs the certificate chain, starting with the new cert for the md + * ad->cred->chain the certificate chain, starting with the new cert for the md * ad->order->certificate the url where ACME offers us the new md certificate. This may * be a single one or even the complete chain - * ad->next_up_link in case the last certificate retrieval did not end the chain, + * ad->chain_up_link in case the last certificate retrieval did not end the chain, * the link header with relation "up" gives us the location * for the next cert in the chain */ - if (md_array_is_empty(ad->certs)) { + if (md_array_is_empty(ad->cred->chain)) { /* Need to start at the order */ - ad->next_up_link = NULL; + ad->chain_up_link = NULL; if (!ad->order) { rv = APR_EGENERAL; md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, @@ -487,6 +493,8 @@ out: static apr_status_t acme_driver_preload_init(md_proto_driver_t *d, md_result_t *result) { md_acme_driver_t *ad; + md_credentials_t *cred; + int i; md_result_set(result, APR_SUCCESS, NULL); @@ -498,7 +506,15 @@ static apr_status_t acme_driver_preload_init(md_proto_driver_t *d, md_result_t * ad->authz_monitor_timeout = apr_time_from_sec(30); ad->cert_poll_timeout = apr_time_from_sec(30); ad->ca_challenges = apr_array_make(d->p, 3, sizeof(const char*)); - ad->certs = apr_array_make(d->p, 5, sizeof(md_cert_t*)); + + /* We want to obtain credentials (key+certificate) for every key spec in this MD */ + ad->creds = apr_array_make(d->p, md_pkeys_spec_count(d->md->pks), sizeof(md_credentials_t*)); + for (i = 0; i < md_pkeys_spec_count(d->md->pks); ++i) { + cred = apr_pcalloc(d->p, sizeof(*cred)); + cred->spec = md_pkeys_spec_get(d->md->pks, i); + cred->chain = apr_array_make(d->p, 5, sizeof(md_cert_t*)); + APR_ARRAY_PUSH(ad->creds, md_credentials_t*) = cred; + } md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, result->status, d->p, "%s: init_base driver", d->md->name); @@ -570,7 +586,7 @@ static apr_status_t acme_driver_init(md_proto_driver_t *d, md_result_t *result) dis_http? " The http: challenge 'http-01' is disabled because the server seems not reachable on public port 80." : "", dis_https? " The https: challenge 'tls-alpn-01' is disabled because the server seems not reachable on public port 443." : "", dis_alpn_acme? " The https: challenge 'tls-alpn-01' is disabled because the Protocols configuration does not include the 'acme-tls/1' protocol." : "", - dis_dns? "The DNS challenge 'dns-01' is disabled because the directive 'MDChallengeDns01' is not configured." : "" + dis_dns? " The DNS challenge 'dns-01' is disabled because the directive 'MDChallengeDns01' is not configured." : "" ); goto leave; } @@ -584,15 +600,47 @@ leave: /**************************************************************************************************/ /* ACME staging */ +static apr_status_t load_missing_creds(md_proto_driver_t *d) +{ + md_acme_driver_t *ad = d->baton; + md_credentials_t *cred; + apr_array_header_t *chain; + int i, complete; + apr_status_t rv; + + complete = 1; + for (i = 0; i < ad->creds->nelts; ++i) { + rv = APR_SUCCESS; + cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*); + if (!cred->pkey) { + rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, cred->spec, &cred->pkey, d->p); + } + if (APR_SUCCESS == rv && md_array_is_empty(cred->chain)) { + rv = md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, cred->spec, &chain, d->p); + if (APR_SUCCESS == rv) { + apr_array_cat(cred->chain, chain); + } + } + if (APR_SUCCESS == rv) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "%s: credentials staged for %s certificate", + d->md->name, md_pkey_spec_name(cred->spec)); + } + else { + complete = 0; + } + } + return complete? APR_SUCCESS : APR_EAGAIN; +} + static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result) { md_acme_driver_t *ad = d->baton; int reset_staging = d->reset; apr_status_t rv = APR_SUCCESS; - apr_time_t now; - apr_array_header_t *staged_certs; + apr_time_t now, t, t2; + md_credentials_t *cred; char ts[APR_RFC822_DATE_LEN]; - int first = 0; + int i, first = 0; if (md_log_is_level(d->p, MD_LOG_DEBUG)) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, " @@ -640,27 +688,15 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result) goto out; } - if (ad->md) { - const char *keyfile, *certfile; - - rv = md_reg_get_cred_files(&keyfile, &certfile, d->reg, MD_SG_STAGING, d->md, d->p); - if (APR_SUCCESS == rv) { - if (md_array_is_empty(ad->certs) - && APR_SUCCESS == md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, &staged_certs, d->p)) { - apr_array_cat(ad->certs, staged_certs); - } - if (!md_array_is_empty(ad->certs)) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: all data staged", d->md->name); - rv = APR_SUCCESS; - goto ready; - } - } + if (ad->md && APR_SUCCESS == load_missing_creds(d)) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: all credentials staged", d->md->name); + goto ready; } /* Need to renew */ md_result_activity_printf(result, "Contacting ACME server for %s at %s", d->md->name, d->md->ca_url); - if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url, d->proxy_url))) { + if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url, d->proxy_url, d->ca_file))) { md_result_printf(result, rv, "setup ACME communications"); md_result_log(result, MD_LOG_ERR); goto out; @@ -688,51 +724,63 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result) ad->domains = md_dns_make_minimal(d->p, ad->md->domains); } - if (md_array_is_empty(ad->certs) - && APR_SUCCESS == md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, &staged_certs, d->p)) { - apr_array_cat(ad->certs, staged_certs); - } - - if (md_array_is_empty(ad->certs)) { - md_result_activity_printf(result, "Driving ACME protocol for renewal of %s", d->md->name); - /* The process of setting up challenges and verifying domain - * names differs between ACME versions. */ - switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) { - case 1: - rv = md_acmev1_drive_renew(ad, d, result); - break; - case 2: - rv = md_acmev2_drive_renew(ad, d, result); - break; - default: - md_result_printf(result, APR_EINVAL, - "ACME server has unknown major version %d (%x)", - MD_ACME_VERSION_MAJOR(ad->acme->version), ad->acme->version); - rv = result->status; - break; - } - if (APR_SUCCESS != rv) goto out; - } - - if (md_array_is_empty(ad->certs) || ad->next_up_link) { - md_result_activity_printf(result, "Retrieving certificate chain for %s", d->md->name); - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, - "%s: retrieving certificate chain", d->md->name); - rv = ad_chain_retrieve(d); - if (APR_SUCCESS != rv) { - md_result_printf(result, rv, "Unable to retrieve certificate chain."); - goto out; - } - - if (!md_array_is_empty(ad->certs)) { - rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, d->md->name, ad->certs, 0); - if (APR_SUCCESS != rv) { - md_result_printf(result, rv, "Saving new certificate chain."); - goto out; + if (APR_SUCCESS != load_missing_creds(d)) { + for (i = 0; i < ad->creds->nelts; ++i) { + ad->cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*); + if (!ad->cred->pkey || md_array_is_empty(ad->cred->chain)) { + md_result_activity_printf(result, "Driving ACME to renew %s certificate for %s", + md_pkey_spec_name(ad->cred->spec),d->md->name); + /* The process of setting up challenges and verifying domain + * names differs between ACME versions. */ + switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) { + case 1: + md_result_printf(result, APR_EINVAL, + "ACME server speaks version 1, an obsolete version of the ACME " + "protocol that is no longer supported."); + rv = result->status; + break; + default: + /* In principle, we only know ACME version 2. But we assume + that a new protocol which announces a directory with all members + from version 2 will act backward compatible. + This is, of course, an assumption... + */ + rv = md_acmev2_drive_renew(ad, d, result); + break; + } + if (APR_SUCCESS != rv) goto out; + + if (md_array_is_empty(ad->cred->chain) || ad->chain_up_link) { + md_result_activity_printf(result, "Retrieving %s certificate chain for %s", + md_pkey_spec_name(ad->cred->spec), d->md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, + "%s: retrieving %s certificate chain", + d->md->name, md_pkey_spec_name(ad->cred->spec)); + rv = ad_chain_retrieve(d); + if (APR_SUCCESS != rv) { + md_result_printf(result, rv, "Unable to retrieve %s certificate chain.", + md_pkey_spec_name(ad->cred->spec)); + goto out; + } + + if (!md_array_is_empty(ad->cred->chain)) { + rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, d->md->name, + ad->cred->spec, ad->cred->chain, 0); + if (APR_SUCCESS != rv) { + md_result_printf(result, rv, "Saving new %s certificate chain.", + md_pkey_spec_name(ad->cred->spec)); + goto out; + } + } + } + + /* Clean up the order, so the next pkey spec sets up a new one */ + md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env); } } } + /* As last step, cleanup any order we created so that challenge data * may be removed asap. */ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env); @@ -742,43 +790,44 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result) ready: md_result_activity_setn(result, NULL); /* we should have the complete cert chain now */ - assert(!md_array_is_empty(ad->certs)); - assert(ad->certs->nelts > 1); - + assert(APR_SUCCESS == load_missing_creds(d)); md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, - "%s: certificate ready, activation delay set to %s", + "%s: certificates ready, activation delay set to %s", d->md->name, md_duration_format(d->p, d->activation_delay)); /* determine when it should be activated */ - md_result_delay_set(result, md_cert_get_not_before(APR_ARRAY_IDX(ad->certs, 0, md_cert_t*))); + t = apr_time_now(); + for (i = 0; i < ad->creds->nelts; ++i) { + cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*); + t2 = md_cert_get_not_before(APR_ARRAY_IDX(cred->chain, 0, md_cert_t*)); + if (t2 > t) t = t2; + } + md_result_delay_set(result, t); /* If the existing MD is complete and un-expired, delay the activation * to 24 hours after new cert is valid (if there is enough time left), so * that cients with skewed clocks do not see a problem. */ now = apr_time_now(); if (d->md->state == MD_S_COMPLETE) { - const md_pubcert_t *pub; apr_time_t valid_until, delay_activation; md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, - "%s: state is COMPLETE, checking existing certificate", d->md->name); - if (APR_SUCCESS == md_reg_get_pubcert(&pub, d->reg, d->md, d->p)) { - valid_until = md_cert_get_not_after(APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*)); - if (d->activation_delay < 0) { - /* special simulation for test case */ - if (first) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, - "%s: delay ready_at to now+1s", d->md->name); - md_result_delay_set(result, apr_time_now() + apr_time_from_sec(1)); - } + "%s: state is COMPLETE, checking existing certificates", d->md->name); + valid_until = md_reg_valid_until(d->reg, d->md, d->p); + if (d->activation_delay < 0) { + /* special simulation for test case */ + if (first) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, + "%s: delay ready_at to now+1s", d->md->name); + md_result_delay_set(result, apr_time_now() + apr_time_from_sec(1)); } - else if (valid_until > now) { - delay_activation = d->activation_delay; - if (delay_activation > (valid_until - now)) { - delay_activation = (valid_until - now); - } - md_result_delay_set(result, result->ready_at + delay_activation); + } + else if (valid_until > now) { + delay_activation = d->activation_delay; + if (delay_activation > (valid_until - now)) { + delay_activation = (valid_until - now); } + md_result_delay_set(result, result->ready_at + delay_activation); } } @@ -815,10 +864,13 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro const char *name, md_result_t *result) { apr_status_t rv; - md_pkey_t *privkey, *acct_key; + md_pkey_t *acct_key; md_t *md; - apr_array_header_t *pubcert; + md_pkey_spec_t *pkspec; + md_credentials_t *creds; + apr_array_header_t *all_creds; struct md_acme_acct_t *acct; + int i; md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name); /* Load data from MD_SG_STAGING and save it into "load_group". @@ -833,15 +885,22 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro md_result_set(result, rv, "loading staged md.json"); goto leave; } - if (APR_SUCCESS != (rv = md_pkey_load(d->store, MD_SG_STAGING, name, &privkey, d->p))) { - md_result_set(result, rv, "loading staged privkey.pem"); - goto leave; - } - if (APR_SUCCESS != (rv = md_pubcert_load(d->store, MD_SG_STAGING, name, &pubcert, d->p))) { - md_result_set(result, rv, "loading staged pubcert.pem"); - goto leave; + + all_creds = apr_array_make(d->p, 5, sizeof(md_credentials_t*)); + for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) { + pkspec = md_pkeys_spec_get(md->pks, i); + if (APR_SUCCESS != (rv = md_creds_load(d->store, MD_SG_STAGING, name, pkspec, &creds, d->p))) { + md_result_printf(result, rv, "loading staged credentials #%d", i); + goto leave; + } + if (!creds->chain) { + rv = APR_ENOENT; + md_result_printf(result, rv, "no certificate in staged credentials #%d", i); + goto leave; + } + APR_ARRAY_PUSH(all_creds, md_credentials_t*) = creds; } - + /* See if staging holds a new or modified account data */ rv = md_acme_acct_load(&acct, &acct_key, d->store, MD_SG_STAGING, name, d->p); if (APR_STATUS_IS_ENOENT(rv)) { @@ -885,7 +944,7 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro } } - if (APR_SUCCESS != (rv = md_acme_create(&acme, d->p, md->ca_url, d->proxy_url))) { + if (APR_SUCCESS != (rv = md_acme_create(&acme, d->p, md->ca_url, d->proxy_url, d->ca_file))) { md_result_set(result, rv, "error setting up acme"); goto leave; } @@ -902,14 +961,15 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro md_result_set(result, rv, "writing md.json"); goto leave; } - if (APR_SUCCESS != (rv = md_pubcert_save(d->store, d->p, load_group, name, pubcert, 1))) { - md_result_set(result, rv, "writing pubcert.pem"); - goto leave; - } - if (APR_SUCCESS != (rv = md_pkey_save(d->store, d->p, load_group, name, privkey, 1))) { - md_result_set(result, rv, "writing privkey.pem"); - goto leave; + + for (i = 0; i < all_creds->nelts; ++i) { + creds = APR_ARRAY_IDX(all_creds, i, md_credentials_t*); + if (APR_SUCCESS != (rv = md_creds_save(d->store, d->p, load_group, name, creds, 1))) { + md_result_printf(result, rv, "writing credentials #%d", i); + goto leave; + } } + md_result_set(result, APR_SUCCESS, "saved staged data successfully"); leave: diff --git a/modules/md/md_acme_drive.h b/modules/md/md_acme_drive.h index 5111408c8cc..88761fab95d 100644 --- a/modules/md/md_acme_drive.h +++ b/modules/md/md_acme_drive.h @@ -18,23 +18,25 @@ struct apr_array_header_t; struct md_acme_order_t; +struct md_credentials_t; struct md_result_t; typedef struct md_acme_driver_t { md_proto_driver_t *driver; void *sub_driver; - int complete; - - md_pkey_t *privkey; /* the new private key */ - apr_array_header_t *certs; /* the certifiacte chain, starting with the new one */ - const char *next_up_link; /* where the next chain cert is */ - md_acme_t *acme; md_t *md; struct apr_array_header_t *domains; - apr_array_header_t *ca_challenges; + + int complete; + apr_array_header_t *creds; /* the new md_credentials_t */ + + struct md_credentials_t *cred; /* credentials currently being processed */ + const char *chain_up_link; /* Link header "up" from last chain retrieval, + needs to be followed */ + struct md_acme_order_t *order; apr_interval_time_t authz_monitor_timeout; @@ -45,8 +47,8 @@ typedef struct md_acme_driver_t { apr_status_t md_acme_drive_set_acct(struct md_proto_driver_t *d, struct md_result_t *result); -apr_status_t md_acme_drive_setup_certificate(struct md_proto_driver_t *d, - struct md_result_t *result); +apr_status_t md_acme_drive_setup_cred_chain(struct md_proto_driver_t *d, + struct md_result_t *result); apr_status_t md_acme_drive_cert_poll(struct md_proto_driver_t *d, int only_once); #endif /* md_acme_drive_h */ diff --git a/modules/md/md_acme_order.c b/modules/md/md_acme_order.c index 974334a79c9..5dde962afad 100644 --- a/modules/md/md_acme_order.c +++ b/modules/md/md_acme_order.c @@ -241,7 +241,7 @@ static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_li if (setup_token) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "order teardown setup %s", setup_token); - md_acme_authz_teardown(store, setup_token, env, p); + md_acme_authz_teardown(store, setup_token, md_name, env, p); } } } @@ -445,7 +445,7 @@ apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *a md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: check AUTHZ at %s", md->name, url); if (APR_SUCCESS != (rv = md_acme_authz_retrieve(acme, p, url, &authz))) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: check authz for %s", + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: check authz for %s", md->name, authz->domain); goto leave; } @@ -456,7 +456,8 @@ apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *a case MD_ACME_AUTHZ_S_PENDING: rv = md_acme_authz_respond(authz, acme, store, challenge_types, - md->pkey_spec, md->acme_tls_1_domains, + md->pks, + md->acme_tls_1_domains, md->name, env, p, &setup_token, result); if (APR_SUCCESS != rv) { goto leave; diff --git a/modules/md/md_acmev1_drive.c b/modules/md/md_acmev1_drive.c deleted file mode 100644 index 027d494b4a2..00000000000 --- a/modules/md/md_acmev1_drive.c +++ /dev/null @@ -1,189 +0,0 @@ -/* Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include -#include -#include -#include -#include - -#include "md.h" -#include "md_crypt.h" -#include "md_json.h" -#include "md_jws.h" -#include "md_http.h" -#include "md_log.h" -#include "md_result.h" -#include "md_reg.h" -#include "md_store.h" -#include "md_util.h" - -#include "md_acme.h" -#include "md_acme_acct.h" -#include "md_acme_authz.h" -#include "md_acme_order.h" - -#include "md_acme_drive.h" -#include "md_acmev1_drive.h" - -/**************************************************************************************************/ -/* authz/challenge setup */ - -/** - * Pre-Req: we have an account for the ACME server that has accepted the current license agreement - * For each domain in MD: - * - check if there already is a valid AUTHZ resource - * - if not, create an AUTHZ resource with challenge data - */ -static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result) -{ - md_acme_driver_t *ad = d->baton; - apr_status_t rv; - md_t *md = ad->md; - const char *url; - md_acme_authz_t *authz; - apr_array_header_t *domains_covered; - int i; - int changed = 0; - - assert(ad->md); - assert(ad->acme); - - md_result_activity_printf(result, "Setup order resource for %s", ad->md->name); - - /* For each domain in MD: AUTHZ setup - * if an AUTHZ resource is known, check if it is still valid - * if known AUTHZ resource is not valid, remove, goto 4.1.1 - * if no AUTHZ available, create a new one for the domain, store it - */ - rv = md_acme_order_load(d->store, MD_SG_STAGING, md->name, &ad->order, d->p); - if (!ad->order || APR_STATUS_IS_ENOENT(rv)) { - ad->order = md_acme_order_create(d->p); - rv = APR_SUCCESS; - } - else if (APR_SUCCESS != rv) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: loading authz data", md->name); - md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md->name, d->env); - return APR_EAGAIN; - } - - /* Retrieve all known authz from ACME server and check status etc. */ - domains_covered = apr_array_make(d->p, 5, sizeof(const char *)); - - for (i = 0; i < ad->order->authz_urls->nelts;) { - url = APR_ARRAY_IDX(ad->order->authz_urls, i, const char*); - rv = md_acme_authz_retrieve(ad->acme, d->p, url, &authz); - if (APR_SUCCESS == rv) { - if (md_array_str_index(ad->domains, authz->domain, 0, 0) < 0) { - md_acme_order_remove(ad->order, url); - changed = 1; - continue; - } - - APR_ARRAY_PUSH(domains_covered, const char *) = authz->domain; - ++i; - } - else if (APR_STATUS_IS_ENOENT(rv)) { - md_acme_order_remove(ad->order, url); - changed = 1; - continue; - } - else { - goto leave; - } - } - - /* Do we have authz urls for all domains? If not, register a new one */ - for (i = 0; i < ad->domains->nelts && APR_SUCCESS == rv; ++i) { - const char *domain = APR_ARRAY_IDX(ad->domains, i, const char *); - - if (md_array_str_index(domains_covered, domain, 0, 0) < 0) { - md_result_activity_printf(result, "Creating authz resource for %s", domain); - rv = md_acme_authz_register(&authz, ad->acme, domain, d->p); - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: created authz for %s (last problem: %s)", - md->name, domain, ad->acme->last->problem); - if (APR_SUCCESS != rv) goto leave; - rv = md_acme_order_add(ad->order, authz->url); - changed = 1; - } - } - - if (changed) { - rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING, md->name, ad->order, 0); - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: saved", md->name); - } - -leave: - md_acme_report_result(ad->acme, rv, result); - return rv; -} - -apr_status_t md_acmev1_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, md_result_t *result) -{ - apr_status_t rv = APR_SUCCESS; - const char *required; - - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: (ACMEv1) need certificate", d->md->name); - - /* Chose (or create) and ACME account to use */ - if (APR_SUCCESS != (rv = md_acme_drive_set_acct(d, result))) goto leave; - - /* Check that the account agreed to the terms-of-service, otherwise - * requests for new authorizations are denied. ToS may change during the - * lifetime of an account */ - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, - "%s: (ACMEv1) check Tems-of-Service agreement", d->md->name); - - rv = md_acme_check_agreement(ad->acme, d->p, ad->md->ca_agreement, &required); - if (APR_STATUS_IS_INCOMPLETE(rv) && required) { - /* The CA wants the user to agree to Terms-of-Services. Until the user - * has reconfigured and restarted the server, this MD cannot be - * driven further */ - ad->md->state = MD_S_MISSING_INFORMATION; - md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0); - md_result_printf(result, rv, - "the CA requires you to accept the terms-of-service as specified in <%s>. " - "Please read the document that you find at that URL and, if you agree to " - "the conditions, configure \"MDCertificateAgreement accepted\" " - "in your Apache. Then (graceful) restart the server to activate.", - required); - goto leave; - } - else if (APR_SUCCESS != rv) goto leave; - - if (!md_array_is_empty(ad->certs)) goto leave; - - rv = ad_setup_order(d, result); - if (APR_SUCCESS != rv) goto leave; - - rv = md_acme_order_start_challenges(ad->order, ad->acme, ad->ca_challenges, - d->store, d->md, d->env, result, d->p); - if (APR_SUCCESS != rv) goto leave; - - rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md, - ad->authz_monitor_timeout, result, d->p); - if (APR_SUCCESS != rv) goto leave; - - rv = md_acme_drive_setup_certificate(d, result); - -leave: - md_result_log(result, MD_LOG_DEBUG); - return result->status; -} - diff --git a/modules/md/md_acmev1_drive.h b/modules/md/md_acmev1_drive.h deleted file mode 100644 index 9dfa72981a8..00000000000 --- a/modules/md/md_acmev1_drive.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright 2019 greenbytes GmbH (https://www.greenbytes.de) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef md_acmev1_drive_h -#define md_acmev1_drive_h - -struct md_acme_driver_t; -struct md_proto_driver_t; -struct md_result_t; - -apr_status_t md_acmev1_drive_renew(struct md_acme_driver_t *ad, - struct md_proto_driver_t *d, - struct md_result_t *result); - -#endif /* md_acmev1_drive_h */ diff --git a/modules/md/md_acmev2_drive.c b/modules/md/md_acmev2_drive.c index 342e60a90f5..40add6f5655 100644 --- a/modules/md/md_acmev2_drive.c +++ b/modules/md/md_acmev2_drive.c @@ -101,7 +101,7 @@ apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m rv = md_acme_drive_set_acct(d, result); if (APR_SUCCESS != rv) goto leave; - if (!md_array_is_empty(ad->certs)) goto leave; + if (!md_array_is_empty(ad->cred->chain)) goto leave; /* ACMEv2 strategy: * 1. load an md_acme_order_t from STAGING, if present @@ -141,22 +141,26 @@ apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md, ad->authz_monitor_timeout, result, d->p); if (APR_SUCCESS != rv) goto leave; - - rv = md_acme_order_await_ready(ad->order, ad->acme, d->md, + + rv = md_acme_order_await_ready(ad->order, ad->acme, d->md, ad->authz_monitor_timeout, result, d->p); if (APR_SUCCESS != rv) goto leave; - - rv = md_acme_drive_setup_certificate(d, result); - if (APR_SUCCESS != rv) goto leave; - - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: finalized order", d->md->name); - + + if (MD_ACME_ORDER_ST_READY == ad->order->status) { + rv = md_acme_drive_setup_cred_chain(d, result); + if (APR_SUCCESS != rv) goto leave; + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: finalized order", d->md->name); + } + rv = md_acme_order_await_valid(ad->order, ad->acme, d->md, ad->authz_monitor_timeout, result, d->p); if (APR_SUCCESS != rv) goto leave; - if (ad->order->certificate) goto leave; - md_result_set(result, APR_EINVAL, "Order valid, but certifiate url is missing."); + if (!ad->order->certificate) { + md_result_set(result, APR_EINVAL, "Order valid, but certifiate url is missing."); + goto leave; + } + md_result_set(result, APR_SUCCESS, NULL); leave: md_result_log(result, MD_LOG_DEBUG); diff --git a/modules/md/md_core.c b/modules/md/md_core.c index 5d59b3d5931..9b696e2b995 100644 --- a/modules/md/md_core.c +++ b/modules/md/md_core.c @@ -33,7 +33,10 @@ int md_contains(const md_t *md, const char *domain, int case_sensitive) { - return md_array_str_index(md->domains, domain, 0, case_sensitive) >= 0; + if (md_array_str_index(md->domains, domain, 0, case_sensitive) >= 0) { + return 1; + } + return md_dns_domains_match(md->domains, domain); } const char *md_common_name(const md_t *md1, const md_t *md2) @@ -180,6 +183,15 @@ md_t *md_get_by_dns_overlap(struct apr_array_header_t *mds, const md_t *md) return NULL; } +int md_cert_count(const md_t *md) +{ + /* cert are defined as a list of static files or a list of private key specs */ + if (md->cert_files && md->cert_files->nelts) { + return md->cert_files->nelts; + } + return md_pkeys_spec_count(md->pks); +} + md_t *md_create(apr_pool_t *p, apr_array_header_t *domains) { md_t *md; @@ -207,6 +219,7 @@ md_t *md_copy(apr_pool_t *p, const md_t *src) md->ca_challenges = apr_array_copy(p, src->ca_challenges); } md->acme_tls_1_domains = apr_array_copy(p, src->acme_tls_1_domains); + md->pks = md_pkeys_spec_clone(p, src->pks); } return md; } @@ -223,7 +236,7 @@ md_t *md_clone(apr_pool_t *p, const md_t *src) md->must_staple = src->must_staple; md->renew_mode = src->renew_mode; md->domains = md_array_str_compact(p, src->domains, 0); - md->pkey_spec = src->pkey_spec; + md->pks = md_pkeys_spec_clone(p, src->pks); md->renew_window = src->renew_window; md->warn_window = src->warn_window; md->contacts = md_array_str_clone(p, src->contacts); @@ -238,8 +251,8 @@ md_t *md_clone(apr_pool_t *p, const md_t *src) } md->acme_tls_1_domains = md_array_str_compact(p, src->acme_tls_1_domains, 0); md->stapling = src->stapling; - if (src->cert_file) md->cert_file = apr_pstrdup(p, src->cert_file); - if (src->pkey_file) md->pkey_file = apr_pstrdup(p, src->pkey_file); + if (src->cert_files) md->cert_files = md_array_str_clone(p, src->cert_files); + if (src->pkey_files) md->pkey_files = md_array_str_clone(p, src->pkey_files); } return md; } @@ -260,8 +273,8 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p) md_json_sets(md->ca_proto, json, MD_KEY_CA, MD_KEY_PROTO, NULL); md_json_sets(md->ca_url, json, MD_KEY_CA, MD_KEY_URL, NULL); md_json_sets(md->ca_agreement, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL); - if (md->pkey_spec) { - md_json_setj(md_pkey_spec_to_json(md->pkey_spec, p), json, MD_KEY_PKEY, NULL); + if (!md_pkeys_spec_is_empty(md->pks)) { + md_json_setj(md_pkeys_spec_to_json(md->pks, p), json, MD_KEY_PKEY, NULL); } md_json_setl(md->state, json, MD_KEY_STATE, NULL); md_json_setl(md->renew_mode, json, MD_KEY_RENEW_MODE, NULL); @@ -286,8 +299,8 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p) } md_json_setb(md->must_staple > 0, json, MD_KEY_MUST_STAPLE, NULL); md_json_setsa(md->acme_tls_1_domains, json, MD_KEY_PROTO, MD_KEY_ACME_TLS_1, NULL); - md_json_sets(md->cert_file, json, MD_KEY_CERT_FILE, NULL); - md_json_sets(md->pkey_file, json, MD_KEY_PKEY_FILE, NULL); + if (md->cert_files) md_json_setsa(md->cert_files, json, MD_KEY_CERT_FILES, NULL); + if (md->pkey_files) md_json_setsa(md->pkey_files, json, MD_KEY_PKEY_FILES, NULL); md_json_setb(md->stapling > 0, json, MD_KEY_STAPLING, NULL); return json; } @@ -306,8 +319,8 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p) md->ca_proto = md_json_dups(p, json, MD_KEY_CA, MD_KEY_PROTO, NULL); md->ca_url = md_json_dups(p, json, MD_KEY_CA, MD_KEY_URL, NULL); md->ca_agreement = md_json_dups(p, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL); - if (md_json_has_key(json, MD_KEY_PKEY, MD_KEY_TYPE, NULL)) { - md->pkey_spec = md_pkey_spec_from_json(md_json_getj(json, MD_KEY_PKEY, NULL), p); + if (md_json_has_key(json, MD_KEY_PKEY, NULL)) { + md->pks = md_pkeys_spec_from_json(md_json_getj(json, MD_KEY_PKEY, NULL), p); } md->state = (md_state_t)md_json_getl(json, MD_KEY_STATE, NULL); if (MD_S_EXPIRED_DEPRECATED == md->state) md->state = MD_S_COMPLETE; @@ -333,8 +346,12 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p) md->must_staple = (int)md_json_getb(json, MD_KEY_MUST_STAPLE, NULL); md_json_dupsa(md->acme_tls_1_domains, p, json, MD_KEY_PROTO, MD_KEY_ACME_TLS_1, NULL); - md->cert_file = md_json_dups(p, json, MD_KEY_CERT_FILE, NULL); - md->pkey_file = md_json_dups(p, json, MD_KEY_PKEY_FILE, NULL); + if (md_json_has_key(json, MD_KEY_CERT_FILES, NULL)) { + md->cert_files = apr_array_make(p, 3, sizeof(char*)); + md->pkey_files = apr_array_make(p, 3, sizeof(char*)); + md_json_dupsa(md->cert_files, p, json, MD_KEY_CERT_FILES, NULL); + md_json_dupsa(md->pkey_files, p, json, MD_KEY_PKEY_FILES, NULL); + } md->stapling = (int)md_json_getb(json, MD_KEY_STAPLING, NULL); return md; diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c index 55155975de4..5c4d9f047e1 100644 --- a/modules/md/md_crypt.c +++ b/modules/md/md_crypt.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include @@ -35,6 +37,7 @@ #include "md_json.h" #include "md_log.h" #include "md_http.h" +#include "md_time.h" #include "md_util.h" /* getpid for *NIX */ @@ -190,8 +193,10 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton) size = (int)ctx->pass_len; } memcpy(buf, ctx->pass_phrase, (size_t)size); + } else { + return 0; } - return ctx->pass_len; + return size; } /**************************************************************************************************/ @@ -258,10 +263,93 @@ apr_time_t md_asn1_generalized_time_get(void *ASN1_GENERALIZEDTIME) return md_asn1_time_get(ASN1_GENERALIZEDTIME); } +/**************************************************************************************************/ +/* OID/NID things */ + +static int get_nid(const char *num, const char *sname, const char *lname) +{ + /* Funny API, an OID for a feature might be configured or + * maybe not. In the second case, we need to add it. But adding + * when it already is there is an error... */ + int nid = OBJ_txt2nid(num); + if (NID_undef == nid) { + nid = OBJ_create(num, sname, lname); + } + return nid; +} + +#define MD_GET_NID(x) get_nid(MD_OID_##x##_NUM, MD_OID_##x##_SNAME, MD_OID_##x##_LNAME) /**************************************************************************************************/ /* private keys */ +md_pkeys_spec_t *md_pkeys_spec_make(apr_pool_t *p) +{ + md_pkeys_spec_t *pks; + + pks = apr_pcalloc(p, sizeof(*pks)); + pks->p = p; + pks->specs = apr_array_make(p, 2, sizeof(md_pkey_spec_t*)); + return pks; +} + +void md_pkeys_spec_add(md_pkeys_spec_t *pks, md_pkey_spec_t *spec) +{ + APR_ARRAY_PUSH(pks->specs, md_pkey_spec_t*) = spec; +} + +void md_pkeys_spec_add_default(md_pkeys_spec_t *pks) +{ + md_pkey_spec_t *spec; + + spec = apr_pcalloc(pks->p, sizeof(*spec)); + spec->type = MD_PKEY_TYPE_DEFAULT; + md_pkeys_spec_add(pks, spec); +} + +int md_pkeys_spec_contains_rsa(md_pkeys_spec_t *pks) +{ + md_pkey_spec_t *spec; + int i; + for (i = 0; i < pks->specs->nelts; ++i) { + spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*); + if (MD_PKEY_TYPE_RSA == spec->type) return 1; + } + return 0; +} + +void md_pkeys_spec_add_rsa(md_pkeys_spec_t *pks, unsigned int bits) +{ + md_pkey_spec_t *spec; + + spec = apr_pcalloc(pks->p, sizeof(*spec)); + spec->type = MD_PKEY_TYPE_RSA; + spec->params.rsa.bits = bits; + md_pkeys_spec_add(pks, spec); +} + +int md_pkeys_spec_contains_ec(md_pkeys_spec_t *pks, const char *curve) +{ + md_pkey_spec_t *spec; + int i; + for (i = 0; i < pks->specs->nelts; ++i) { + spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*); + if (MD_PKEY_TYPE_EC == spec->type + && !apr_strnatcasecmp(curve, spec->params.ec.curve)) return 1; + } + return 0; +} + +void md_pkeys_spec_add_ec(md_pkeys_spec_t *pks, const char *curve) +{ + md_pkey_spec_t *spec; + + spec = apr_pcalloc(pks->p, sizeof(*spec)); + spec->type = MD_PKEY_TYPE_EC; + spec->params.ec.curve = apr_pstrdup(pks->p, curve); + md_pkeys_spec_add(pks, spec); +} + md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p) { md_json_t *json = md_json_create(p); @@ -276,6 +364,12 @@ md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p) md_json_setl((long)spec->params.rsa.bits, json, MD_KEY_BITS, NULL); } break; + case MD_PKEY_TYPE_EC: + md_json_sets("EC", json, MD_KEY_TYPE, NULL); + if (spec->params.ec.curve) { + md_json_sets(spec->params.ec.curve, json, MD_KEY_CURVE, NULL); + } + break; default: md_json_sets("Unsupported", json, MD_KEY_TYPE, NULL); break; @@ -284,6 +378,27 @@ md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p) return json; } +static apr_status_t spec_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton) +{ + md_json_t *jspec; + + (void)baton; + jspec = md_pkey_spec_to_json((md_pkey_spec_t*)value, p); + return md_json_setj(jspec, json, NULL); +} + +md_json_t *md_pkeys_spec_to_json(const md_pkeys_spec_t *pks, apr_pool_t *p) +{ + md_json_t *j; + + if (pks->specs->nelts == 1) { + return md_pkey_spec_to_json(md_pkeys_spec_get(pks, 0), p); + } + j = md_json_create(p); + md_json_seta(pks->specs, spec_to_json, (void*)pks, j, "specs", NULL); + return md_json_getj(j, "specs", NULL); +} + md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p) { md_pkey_spec_t *spec = apr_pcalloc(p, sizeof(*spec)); @@ -305,29 +420,161 @@ md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p) spec->params.rsa.bits = MD_PKEY_RSA_BITS_DEF; } } + else if (!apr_strnatcasecmp("EC", s)) { + spec->type = MD_PKEY_TYPE_EC; + s = md_json_gets(json, MD_KEY_CURVE, NULL); + if (s) { + spec->params.ec.curve = apr_pstrdup(p, s); + } + else { + spec->params.ec.curve = NULL; + } + } } return spec; } -int md_pkey_spec_eq(md_pkey_spec_t *spec1, md_pkey_spec_t *spec2) +static apr_status_t spec_from_json(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton) +{ + (void)baton; + *pvalue = md_pkey_spec_from_json(json, p); + return APR_SUCCESS; +} + +md_pkeys_spec_t *md_pkeys_spec_from_json(struct md_json_t *json, apr_pool_t *p) +{ + md_pkeys_spec_t *pks; + md_pkey_spec_t *spec; + + pks = md_pkeys_spec_make(p); + if (md_json_is(MD_JSON_TYPE_ARRAY, json, NULL)) { + md_json_geta(pks->specs, spec_from_json, pks, json, NULL); + } + else { + spec = md_pkey_spec_from_json(json, p); + md_pkeys_spec_add(pks, spec); + } + return pks; +} + +static int pkey_spec_eq(md_pkey_spec_t *s1, md_pkey_spec_t *s2) { - if (spec1 == spec2) { + if (s1 == s2) { return 1; } - if (spec1 && spec2 && spec1->type == spec2->type) { - switch (spec1->type) { + if (s1 && s2 && s1->type == s2->type) { + switch (s1->type) { case MD_PKEY_TYPE_DEFAULT: return 1; case MD_PKEY_TYPE_RSA: - if (spec1->params.rsa.bits == spec2->params.rsa.bits) { + if (s1->params.rsa.bits == s2->params.rsa.bits) { return 1; } break; + case MD_PKEY_TYPE_EC: + if (s1->params.ec.curve == s2->params.ec.curve) { + return 1; + } + else if (!s1->params.ec.curve || !s2->params.ec.curve) { + return 0; + } + return !strcmp(s1->params.ec.curve, s2->params.ec.curve); + } + } + return 0; +} + +int md_pkeys_spec_eq(md_pkeys_spec_t *pks1, md_pkeys_spec_t *pks2) +{ + int i; + + if (pks1 == pks2) { + return 1; + } + if (pks1 && pks2 && pks1->specs->nelts == pks2->specs->nelts) { + for(i = 0; i < pks1->specs->nelts; ++i) { + if (!pkey_spec_eq(APR_ARRAY_IDX(pks1->specs, i, md_pkey_spec_t *), + APR_ARRAY_IDX(pks2->specs, i, md_pkey_spec_t *))) { + return 0; + } } + return 1; } return 0; } +static md_pkey_spec_t *pkey_spec_clone(apr_pool_t *p, md_pkey_spec_t *spec) +{ + md_pkey_spec_t *nspec; + + nspec = apr_pcalloc(p, sizeof(*nspec)); + nspec->type = spec->type; + switch (spec->type) { + case MD_PKEY_TYPE_DEFAULT: + break; + case MD_PKEY_TYPE_RSA: + nspec->params.rsa.bits = spec->params.rsa.bits; + break; + case MD_PKEY_TYPE_EC: + nspec->params.ec.curve = apr_pstrdup(p, spec->params.ec.curve); + break; + } + return nspec; +} + +const char *md_pkey_spec_name(const md_pkey_spec_t *spec) +{ + if (!spec) return "rsa"; + switch (spec->type) { + case MD_PKEY_TYPE_DEFAULT: + case MD_PKEY_TYPE_RSA: + return "rsa"; + case MD_PKEY_TYPE_EC: + return spec->params.ec.curve; + } + return "unknown"; +} + +int md_pkeys_spec_is_empty(const md_pkeys_spec_t *pks) +{ + return NULL == pks || 0 == pks->specs->nelts; +} + +md_pkeys_spec_t *md_pkeys_spec_clone(apr_pool_t *p, const md_pkeys_spec_t *pks) +{ + md_pkeys_spec_t *npks = NULL; + md_pkey_spec_t *spec; + int i; + + if (pks && pks->specs->nelts > 0) { + npks = apr_pcalloc(p, sizeof(*npks)); + npks->specs = apr_array_make(p, pks->specs->nelts, sizeof(md_pkey_spec_t*)); + for (i = 0; i < pks->specs->nelts; ++i) { + spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*); + APR_ARRAY_PUSH(npks->specs, md_pkey_spec_t*) = pkey_spec_clone(p, spec); + } + } + return npks; +} + +int md_pkeys_spec_count(const md_pkeys_spec_t *pks) +{ + return md_pkeys_spec_is_empty(pks)? 1 : pks->specs->nelts; +} + +static md_pkey_spec_t PkeySpecDef = { MD_PKEY_TYPE_DEFAULT, {{ 0 }} }; + +md_pkey_spec_t *md_pkeys_spec_get(const md_pkeys_spec_t *pks, int index) +{ + if (md_pkeys_spec_is_empty(pks)) { + return index == 1? &PkeySpecDef : NULL; + } + else if (pks && index >= 0 && index < pks->specs->nelts) { + return APR_ARRAY_IDX(pks->specs, index, md_pkey_spec_t*); + } + return NULL; +} + static md_pkey_t *make_pkey(apr_pool_t *p) { md_pkey_t *pkey = apr_pcalloc(p, sizeof(*pkey)); @@ -418,7 +665,11 @@ static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t * } ERR_clear_error(); +#if 1 + if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) { +#else if (!PEM_write_bio_PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) { +#endif BIO_free(bio); err = ERR_get_error(); md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "PEM_write key: %ld %s", @@ -451,6 +702,24 @@ apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p, return rv; } +/* Determine the message digest used for signing with the given private key. + */ +static const EVP_MD *pkey_get_MD(md_pkey_t *pkey) +{ + switch (EVP_PKEY_id(pkey->pkey)) { +#ifdef NID_ED25519 + case NID_ED25519: + return NULL; +#endif +#ifdef NID_ED448 + case NID_ED448: + return NULL; +#endif + default: + return EVP_sha256(); + } +} + static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits) { EVP_PKEY_CTX *ctx = NULL; @@ -476,6 +745,125 @@ static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits) return rv; } +static apr_status_t check_EC_curve(int nid, apr_pool_t *p) { + EC_builtin_curve *curves = NULL; + size_t nc, i; + int rv = APR_ENOENT; + + nc = EC_get_builtin_curves(NULL, 0); + if (NULL == (curves = OPENSSL_malloc(sizeof(*curves) * nc)) || + nc != EC_get_builtin_curves(curves, nc)) { + rv = APR_EGENERAL; + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, + "error looking up OpenSSL builtin EC curves"); + goto leave; + } + for (i = 0; i < nc; ++i) { + if (nid == curves[i].nid) { + rv = APR_SUCCESS; + break; + } + } +leave: + OPENSSL_free(curves); + return rv; +} + +static apr_status_t gen_ec(md_pkey_t **ppkey, apr_pool_t *p, const char *curve) +{ + EVP_PKEY_CTX *ctx = NULL; + apr_status_t rv; + int curve_nid = NID_undef; + + /* 1. Convert the cure into its registered identifier. Curves can be known under + * different names. + * 2. Determine, if the curve is supported by OpenSSL (or whatever is linked). + * 3. Generate the key, respecting the specific quirks some curves require. + */ + curve_nid = EC_curve_nist2nid(curve); + /* In case this fails, try some names from other standards, like SECG */ +#ifdef NID_secp384r1 + if (NID_undef == curve_nid && !apr_strnatcasecmp("secp384r1", curve)) { + curve_nid = NID_secp384r1; + } +#endif +#ifdef NID_X9_62_prime256v1 + if (NID_undef == curve_nid && !apr_strnatcasecmp("secp256r1", curve)) { + curve_nid = NID_X9_62_prime256v1; + } +#endif +#ifdef NID_X9_62_prime192v1 + if (NID_undef == curve_nid && !apr_strnatcasecmp("secp192r1", curve)) { + curve_nid = NID_X9_62_prime192v1; + } +#endif +#ifdef NID_X25519 + if (NID_undef == curve_nid && !apr_strnatcasecmp("X25519", curve)) { + curve_nid = NID_X25519; + } +#endif + if (NID_undef == curve_nid) { + /* OpenSSL object/curve names */ + curve_nid = OBJ_sn2nid(curve); + } + if (NID_undef == curve_nid) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "ec curve unknown: %s", curve); + rv = APR_ENOTIMPL; goto leave; + } + + *ppkey = make_pkey(p); + switch (curve_nid) { + +#ifdef NID_X25519 + case NID_X25519: + /* no parameters */ + if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL)) + || EVP_PKEY_keygen_init(ctx) <= 0 + || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, + "error generate EC key for group: %s", curve); + rv = APR_EGENERAL; goto leave; + } + rv = APR_SUCCESS; + break; +#endif + +#ifdef NID_X448 + case NID_X448: + /* no parameters */ + if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X448, NULL)) + || EVP_PKEY_keygen_init(ctx) <= 0 + || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, + "error generate EC key for group: %s", curve); + rv = APR_EGENERAL; goto leave; + } + rv = APR_SUCCESS; + break; +#endif + + default: + if (APR_SUCCESS != (rv = check_EC_curve(curve_nid, p))) goto leave; + if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) + || EVP_PKEY_paramgen_init(ctx) <= 0 + || EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_nid) <= 0 + || EVP_PKEY_CTX_set_ec_param_enc(ctx, OPENSSL_EC_NAMED_CURVE) <= 0 + || EVP_PKEY_keygen_init(ctx) <= 0 + || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, + "error generate EC key for group: %s", curve); + rv = APR_EGENERAL; goto leave; + } + rv = APR_SUCCESS; + break; + } + +leave: + if (APR_SUCCESS != rv) *ppkey = NULL; + EVP_PKEY_CTX_free(ctx); + return rv; +} + apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec) { md_pkey_type_t ptype = spec? spec->type : MD_PKEY_TYPE_DEFAULT; @@ -484,6 +872,8 @@ apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec) return gen_rsa(ppkey, p, MD_PKEY_RSA_BITS_DEF); case MD_PKEY_TYPE_RSA: return gen_rsa(ppkey, p, spec->params.rsa.bits); + case MD_PKEY_TYPE_EC: + return gen_ec(ppkey, p, spec->params.ec.curve); default: return APR_ENOTIMPL; } @@ -727,6 +1117,14 @@ apr_time_t md_cert_get_not_before(const md_cert_t *cert) return md_asn1_time_get(X509_get_notBefore(cert->x509)); } +md_timeperiod_t md_cert_get_valid(const md_cert_t *cert) +{ + md_timeperiod_t p; + p.start = md_cert_get_not_before(cert); + p.end = md_cert_get_not_after(cert); + return p; +} + int md_cert_covers_domain(md_cert_t *cert, const char *domain_name) { apr_array_header_t *alt_names; @@ -799,10 +1197,12 @@ apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t STACK_OF(GENERAL_NAME) *xalt_names; unsigned char *buf; int i; - + xalt_names = X509_get_ext_d2i(cert->x509, NID_subject_alt_name, NULL, NULL); if (xalt_names) { GENERAL_NAME *cval; + const unsigned char *ip; + int len; names = apr_array_make(p, sk_GENERAL_NAME_num(xalt_names), sizeof(char *)); for (i = 0; i < sk_GENERAL_NAME_num(xalt_names); ++i) { @@ -810,11 +1210,33 @@ apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t switch (cval->type) { case GEN_DNS: case GEN_URI: - case GEN_IPADD: ASN1_STRING_to_UTF8(&buf, cval->d.ia5); APR_ARRAY_PUSH(names, const char *) = apr_pstrdup(p, (char*)buf); OPENSSL_free(buf); break; + case GEN_IPADD: + len = ASN1_STRING_length(cval->d.iPAddress); +#if OPENSSL_VERSION_NUMBER < 0x10100000L + ip = ASN1_STRING_data(cval->d.iPAddress); +#else + ip = ASN1_STRING_get0_data(cval->d.iPAddress); +#endif + if (len == 4) /* IPv4 address */ + APR_ARRAY_PUSH(names, const char *) = apr_psprintf(p, "%u.%u.%u.%u", + ip[0], ip[1], ip[2], ip[3]); + else if (len == 16) /* IPv6 address */ + APR_ARRAY_PUSH(names, const char *) = apr_psprintf(p, "%02x%02x%02x%02x:" + "%02x%02x%02x%02x:" + "%02x%02x%02x%02x:" + "%02x%02x%02x%02x", + ip[0], ip[1], ip[2], ip[3], + ip[4], ip[5], ip[6], ip[7], + ip[8], ip[9], ip[10], ip[11], + ip[12], ip[13], ip[14], ip[15]); + else { + ; /* Unknown address type - Log? Assert? */ + } + break; default: break; } @@ -937,22 +1359,44 @@ static int md_cert_read_pem(BIO *bf, apr_pool_t *p, md_cert_t **pcert) { md_cert_t *cert; X509 *x509; - apr_status_t rv; + apr_status_t rv = APR_ENOENT; ERR_clear_error(); x509 = PEM_read_bio_X509(bf, NULL, NULL, NULL); - if (x509 == NULL) { - rv = APR_ENOENT; - goto out; - } + if (x509 == NULL) goto cleanup; cert = md_cert_make(p, x509); rv = APR_SUCCESS; - -out: +cleanup: *pcert = (APR_SUCCESS == rv)? cert : NULL; return rv; } +apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p, + const char *pem, apr_size_t pem_len) +{ + BIO *bf = NULL; + apr_status_t rv = APR_SUCCESS; + md_cert_t *cert; + int added = 0; + + if (NULL == (bf = BIO_new_mem_buf(pem, (int)pem_len))) { + rv = APR_ENOMEM; + goto cleanup; + } + while (APR_SUCCESS == (rv = md_cert_read_pem(bf, chain->pool, &cert))) { + APR_ARRAY_PUSH(chain, md_cert_t *) = cert; + added = 1; + } + if (APR_ENOENT == rv && added) { + rv = APR_SUCCESS; + } + +cleanup: + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "read chain with %d certs", chain->nelts); + if (bf) BIO_free(bf); + return rv; +} + apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p, const md_http_response_t *res) { @@ -964,6 +1408,7 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p, apr_status_t rv; ct = apr_table_get(res->headers, "Content-Type"); + ct = md_util_parse_ct(res->req->pool, ct); if (!res->body || !ct || strcmp("application/pkix-cert", ct)) { rv = APR_ENOENT; goto out; @@ -984,7 +1429,8 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p, else { cert = md_cert_make(p, x509); rv = APR_SUCCESS; - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed"); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, + "parsing cert from content-type=%s, content-length=%ld", ct, (long)data_len); } } } @@ -996,60 +1442,40 @@ out: apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain, apr_pool_t *p, const struct md_http_response_t *res) { - const char *ct; + const char *ct = NULL; apr_off_t blen; - apr_size_t data_len; + apr_size_t data_len = 0; char *data; - BIO *bf = NULL; - apr_status_t rv; + md_cert_t *cert; + apr_status_t rv = APR_ENOENT; - if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto out; + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, + "chain_read, processing %d response", res->status); + if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto cleanup; if (blen > 1024*1024) { /* certs usually are <2k each */ rv = APR_EINVAL; - goto out; + goto cleanup; } data_len = (apr_size_t)blen; ct = apr_table_get(res->headers, "Content-Type"); - if (!res->body || !ct) { - rv = APR_ENOENT; - goto out; - } - else if (!strcmp("application/pem-certificate-chain", ct)) { - if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool))) { - int added = 0; - md_cert_t *cert; - - if (NULL == (bf = BIO_new_mem_buf(data, (int)data_len))) { - rv = APR_ENOMEM; - goto out; - } - - while (APR_SUCCESS == (rv = md_cert_read_pem(bf, p, &cert))) { - APR_ARRAY_PUSH(chain, md_cert_t *) = cert; - added = 1; - } - if (APR_ENOENT == rv && added) { - rv = APR_SUCCESS; - } - } - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed"); + if (!res->body || !ct) goto cleanup; + ct = md_util_parse_ct(res->req->pool, ct); + if (!strcmp("application/pem-certificate-chain", ct) + || !strncmp("text/plain", ct, sizeof("text/plain")-1)) { + /* Some servers seem to think 'text/plain' is sufficient, see #232 */ + rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool); + if (APR_SUCCESS != rv) goto cleanup; + rv = md_cert_read_chain(chain, res->req->pool, data, data_len); } else if (!strcmp("application/pkix-cert", ct)) { - md_cert_t *cert; - rv = md_cert_read_http(&cert, p, res); - if (APR_SUCCESS == rv) { - APR_ARRAY_PUSH(chain, md_cert_t *) = cert; - } + if (APR_SUCCESS != rv) goto cleanup; + APR_ARRAY_PUSH(chain, md_cert_t *) = cert; } - else { - /* unrecongized content type */ - rv = APR_ENOENT; - goto out; - } -out: - if (bf) BIO_free(bf); +cleanup: + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, + "parsed certs from content-type=%s, content-length=%ld", ct, (long)data_len); return rv; } @@ -1211,23 +1637,10 @@ static apr_status_t sk_add_alt_names(STACK_OF(X509_EXTENSION) *exts, #define MD_OID_MUST_STAPLE_SNAME "tlsfeature" #define MD_OID_MUST_STAPLE_LNAME "TLS Feature" -static int get_must_staple_nid(void) -{ - /* Funny API, the OID for must staple might be configured or - * might be not. In the second case, we need to add it. But adding - * when it already is there is an error... */ - int nid = OBJ_txt2nid(MD_OID_MUST_STAPLE_NUM); - if (NID_undef == nid) { - nid = OBJ_create(MD_OID_MUST_STAPLE_NUM, - MD_OID_MUST_STAPLE_SNAME, MD_OID_MUST_STAPLE_LNAME); - } - return nid; -} - int md_cert_must_staple(const md_cert_t *cert) { /* In case we do not get the NID for it, we treat this as not set. */ - int nid = get_must_staple_nid(); + int nid = MD_GET_NID(MUST_STAPLE); return ((NID_undef != nid)) && X509_get_ext_by_NID(cert->x509, nid, -1) >= 0; } @@ -1236,7 +1649,7 @@ static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const char * X509_EXTENSION *x; int nid; - nid = get_must_staple_nid(); + nid = MD_GET_NID(MUST_STAPLE); if (NID_undef == nid) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: unable to get NID for v3 must-staple TLS feature", name); @@ -1277,8 +1690,18 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name, /* subject name == first domain */ domain = APR_ARRAY_IDX(domains, 0, const unsigned char *); - if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0) - || !X509_REQ_set_subject_name(csr, n)) { + /* Do not set the domain in the CN if it is longer than 64 octets. + * Instead, let the CA choose a 'proper' name. At the moment (2021-01), LE will + * inspect all SAN names and use one < 64 chars if it can be found. It will fail + * otherwise. + * The reason we do not check this beforehand is that the restrictions on CNs + * are in flux. They used to be authoritative, now browsers no longer do that, but + * no one wants to hand out a cert with "google.com" as CN either. So, we leave + * it for the CA to decide if and how it hands out a cert for this or fails. + * This solves issue where the name is too long, see #227 */ + if (strlen((const char*)domain) < 64 + && (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0) + || !X509_REQ_set_subject_name(csr, n))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: REQ name add entry", name); rv = APR_EGENERAL; goto out; } @@ -1306,7 +1729,7 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name, rv = APR_EGENERAL; goto out; } /* sign, der encode and base64url encode */ - if (!X509_REQ_sign(csr, pkey->pkey, EVP_sha256())) { + if (!X509_REQ_sign(csr, pkey->pkey, pkey_get_MD(pkey))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign csr", name); rv = APR_EGENERAL; goto out; } @@ -1377,7 +1800,7 @@ static apr_status_t mk_x509(X509 **px, md_pkey_t *pkey, const char *cn, rv = APR_EGENERAL; goto out; } /* cert are unconstrained (but not very trustworthy) */ - if (APR_SUCCESS != (rv = add_ext(x, NID_basic_constraints, "CA:FALSE, pathlen:0", p))) { + if (APR_SUCCESS != (rv = add_ext(x, NID_basic_constraints, "critical,CA:FALSE", p))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set basic constraints ext", cn); goto out; } @@ -1422,8 +1845,19 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, goto out; } + /* keyUsage, ExtendedKeyUsage */ + + if (APR_SUCCESS != (rv = add_ext(x, NID_key_usage, "critical,digitalSignature", p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set keyUsage", cn); + goto out; + } + if (APR_SUCCESS != (rv = add_ext(x, NID_ext_key_usage, "serverAuth", p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set extKeyUsage", cn); + goto out; + } + /* sign with same key */ - if (!X509_sign(x, pkey->pkey, EVP_sha256())) { + if (!X509_sign(x, pkey->pkey, pkey_get_MD(pkey))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", cn); rv = APR_EGENERAL; goto out; } @@ -1460,7 +1894,7 @@ apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain, const char *alts; apr_status_t rv; - if (APR_SUCCESS != (rv = mk_x509(&x, pkey, domain, valid_for, p))) goto out; + if (APR_SUCCESS != (rv = mk_x509(&x, pkey, "tls-alpn-01-challenge", valid_for, p))) goto out; /* add the domain as alt name */ alts = apr_psprintf(p, "DNS:%s", domain); @@ -1475,7 +1909,7 @@ apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain, } /* sign with same key */ - if (!X509_sign(x, pkey->pkey, EVP_sha256())) { + if (!X509_sign(x, pkey->pkey, pkey_get_MD(pkey))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", domain); rv = APR_EGENERAL; goto out; } diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h index 2fba40912b9..cd1db294419 100644 --- a/modules/md/md_crypt.h +++ b/modules/md/md_crypt.h @@ -25,7 +25,7 @@ struct md_http_response_t; struct md_cert_t; struct md_pkey_t; struct md_data_t; - +struct md_timeperiod_t; /**************************************************************************************************/ /* random */ @@ -51,22 +51,54 @@ typedef struct md_pkey_t md_pkey_t; typedef enum { MD_PKEY_TYPE_DEFAULT, MD_PKEY_TYPE_RSA, + MD_PKEY_TYPE_EC, } md_pkey_type_t; -typedef struct md_pkey_rsa_spec_t { +typedef struct md_pkey_rsa_params_t { apr_uint32_t bits; -} md_pkey_rsa_spec_t; +} md_pkey_rsa_params_t; + +typedef struct md_pkey_ec_params_t { + const char *curve; +} md_pkey_ec_params_t; typedef struct md_pkey_spec_t { md_pkey_type_t type; union { - md_pkey_rsa_spec_t rsa; + md_pkey_rsa_params_t rsa; + md_pkey_ec_params_t ec; } params; } md_pkey_spec_t; +typedef struct md_pkeys_spec_t { + apr_pool_t *p; + struct apr_array_header_t *specs; +} md_pkeys_spec_t; + apr_status_t md_crypt_init(apr_pool_t *pool); -apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec); +const char *md_pkey_spec_name(const md_pkey_spec_t *spec); + +md_pkeys_spec_t *md_pkeys_spec_make(apr_pool_t *p); +void md_pkeys_spec_add_default(md_pkeys_spec_t *pks); +int md_pkeys_spec_contains_rsa(md_pkeys_spec_t *pks); +void md_pkeys_spec_add_rsa(md_pkeys_spec_t *pks, unsigned int bits); +int md_pkeys_spec_contains_ec(md_pkeys_spec_t *pks, const char *curve); +void md_pkeys_spec_add_ec(md_pkeys_spec_t *pks, const char *curve); +int md_pkeys_spec_eq(md_pkeys_spec_t *pks1, md_pkeys_spec_t *pks2); +md_pkeys_spec_t *md_pkeys_spec_clone(apr_pool_t *p, const md_pkeys_spec_t *pks); +int md_pkeys_spec_is_empty(const md_pkeys_spec_t *pks); +md_pkey_spec_t *md_pkeys_spec_get(const md_pkeys_spec_t *pks, int index); +int md_pkeys_spec_count(const md_pkeys_spec_t *pks); +void md_pkeys_spec_add(md_pkeys_spec_t *pks, md_pkey_spec_t *spec); + +struct md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p); +md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p); +struct md_json_t *md_pkeys_spec_to_json(const md_pkeys_spec_t *pks, apr_pool_t *p); +md_pkeys_spec_t *md_pkeys_spec_from_json(struct md_json_t *json, apr_pool_t *p); + + +apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *key_props); void md_pkey_free(md_pkey_t *pkey); const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p); @@ -84,10 +116,6 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t * void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey); -struct md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p); -md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p); -int md_pkey_spec_eq(md_pkey_spec_t *spec1, md_pkey_spec_t *spec2); - /**************************************************************************************************/ /* X509 certificates */ @@ -125,6 +153,12 @@ apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p, apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *pool, const struct md_http_response_t *res); +/** + * Read at least one certificate from the given PEM data. + */ +apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p, + const char *pem, apr_size_t pem_len); + /** * Read one or even a chain of certificates from a http response. * Will return APR_ENOENT if content-type is not recognized (currently @@ -142,6 +176,7 @@ int md_cert_covers_md(md_cert_t *cert, const struct md_t *md); int md_cert_must_staple(const md_cert_t *cert); apr_time_t md_cert_get_not_after(const md_cert_t *cert); apr_time_t md_cert_get_not_before(const md_cert_t *cert); +struct md_timeperiod_t md_cert_get_valid(const md_cert_t *cert); apr_status_t md_cert_get_issuers_uri(const char **puri, const md_cert_t *cert, apr_pool_t *p); apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t *cert, apr_pool_t *p); @@ -183,7 +218,6 @@ apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain, apr_status_t md_cert_get_ct_scts(apr_array_header_t *scts, apr_pool_t *p, const md_cert_t *cert); - /**************************************************************************************************/ /* X509 certificate transparency */ diff --git a/modules/md/md_curl.c b/modules/md/md_curl.c index ed40e7604e8..e93bea4d429 100644 --- a/modules/md/md_curl.c +++ b/modules/md/md_curl.c @@ -244,22 +244,29 @@ static apr_status_t internals_setup(md_http_request_t *req) md_curl_internals_t *internals; CURL *curl; apr_status_t rv = APR_SUCCESS; - - curl = curl_easy_init(); + + curl = md_http_get_impl_data(req->http); if (!curl) { - rv = APR_EGENERAL; - goto leave; + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "creating curl instance"); + curl = curl_easy_init(); + if (!curl) { + rv = APR_EGENERAL; + goto leave; + } + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb); + curl_easy_setopt(curl, CURLOPT_READDATA, NULL); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); + } + else { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "reusing curl instance from http"); } + internals = apr_pcalloc(req->pool, sizeof(*internals)); internals->curl = curl; - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL); - curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb); - curl_easy_setopt(curl, CURLOPT_READDATA, NULL); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); - internals->response = apr_pcalloc(req->pool, sizeof(md_http_response_t)); internals->response->req = req; internals->response->status = 400; @@ -293,7 +300,10 @@ static apr_status_t internals_setup(md_http_request_t *req) curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, req->timeout.stall_bytes_per_sec); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec(req->timeout.stalled)); } - + if (req->ca_file) { + curl_easy_setopt(curl, CURLOPT_CAINFO, req->ca_file); + } + if (req->body_len >= 0) { /* set the Content-Length */ curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req->body_len); @@ -568,17 +578,44 @@ static void md_curl_req_cleanup(md_http_request_t *req) { md_curl_internals_t *internals = req->internals; if (internals) { - if (internals->curl) curl_easy_cleanup(internals->curl); + if (internals->curl) { + CURL *curl = md_http_get_impl_data(req->http); + if (curl == internals->curl) { + /* NOP: we have this curl at the md_http_t already */ + } + else if (!curl) { + /* no curl at the md_http_t yet, install this one */ + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "register curl instance at http"); + md_http_set_impl_data(req->http, internals->curl); + } + else { + /* There already is a curl at the md_http_t and it's not this one. */ + curl_easy_cleanup(internals->curl); + } + } if (internals->req_hdrs) curl_slist_free_all(internals->req_hdrs); req->internals = NULL; } } +static void md_curl_cleanup(md_http_t *http, apr_pool_t *pool) +{ + CURL *curl; + + curl = md_http_get_impl_data(http); + if (curl) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, pool, "cleanup curl instance"); + md_http_set_impl_data(http, NULL); + curl_easy_cleanup(curl); + } +} + static md_http_impl_t impl = { md_curl_init, md_curl_req_cleanup, md_curl_perform, md_curl_multi_perform, + md_curl_cleanup, }; md_http_impl_t * md_curl_get_impl(apr_pool_t *p) diff --git a/modules/md/md_event.c b/modules/md/md_event.c new file mode 100644 index 00000000000..c731d55e0ce --- /dev/null +++ b/modules/md/md_event.c @@ -0,0 +1,89 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include +#include + +#include "md.h" +#include "md_event.h" + + +typedef struct md_subscription { + struct md_subscription *next; + md_event_cb *cb; + void *baton; +} md_subscription; + +static struct { + apr_pool_t *p; + md_subscription *subs; +} EVNT; + +static apr_status_t cleanup_setup(void *dummy) +{ + (void)dummy; + memset(&EVNT, 0, sizeof(EVNT)); + return APR_SUCCESS; +} + +void md_event_init(apr_pool_t *p) +{ + memset(&EVNT, 0, sizeof(EVNT)); + EVNT.p = p; + apr_pool_cleanup_register(p, NULL, cleanup_setup, apr_pool_cleanup_null); +} + +void md_event_subscribe(md_event_cb *cb, void *baton) +{ + md_subscription *sub; + + sub = apr_pcalloc(EVNT.p, sizeof(*sub)); + sub->cb = cb; + sub->baton = baton; + sub->next = EVNT.subs; + EVNT.subs = sub; +} + +apr_status_t md_event_raise(const char *event, + const char *mdomain, + struct md_job_t *job, + struct md_result_t *result, + apr_pool_t *p) +{ + md_subscription *sub = EVNT.subs; + apr_status_t rv; + + while (sub) { + rv = sub->cb(event, mdomain, sub->baton, job, result, p); + if (APR_SUCCESS != rv) return rv; + sub = sub->next; + } + return APR_SUCCESS; +} + +void md_event_holler(const char *event, + const char *mdomain, + struct md_job_t *job, + struct md_result_t *result, + apr_pool_t *p) +{ + md_subscription *sub = EVNT.subs; + while (sub) { + sub->cb(event, mdomain, sub->baton, job, result, p); + sub = sub->next; + } +} diff --git a/modules/md/md_event.h b/modules/md/md_event.h new file mode 100644 index 00000000000..e66c3c23955 --- /dev/null +++ b/modules/md/md_event.h @@ -0,0 +1,46 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef md_event_h +#define md_event_h + +struct md_job_t; +struct md_result_t; + +typedef apr_status_t md_event_cb(const char *event, + const char *mdomain, + void *baton, + struct md_job_t *job, + struct md_result_t *result, + apr_pool_t *p); + +void md_event_init(apr_pool_t *p); + +void md_event_subscribe(md_event_cb *cb, void *baton); + +apr_status_t md_event_raise(const char *event, + const char *mdomain, + struct md_job_t *job, + struct md_result_t *result, + apr_pool_t *p); + +void md_event_holler(const char *event, + const char *mdomain, + struct md_job_t *job, + struct md_result_t *result, + apr_pool_t *p); + +#endif /* md_event_h */ diff --git a/modules/md/md_http.c b/modules/md/md_http.c index 9c2790cd66a..53e4f89d99f 100644 --- a/modules/md/md_http.c +++ b/modules/md/md_http.c @@ -30,9 +30,11 @@ struct md_http_t { int next_id; apr_off_t resp_limit; md_http_impl_t *impl; + void *impl_data; /* to be used by the implementation */ const char *user_agent; const char *proxy_url; md_http_timeouts_t timeout; + const char *ca_file; }; static md_http_impl_t *cur_impl; @@ -46,6 +48,15 @@ void md_http_use_implementation(md_http_impl_t *impl) } } +static apr_status_t http_cleanup(void *data) +{ + md_http_t *http = data; + if (http && http->impl && http->impl->cleanup) { + http->impl->cleanup(http, http->pool); + } + return APR_SUCCESS; +} + apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_agent, const char *proxy_url) { @@ -75,10 +86,21 @@ apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_a if (!http->bucket_alloc) { return APR_EGENERAL; } + apr_pool_cleanup_register(p, http, http_cleanup, apr_pool_cleanup_null); *phttp = http; return APR_SUCCESS; } +void md_http_set_impl_data(md_http_t *http, void *data) +{ + http->impl_data = data; +} + +void *md_http_get_impl_data(md_http_t *http) +{ + return http->impl_data; +} + void md_http_set_response_limit(md_http_t *http, apr_off_t resp_limit) { http->resp_limit = resp_limit; @@ -116,6 +138,11 @@ void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t req->timeout.stalled = timeout; } +void md_http_set_ca_file(md_http_t *http, const char *ca_file) +{ + http->ca_file = ca_file; +} + static apr_status_t req_set_body(md_http_request_t *req, const char *content_type, apr_bucket_brigade *body, apr_off_t body_len, int detect_len) @@ -183,6 +210,7 @@ static apr_status_t req_create(md_http_request_t **preq, md_http_t *http, req->user_agent = http->user_agent; req->proxy_url = http->proxy_url; req->timeout = http->timeout; + req->ca_file = http->ca_file; *preq = req; return rv; } diff --git a/modules/md/md_http.h b/modules/md/md_http.h index 78668b31d2a..e24de031139 100644 --- a/modules/md/md_http.h +++ b/modules/md/md_http.h @@ -64,6 +64,7 @@ struct md_http_request_t { const char *url; const char *user_agent; const char *proxy_url; + const char *ca_file; apr_table_t *headers; struct apr_bucket_brigade *body; apr_off_t body_len; @@ -109,6 +110,13 @@ void md_http_set_connect_timeout(md_http_request_t *req, apr_time_t timeout); void md_http_set_stalling_default(md_http_t *http, long bytes_per_sec, apr_time_t timeout); void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t timeout); +/** + * Set a CA file (in PERM format) to use for root certificates when + * verifying SSL connections. If not set (or set to NULL), the systems + * certificate store will be used. + */ +void md_http_set_ca_file(md_http_t *http, const char *ca_file); + /** * Perform the request. Then this function returns, the request and * all its memory has been freed and must no longer be used. @@ -222,6 +230,7 @@ apr_status_t md_http_multi_perform(md_http_t *http, md_http_next_req *nextreq, v /* interface to implementation */ typedef apr_status_t md_http_init_cb(void); +typedef void md_http_cleanup_cb(md_http_t *req, apr_pool_t *p); typedef void md_http_req_cleanup_cb(md_http_request_t *req); typedef apr_status_t md_http_perform_cb(md_http_request_t *req); typedef apr_status_t md_http_multi_perform_cb(md_http_t *http, apr_pool_t *p, @@ -233,10 +242,17 @@ struct md_http_impl_t { md_http_req_cleanup_cb *req_cleanup; md_http_perform_cb *perform; md_http_multi_perform_cb *multi_perform; + md_http_cleanup_cb *cleanup; }; void md_http_use_implementation(md_http_impl_t *impl); +/** + * get/set data the implementation wants to remember between requests + * in the same md_http_t instance. + */ +void md_http_set_impl_data(md_http_t *http, void *data); +void *md_http_get_impl_data(md_http_t *http); #endif /* md_http_h */ diff --git a/modules/md/md_json.c b/modules/md/md_json.c index 73120d78ed2..c81fb0f786e 100644 --- a/modules/md/md_json.c +++ b/modules/md/md_json.c @@ -832,6 +832,32 @@ int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...) return 1; } +int md_json_iterkey(md_json_iterkey_cb *cb, void *baton, md_json_t *json, ...) +{ + json_t *j; + va_list ap; + const char *key; + json_t *val; + md_json_t wrap; + + va_start(ap, json); + j = jselect(json, ap); + va_end(ap); + + if (!j || !json_is_object(j)) { + return 0; + } + + wrap.p = json->p; + json_object_foreach(j, key, val) { + wrap.j = val; + if (!cb(baton, key, &wrap)) { + return 0; + } + } + return 1; +} + /**************************************************************************************************/ /* array strings */ @@ -1101,11 +1127,14 @@ apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, apr_bucket_briga json_t *j; j = json_load_callback(load_cb, bb, 0, &error); - if (!j) { - return APR_EINVAL; + if (j) { + *pjson = json_create(pool, j); + } else { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, pool, + "failed to load JSON file: %s (line %d:%d)", + error.text, error.line, error.column); } - *pjson = json_create(pool, j); - return APR_SUCCESS; + return (j && *pjson) ? APR_SUCCESS : APR_EINVAL; } static size_t load_file_cb(void *data, size_t max_len, void *baton) @@ -1156,8 +1185,13 @@ apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *p, const char *fpath) apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, const md_http_response_t *res) { apr_status_t rv = APR_ENOENT; - const char *ctype = apr_table_get(res->headers, "content-type"); - if (ctype && res->body && (strstr(ctype, "/json") || strstr(ctype, "+json"))) { + const char *ctype = apr_table_get(res->headers, "content-type"), *p; + + *pjson = NULL; + ctype = md_util_parse_ct(res->req->pool, ctype); + p = ctype + strlen(ctype) +1; + if (ctype && res->body && (!strcmp(p - sizeof("/json"), "/json") || + !strcmp(p - sizeof("+json"), "+json"))) { rv = md_json_readb(pjson, pool, res->body); } return rv; diff --git a/modules/md/md_json.h b/modules/md/md_json.h index 95a828b3514..50b882804d1 100644 --- a/modules/md/md_json.h +++ b/modules/md/md_json.h @@ -113,6 +113,10 @@ apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb, typedef int md_json_itera_cb(void *baton, size_t index, md_json_t *json); int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...); +/* Called on each object key, aborts iteration when returning 0 */ +typedef int md_json_iterkey_cb(void *baton, const char* key, md_json_t *json); +int md_json_iterkey(md_json_iterkey_cb *cb, void *baton, md_json_t *json, ...); + /* Manipulating Object String values */ apr_status_t md_json_gets_dict(apr_table_t *dict, const md_json_t *json, ...); apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...); diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c index dc95393249e..d3f0e1577e5 100644 --- a/modules/md/md_ocsp.c +++ b/modules/md/md_ocsp.c @@ -41,6 +41,7 @@ #include "md.h" #include "md_crypt.h" +#include "md_event.h" #include "md_json.h" #include "md_log.h" #include "md_http.h" @@ -58,7 +59,8 @@ struct md_ocsp_reg_t { md_store_t *store; const char *user_agent; const char *proxy_url; - apr_hash_t *hash; + apr_hash_t *id_by_external_id; + apr_hash_t *ostat_by_id; apr_thread_mutex_t *mutex; md_timeslice_t renew_window; md_job_notify_cb *notify; @@ -91,6 +93,12 @@ struct md_ocsp_status_t { apr_time_t resp_last_check; }; +typedef struct md_ocsp_id_map_t md_ocsp_id_map_t; +struct md_ocsp_id_map_t { + md_data_t id; + md_data_t external_id; +}; + const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat) { switch (stat) { @@ -107,16 +115,17 @@ md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name) return MD_OCSP_CERT_ST_UNKNOWN; } -static apr_status_t init_cert_id(md_data_t *data, const md_cert_t *cert) +apr_status_t md_ocsp_init_id(md_data_t *id, apr_pool_t *p, const md_cert_t *cert) { + unsigned char iddata[SHA_DIGEST_LENGTH]; X509 *x = md_cert_get_X509(cert); unsigned int ulen = 0; - assert(data->len == SHA_DIGEST_LENGTH); - if (X509_digest(x, EVP_sha1(), (unsigned char*)data->data, &ulen) != 1) { + if (X509_digest(x, EVP_sha1(), iddata, &ulen) != 1) { return APR_EGENERAL; } - data->len = ulen; + id->len = ulen; + id->data = apr_pmemdup(p, iddata, id->len); return APR_SUCCESS; } @@ -172,7 +181,7 @@ static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat, s = OPENSSL_malloc(der->len); if (!s) { rv = APR_ENOMEM; - goto leave; + goto cleanup; } memcpy((char*)s, der->data, der->len); } @@ -193,7 +202,7 @@ static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat, ostat->next_run = md_timeperiod_slice_before_end( &ostat->resp_valid, &ostat->reg->renew_window).start; -leave: +cleanup: return rv; } @@ -212,12 +221,12 @@ static apr_status_t ostat_from_json(md_ocsp_cert_stat_t *pstat, s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_UNTIL, NULL); if (s && *s) valid.end = apr_date_parse_rfc(s); s = md_json_dups(p, json, MD_KEY_RESPONSE, NULL); - if (!s || !*s) goto leave; + if (!s || !*s) goto cleanup; md_util_base64url_decode(resp_der, s, p); *pstat = md_ocsp_cert_stat_value(md_json_gets(json, MD_KEY_STATUS, NULL)); *resp_valid = valid; rv = APR_SUCCESS; -leave: +cleanup: return rv; } @@ -246,14 +255,14 @@ static apr_status_t ocsp_status_refresh(md_ocsp_status_t *ostat, apr_pool_t *pte md_ocsp_cert_stat_t resp_stat; /* Check if the store holds a newer response than the one we have */ mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp); - if (mtime <= ostat->resp_mtime) goto leave; + if (mtime <= ostat->resp_mtime) goto cleanup; rv = md_store_load_json(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, &jprops, ptemp); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; rv = ostat_from_json(&resp_stat, &resp_der, &resp_valid, jprops, ptemp); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; rv = ostat_set(ostat, resp_stat, &resp_der, &resp_valid, mtime); - if (APR_SUCCESS != rv) goto leave; -leave: + if (APR_SUCCESS != rv) goto cleanup; +cleanup: return rv; } @@ -270,10 +279,10 @@ static apr_status_t ocsp_status_save(md_ocsp_cert_stat_t stat, const md_data_t * jprops = md_json_create(ptemp); ostat_to_json(jprops, stat, resp_der, resp_valid, ptemp); rv = md_store_save_json(store, ptemp, MD_SG_OCSP, ostat->md_name, ostat->file_name, jprops, 0); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp); if (mtime) ostat->resp_mtime = mtime; -leave: +cleanup: return rv; } @@ -282,7 +291,7 @@ static apr_status_t ocsp_reg_cleanup(void *data) md_ocsp_reg_t *reg = data; /* free all OpenSSL structures that we hold */ - apr_hash_do(ostat_cleanup, reg, reg->hash); + apr_hash_do(ostat_cleanup, reg, reg->ostat_by_id); return APR_SUCCESS; } @@ -296,53 +305,53 @@ apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, md_store_t *s reg = apr_palloc(p, sizeof(*reg)); if (!reg) { rv = APR_ENOMEM; - goto leave; + goto cleanup; } reg->p = p; reg->store = store; reg->user_agent = user_agent; reg->proxy_url = proxy_url; - reg->hash = apr_hash_make(p); + reg->id_by_external_id = apr_hash_make(p); + reg->ostat_by_id = apr_hash_make(p); reg->renew_window = *renew_window; rv = apr_thread_mutex_create(®->mutex, APR_THREAD_MUTEX_NESTED, p); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; apr_pool_cleanup_register(p, reg, ocsp_reg_cleanup, apr_pool_cleanup_null); -leave: +cleanup: *preg = (APR_SUCCESS == rv)? reg : NULL; return rv; } -apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issuer, const md_t *md) +apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const char *ext_id, apr_size_t ext_id_len, + md_cert_t *cert, md_cert_t *issuer, const md_t *md) { - char iddata[MD_OCSP_ID_LENGTH]; md_ocsp_status_t *ostat; STACK_OF(OPENSSL_STRING) *ssk = NULL; const char *name, *s; md_data_t id; - apr_status_t rv; + apr_status_t rv = APR_SUCCESS; /* Called during post_config. no mutex protection needed */ name = md? md->name : MD_OTHER; - id.data = iddata; id.len = sizeof(iddata); - - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p, + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p, "md[%s]: priming OCSP status", name); - rv = init_cert_id(&id, cert); - if (APR_SUCCESS != rv) goto leave; - - ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len); - if (ostat) goto leave; /* already seen it, cert is used in >1 server_rec */ - + + rv = md_ocsp_init_id(&id, reg->p, cert); + if (APR_SUCCESS != rv) goto cleanup; + + ostat = apr_hash_get(reg->ostat_by_id, id.data, (apr_ssize_t)id.len); + if (ostat) goto cleanup; /* already seen it, cert is used in >1 server_rec */ + ostat = apr_pcalloc(reg->p, sizeof(*ostat)); - md_data_assign_pcopy(&ostat->id, &id, reg->p); + ostat->id = id; ostat->reg = reg; ostat->md_name = name; md_data_to_hex(&ostat->hexid, 0, reg->p, &ostat->id); ostat->file_name = apr_psprintf(reg->p, "ocsp-%s.json", ostat->hexid); rv = md_cert_to_sha256_fingerprint(&ostat->hex_sha256, cert, reg->p); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, "md[%s]: getting ocsp responder from cert", name); @@ -352,7 +361,7 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p, "md[%s]: certificate with serial %s has not OCSP responder URL", name, md_cert_get_serial_number(cert, reg->p)); - goto leave; + goto cleanup; } s = sk_OPENSSL_STRING_value(ssk, 0); md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, @@ -366,7 +375,7 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p, "md[%s]: unable to create OCSP certid for certificate with serial %s", name, md_cert_get_serial_number(cert, reg->p)); - goto leave; + goto cleanup; } /* See, if we have something in store */ @@ -374,38 +383,47 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, reg->p, "md[%s]: adding ocsp info (responder=%s)", name, ostat->responder_url); - apr_hash_set(reg->hash, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat); + apr_hash_set(reg->ostat_by_id, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat); + if (ext_id) { + md_ocsp_id_map_t *id_map; + + id_map = apr_pcalloc(reg->p, sizeof(*id_map)); + id_map->id = id; + md_data_assign_pcopy(&id_map->external_id, ext_id, ext_id_len, reg->p); + /* check for collision/uniqness? */ + apr_hash_set(reg->id_by_external_id, id_map->external_id.data, + (apr_ssize_t)id_map->external_id.len, id_map); + } rv = APR_SUCCESS; -leave: +cleanup: return rv; } -apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen, - md_ocsp_reg_t *reg, const md_cert_t *cert, +apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata, md_ocsp_reg_t *reg, + const char *ext_id, apr_size_t ext_id_len, apr_pool_t *p, const md_t *md) { - char iddata[MD_OCSP_ID_LENGTH]; md_ocsp_status_t *ostat; const char *name; - apr_status_t rv; + apr_status_t rv = APR_SUCCESS; + md_ocsp_id_map_t *id_map; + const char *id; + apr_size_t id_len; int locked = 0; - md_data_t id; - + (void)p; (void)md; - id.data = iddata; id.len = sizeof(iddata); - *pder = NULL; - *pderlen = 0; name = md? md->name : MD_OTHER; md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, "md[%s]: OCSP, get_status", name); - rv = init_cert_id(&id, cert); - if (APR_SUCCESS != rv) goto leave; - - ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len); + + id_map = apr_hash_get(reg->id_by_external_id, ext_id, (apr_ssize_t)ext_id_len); + id = id_map? id_map->id.data : ext_id; + id_len = id_map? id_map->id.len : ext_id_len; + ostat = apr_hash_get(reg->ostat_by_id, id, (apr_ssize_t)id_len); if (!ostat) { rv = APR_ENOENT; - goto leave; + goto cleanup; } /* While the ostat instance itself always exists, the response data it holds @@ -419,7 +437,8 @@ apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen, if (ostat->resp_der.len <= 0) { md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, "md[%s]: OCSP, no response available", name); - goto leave; + cb(NULL, 0, userdata); + goto cleanup; } } /* We have a response */ @@ -440,18 +459,12 @@ apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen, ocsp_status_refresh(ostat, p); } } - - *pder = OPENSSL_malloc(ostat->resp_der.len); - if (*pder == NULL) { - rv = APR_ENOMEM; - goto leave; - } - memcpy(*pder, ostat->resp_der.data, ostat->resp_der.len); - *pderlen = (int)ostat->resp_der.len; - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, - "md[%s]: OCSP, returning %ld bytes of response", + + cb((const unsigned char*)ostat->resp_der.data, ostat->resp_der.len, userdata); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, + "md[%s]: OCSP, provided %ld bytes of response", name, (long)ostat->resp_der.len); -leave: +cleanup: if (locked) apr_thread_mutex_unlock(reg->mutex); return rv; } @@ -474,7 +487,6 @@ apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvali md_ocsp_reg_t *reg, const md_cert_t *cert, apr_pool_t *p, const md_t *md) { - char iddata[MD_OCSP_ID_LENGTH]; md_ocsp_status_t *ostat; const char *name; apr_status_t rv; @@ -484,23 +496,22 @@ apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvali (void)p; (void)md; - id.data = iddata; id.len = sizeof(iddata); name = md? md->name : MD_OTHER; memset(&valid, 0, sizeof(valid)); stat = MD_OCSP_CERT_ST_UNKNOWN; md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, "md[%s]: OCSP, get_status", name); - rv = init_cert_id(&id, cert); - if (APR_SUCCESS != rv) goto leave; + rv = md_ocsp_init_id(&id, p, cert); + if (APR_SUCCESS != rv) goto cleanup; - ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len); + ostat = apr_hash_get(reg->ostat_by_id, id.data, (apr_ssize_t)id.len); if (!ostat) { rv = APR_ENOENT; - goto leave; + goto cleanup; } ocsp_get_meta(&stat, &valid, reg, ostat, p); -leave: +cleanup: *pstat = stat; *pvalid = valid; return rv; @@ -508,7 +519,7 @@ leave: apr_size_t md_ocsp_count(md_ocsp_reg_t *reg) { - return apr_hash_count(reg->hash); + return apr_hash_count(reg->ostat_by_id); } static const char *certid_as_hex(const OCSP_CERTID *certid, apr_pool_t *p) @@ -617,14 +628,14 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) ostat->hexid); if (APR_SUCCESS != (rv = apr_brigade_pflatten(resp->body, (char**)&der.data, &der.len, req->pool))) { - goto leave; + goto cleanup; } if (NULL == (ocsp_resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char**)&der.data, (long)der.len))) { rv = APR_EINVAL; md_result_set(update->result, rv, "response body does not parse as OCSP response"); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } /* got a response! but what does it say? */ n = OCSP_response_status(ocsp_resp); @@ -632,14 +643,14 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) rv = APR_EINVAL; md_result_printf(update->result, rv, "OCSP response status is, unsuccessfully, %d", n); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } basic_resp = OCSP_response_get1_basic(ocsp_resp); if (!basic_resp) { rv = APR_EINVAL; md_result_set(update->result, rv, "OCSP response has no basicresponse"); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } /* The notion of nonce enabled freshness in OCSP responses, e.g. that the response * contains the signed nonce we sent to the responder, does not scale well. Responders @@ -655,7 +666,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) rv = APR_EINVAL; md_result_printf(update->result, rv, "OCSP nonce mismatch in response", n); md_result_log(update->result, MD_LOG_WARNING); - goto leave; + goto cleanup; case -1: md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool, @@ -681,19 +692,19 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) } md_result_printf(update->result, rv, "%s, status list [%s]", prefix, slist); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } if (V_OCSP_CERTSTATUS_UNKNOWN == bstatus) { rv = APR_ENOENT; md_result_set(update->result, rv, "OCSP basicresponse says cert is unknown"); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } if (!bnextup) { rv = APR_EINVAL; md_result_set(update->result, rv, "OCSP basicresponse reports not valid dates"); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } /* Coming here, we have a response for our certid and it is either GOOD @@ -703,7 +714,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) rv = APR_EGENERAL; md_result_set(update->result, rv, "error DER encoding OCSP response"); md_result_log(update->result, MD_LOG_WARNING); - goto leave; + goto cleanup; } nstat = (bstatus == V_OCSP_CERTSTATUS_GOOD)? MD_OCSP_CERT_ST_GOOD : MD_OCSP_CERT_ST_REVOKED; new_der.len = (apr_size_t)n; @@ -720,7 +731,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) if (APR_SUCCESS != rv) { md_result_set(update->result, rv, "error saving OCSP status"); md_result_log(update->result, MD_LOG_ERR); - goto leave; + goto cleanup; } md_result_printf(update->result, rv, "certificate status is %s, status valid %s", @@ -728,7 +739,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) md_timeperiod_print(req->pool, &ostat->resp_valid)); md_result_log(update->result, MD_LOG_DEBUG); -leave: +cleanup: if (new_der.data) OPENSSL_free((void*)new_der.data); if (basic_resp) OCSP_BASICRESP_free(basic_resp); if (ocsp_resp) OCSP_RESPONSE_free(ocsp_resp); @@ -745,18 +756,18 @@ static apr_status_t ostat_on_req_status(const md_http_request_t *req, apr_status md_job_end_run(update->job, update->result); if (APR_SUCCESS != status) { ++ostat->errors; - ostat->next_run = apr_time_now() + md_job_delay_on_errors(ostat->errors); + ostat->next_run = apr_time_now() + md_job_delay_on_errors(update->job, ostat->errors, NULL); md_result_printf(update->result, status, "OCSP status update failed (%d. time)", ostat->errors); md_result_log(update->result, MD_LOG_DEBUG); md_job_log_append(update->job, "ocsp-error", update->result->problem, update->result->detail); - md_job_holler(update->job, "ocsp-errored"); - goto leave; + md_event_holler("ocsp-errored", update->job->mdomain, update->job, update->result, update->p); + goto cleanup; } - md_job_notify(update->job, "ocsp-renewed", update->result); + md_event_holler("ocsp-renewed", update->job->mdomain, update->job, update->result, update->p); -leave: +cleanup: md_job_save(update->job, update->result, update->p); ostat_req_cleanup(ostat); return APR_SUCCESS; @@ -794,16 +805,16 @@ static apr_status_t next_todo(md_http_request_t **preq, void *baton, if (!ostat->ocsp_req) { ostat->ocsp_req = OCSP_REQUEST_new(); - if (!ostat->ocsp_req) goto leave; + if (!ostat->ocsp_req) goto cleanup; certid = OCSP_CERTID_dup(ostat->certid); - if (!certid) goto leave; - if (!OCSP_request_add0_id(ostat->ocsp_req, certid)) goto leave; + if (!certid) goto cleanup; + if (!OCSP_request_add0_id(ostat->ocsp_req, certid)) goto cleanup; OCSP_request_add1_nonce(ostat->ocsp_req, 0, -1); certid = NULL; } if (0 == ostat->req_der.len) { len = i2d_OCSP_REQUEST(ostat->ocsp_req, (unsigned char**)&ostat->req_der.data); - if (len < 0) goto leave; + if (len < 0) goto cleanup; ostat->req_der.len = (apr_size_t)len; } md_result_activity_printf(update->result, "status of certid %s, " @@ -812,13 +823,13 @@ static apr_status_t next_todo(md_http_request_t **preq, void *baton, apr_table_set(headers, "Expect", ""); rv = md_http_POSTd_create(&req, http, ostat->responder_url, headers, "application/ocsp-request", &ostat->req_der); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; md_http_set_on_status_cb(req, ostat_on_req_status, update); md_http_set_on_response_cb(req, ostat_on_resp, update); rv = APR_SUCCESS; } } -leave: +cleanup: *preq = (APR_SUCCESS == rv)? req : NULL; if (certid) OCSP_CERTID_free(certid); return rv; @@ -872,27 +883,27 @@ void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_tim /* Create a list of update tasks that are needed now or in the next minute */ ctx.time = apr_time_now() + apr_time_from_sec(60);; - apr_hash_do(select_updates, &ctx, reg->hash); + apr_hash_do(select_updates, &ctx, reg->ostat_by_id); md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "OCSP status updates due: %d", ctx.todos->nelts); - if (!ctx.todos->nelts) goto leave; + if (!ctx.todos->nelts) goto cleanup; rv = md_http_create(&http, ptemp, reg->user_agent, reg->proxy_url); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; rv = md_http_multi_perform(http, next_todo, &ctx); -leave: +cleanup: /* When do we need to run next? *pnext_run contains the planned schedule from * the watchdog. We can make that earlier if we need it. */ ctx.time = *pnext_run; - apr_hash_do(select_next_run, &ctx, reg->hash); + apr_hash_do(select_next_run, &ctx, reg->ostat_by_id); /* sanity check and return */ if (ctx.time < apr_time_now()) ctx.time = apr_time_now() + apr_time_from_sec(1); *pnext_run = ctx.time; - if (APR_SUCCESS != rv) { + if (APR_SUCCESS != rv && APR_ENOENT != rv) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "ocsp_renew done"); } return; @@ -939,7 +950,7 @@ void md_ocsp_get_summary(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p) memset(&ctx, 0, sizeof(ctx)); ctx.p = p; ctx.reg = reg; - apr_hash_do(add_to_summary, &ctx, reg->hash); + apr_hash_do(add_to_summary, &ctx, reg->ostat_by_id); json = md_json_create(p); md_json_setl(ctx.good+ctx.revoked+ctx.unknown, json, MD_KEY_TOTAL, NULL); @@ -1019,10 +1030,10 @@ void md_ocsp_get_status_all(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p memset(&ctx, 0, sizeof(ctx)); ctx.p = p; ctx.reg = reg; - ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->hash), sizeof(md_ocsp_status_t*)); + ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->ostat_by_id), sizeof(md_ocsp_status_t*)); json = md_json_create(p); - apr_hash_do(add_ostat, &ctx, reg->hash); + apr_hash_do(add_ostat, &ctx, reg->ostat_by_id); qsort(ctx.ostats->elts, (size_t)ctx.ostats->nelts, sizeof(md_json_t*), md_ostat_cmp); for (i = 0; i < ctx.ostats->nelts; ++i) { @@ -1032,17 +1043,7 @@ void md_ocsp_get_status_all(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p *pjson = json; } -void md_ocsp_set_notify_cb(md_ocsp_reg_t *ocsp, md_job_notify_cb *cb, void *baton) -{ - ocsp->notify = cb; - ocsp->notify_ctx = baton; -} - md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p) { - md_job_t *job; - - job = md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain); - md_job_set_notify_cb(job, ocsp->notify, ocsp->notify_ctx); - return job; + return md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain); } diff --git a/modules/md/md_ocsp.h b/modules/md/md_ocsp.h index 9f0c0fd0351..d6ee0f1d7dd 100644 --- a/modules/md/md_ocsp.h +++ b/modules/md/md_ocsp.h @@ -17,6 +17,7 @@ #ifndef md_ocsp_h #define md_ocsp_h +struct md_data_t; struct md_job_t; struct md_json_t; struct md_result_t; @@ -39,11 +40,15 @@ apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, const md_timeslice_t *renew_window, const char *user_agent, const char *proxy_url); -apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *x, - md_cert_t *issuer, const md_t *md); +apr_status_t md_ocsp_init_id(struct md_data_t *id, apr_pool_t *p, const md_cert_t *cert); -apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen, - md_ocsp_reg_t *reg, const md_cert_t *cert, +apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const char *ext_id, apr_size_t ext_id_len, + md_cert_t *x, md_cert_t *issuer, const md_t *md); + +typedef void md_ocsp_copy_der(const unsigned char *der, apr_size_t der_len, void *userdata); + +apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata, md_ocsp_reg_t *reg, + const char *ext_id, apr_size_t ext_id_len, apr_pool_t *p, const md_t *md); apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid, @@ -60,7 +65,6 @@ apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t void md_ocsp_get_summary(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p); void md_ocsp_get_status_all(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p); -void md_ocsp_set_notify_cb(md_ocsp_reg_t *reg, md_job_notify_cb *cb, void *baton); struct md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p); #endif /* md_ocsp_h */ diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c index 9c57c29046b..7d61f0c5d45 100644 --- a/modules/md/md_reg.c +++ b/modules/md/md_reg.c @@ -26,6 +26,7 @@ #include "md.h" #include "md_crypt.h" +#include "md_event.h" #include "md_log.h" #include "md_json.h" #include "md_result.h" @@ -45,6 +46,7 @@ struct md_reg_t { int can_http; int can_https; const char *proxy_url; + const char *ca_file; int domains_frozen; md_timeslice_t *renew_window; md_timeslice_t *warn_window; @@ -77,7 +79,7 @@ static apr_status_t load_props(md_reg_t *reg, apr_pool_t *p) } apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store, - const char *proxy_url) + const char *proxy_url, const char *ca_file) { md_reg_t *reg; apr_status_t rv; @@ -90,7 +92,8 @@ apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *st reg->can_http = 1; reg->can_https = 1; reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL; - + reg->ca_file = ca_file? apr_pstrdup(p, ca_file) : NULL; + md_timeslice_create(®->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF); md_timeslice_create(®->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF); @@ -194,41 +197,46 @@ static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, i static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md) { - md_state_t state = MD_S_UNKNOWN; + md_state_t state; const md_pubcert_t *pub; const md_cert_t *cert; - apr_status_t rv; + apr_status_t rv = APR_SUCCESS; + int i; if (md->renew_window == NULL) md->renew_window = reg->renew_window; if (md->warn_window == NULL) md->warn_window = reg->warn_window; - - if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, p))) { - cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*); - if (!md_is_covered_by_alt_names(md, pub->alt_names)) { - state = MD_S_INCOMPLETE; - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, - "md{%s}: incomplete, cert no longer covers all domains, " - "needs sign up for a new certificate", md->name); - goto out; + + for (i = 0; i < md_cert_count(md); ++i) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: check cert %d", md->name, i); + if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, i, p))) { + cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*); + if (!md_is_covered_by_alt_names(md, pub->alt_names)) { + state = MD_S_INCOMPLETE; + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, + "md{%s}: incomplete, certificate(%d) does not cover all domains.", + md->name, i); + goto out; + } + if (!md->must_staple != !md_cert_must_staple(cert)) { + state = MD_S_INCOMPLETE; + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, + "md{%s}: incomplete, OCSP Stapling is%s requested, but " + "certificate(%d) has it%s enabled.", + md->name, md->must_staple? "" : " not", i, + !md->must_staple? "" : " not"); + goto out; + } + state = MD_S_COMPLETE; + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: certificate(%d) is ok", + md->name, i); } - if (!md->must_staple != !md_cert_must_staple(cert)) { + else if (APR_STATUS_IS_ENOENT(rv)) { state = MD_S_INCOMPLETE; + rv = APR_SUCCESS; md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, - "md{%s}: OCSP Stapling is%s requested, but certificate " - "has it%s enabled. Need to get a new certificate.", md->name, - md->must_staple? "" : " not", - !md->must_staple? "" : " not"); + "md{%s}: incomplete, certificate(%d) is missing", md->name, i); goto out; } - - state = MD_S_COMPLETE; - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: is complete", md->name); - } - else if (APR_STATUS_IS_ENOENT(rv)) { - state = MD_S_INCOMPLETE; - rv = APR_SUCCESS; - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, - "md{%s}: incomplete, credentials not all there", md->name); } out: @@ -236,6 +244,7 @@ out: state = MD_S_ERROR; md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name); } + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: state==%d", md->name, state); md->state = state; return rv; } @@ -465,10 +474,7 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v } if (MD_UPD_PKEY_SPEC & fields) { md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update pkey spec: %s", name); - nmd->pkey_spec = NULL; - if (updates->pkey_spec) { - nmd->pkey_spec = apr_pmemdup(p, updates->pkey_spec, sizeof(md_pkey_spec_t)); - } + nmd->pks = md_pkeys_spec_clone(p, updates->pks); } if (MD_UPD_REQUIRE_HTTPS & fields) { md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update require-https: %s", name); @@ -524,6 +530,7 @@ static apr_status_t pubcert_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, apr_array_header_t *certs; md_pubcert_t *pubcert, **ppubcert; const md_t *md; + int index; const md_cert_t *cert; md_cert_state_t cert_state; md_store_group_t group; @@ -532,12 +539,14 @@ static apr_status_t pubcert_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, ppubcert = va_arg(ap, md_pubcert_t **); group = (md_store_group_t)va_arg(ap, int); md = va_arg(ap, const md_t *); + index = va_arg(ap, int); - if (md->cert_file) { - rv = md_chain_fload(&certs, p, md->cert_file); + if (md->cert_files && md->cert_files->nelts) { + rv = md_chain_fload(&certs, p, APR_ARRAY_IDX(md->cert_files, index, const char *)); } else { - rv = md_pubcert_load(reg->store, group, md->name, &certs, p); + md_pkey_spec_t *spec = md_pkeys_spec_get(md->pks, index);; + rv = md_pubcert_load(reg->store, group, md->name, spec, &certs, p); } if (APR_SUCCESS != rv) goto leave; @@ -561,21 +570,22 @@ leave: } apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg, - const md_t *md, apr_pool_t *p) + const md_t *md, int i, apr_pool_t *p) { apr_status_t rv = APR_SUCCESS; const md_pubcert_t *pubcert; const char *name; - pubcert = apr_hash_get(reg->certs, md->name, (apr_ssize_t)strlen(md->name)); + name = apr_psprintf(p, "%s[%d]", md->name, i); + pubcert = apr_hash_get(reg->certs, name, (apr_ssize_t)strlen(name)); if (!pubcert && !reg->domains_frozen) { - rv = md_util_pool_vdo(pubcert_load, reg, reg->p, &pubcert, MD_SG_DOMAINS, md, NULL); + rv = md_util_pool_vdo(pubcert_load, reg, reg->p, &pubcert, MD_SG_DOMAINS, md, i, NULL); if (APR_STATUS_IS_ENOENT(rv)) { /* We cache it missing with an empty record */ pubcert = apr_pcalloc(reg->p, sizeof(*pubcert)); } else if (APR_SUCCESS != rv) goto leave; - name = (p != reg->p)? apr_pstrdup(reg->p, md->name) : md->name; + if (p != reg->p) name = apr_pstrdup(reg->p, name); apr_hash_set(reg->certs, name, (apr_ssize_t)strlen(name), pubcert); } leave: @@ -588,50 +598,73 @@ leave: apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile, md_reg_t *reg, md_store_group_t group, - const md_t *md, apr_pool_t *p) + const md_t *md, md_pkey_spec_t *spec, apr_pool_t *p) { apr_status_t rv; - if (md->cert_file) { - /* With fixed files configured, we use those without further checking them ourself */ - *pcertfile = md->cert_file; - *pkeyfile = md->pkey_file; - return APR_SUCCESS; - } - rv = md_store_get_fname(pkeyfile, reg->store, group, md->name, MD_FN_PRIVKEY, p); + rv = md_store_get_fname(pkeyfile, reg->store, group, md->name, md_pkey_filename(spec, p), p); if (APR_SUCCESS != rv) return rv; if (!md_file_exists(*pkeyfile, p)) return APR_ENOENT; - rv = md_store_get_fname(pcertfile, reg->store, group, md->name, MD_FN_PUBCERT, p); + rv = md_store_get_fname(pcertfile, reg->store, group, md->name, md_chain_filename(spec, p), p); if (APR_SUCCESS != rv) return rv; if (!md_file_exists(*pcertfile, p)) return APR_ENOENT; return APR_SUCCESS; } +apr_time_t md_reg_valid_until(md_reg_t *reg, const md_t *md, apr_pool_t *p) +{ + const md_pubcert_t *pub; + const md_cert_t *cert; + int i; + apr_time_t t, valid_until = 0; + apr_status_t rv; + + for (i = 0; i < md_cert_count(md); ++i) { + rv = md_reg_get_pubcert(&pub, reg, md, i, p); + if (APR_SUCCESS == rv) { + cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*); + t = md_cert_get_not_after(cert); + if (valid_until == 0 || t < valid_until) { + valid_until = t; + } + } + } + return valid_until; +} + apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p) { const md_pubcert_t *pub; const md_cert_t *cert; md_timeperiod_t certlife, renewal; + int i; + apr_time_t renew_at = 0; apr_status_t rv; if (md->state == MD_S_INCOMPLETE) return apr_time_now(); - rv = md_reg_get_pubcert(&pub, reg, md, p); - if (APR_STATUS_IS_ENOENT(rv)) return apr_time_now(); - if (APR_SUCCESS == rv) { - cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*); - certlife.start = md_cert_get_not_before(cert); - certlife.end = md_cert_get_not_after(cert); - - renewal = md_timeperiod_slice_before_end(&certlife, md->renew_window); - if (md_log_is_level(p, MD_LOG_TRACE1)) { - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, - "md[%s]: cert-life[%s] renewal[%s]", md->name, - md_timeperiod_print(p, &certlife), - md_timeperiod_print(p, &renewal)); + for (i = 0; i < md_cert_count(md); ++i) { + rv = md_reg_get_pubcert(&pub, reg, md, i, p); + if (APR_STATUS_IS_ENOENT(rv)) return apr_time_now(); + if (APR_SUCCESS == rv) { + cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*); + certlife.start = md_cert_get_not_before(cert); + certlife.end = md_cert_get_not_after(cert); + + renewal = md_timeperiod_slice_before_end(&certlife, md->renew_window); + if (md_log_is_level(p, MD_LOG_TRACE1)) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, + "md[%s]: certificate(%d) valid[%s] renewal[%s]", + md->name, i, + md_timeperiod_print(p, &certlife), + md_timeperiod_print(p, &renewal)); + } + + if (renew_at == 0 || renewal.start < renew_at) { + renew_at = renewal.start; + } } - return renewal.start; } - return 0; + return renew_at; } int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p) @@ -647,24 +680,30 @@ int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p) const md_pubcert_t *pub; const md_cert_t *cert; md_timeperiod_t certlife, warn; + int i; apr_status_t rv; if (md->state == MD_S_INCOMPLETE) return 0; - rv = md_reg_get_pubcert(&pub, reg, md, p); - if (APR_STATUS_IS_ENOENT(rv)) return 0; - if (APR_SUCCESS == rv) { - cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*); - certlife.start = md_cert_get_not_before(cert); - certlife.end = md_cert_get_not_after(cert); - - warn = md_timeperiod_slice_before_end(&certlife, md->warn_window); - if (md_log_is_level(p, MD_LOG_TRACE1)) { - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, - "md[%s]: cert-life[%s] warn[%s]", md->name, - md_timeperiod_print(p, &certlife), - md_timeperiod_print(p, &warn)); + for (i = 0; i < md_cert_count(md); ++i) { + rv = md_reg_get_pubcert(&pub, reg, md, i, p); + if (APR_STATUS_IS_ENOENT(rv)) return 0; + if (APR_SUCCESS == rv) { + cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*); + certlife.start = md_cert_get_not_before(cert); + certlife.end = md_cert_get_not_after(cert); + + warn = md_timeperiod_slice_before_end(&certlife, md->warn_window); + if (md_log_is_level(p, MD_LOG_TRACE1)) { + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, + "md[%s]: certificate(%d) life[%s] warn[%s]", + md->name, i, + md_timeperiod_print(p, &certlife), + md_timeperiod_print(p, &warn)); + } + if (md_timeperiod_has_started(&warn, apr_time_now())) { + return 1; + } } - return md_timeperiod_has_started(&warn, apr_time_now()); } return 0; } @@ -846,8 +885,6 @@ apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool apr_status_t rv; int changed = 1; - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "sync MDs, finish start"); - if (!md->ca_url) { md->ca_url = MD_ACME_DEF_URL; md->ca_proto = MD_PROTO_ACME; @@ -856,7 +893,9 @@ apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool rv = state_init(reg, ptemp, md); if (APR_SUCCESS != rv) goto leave; + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "loading md %s", md->name); if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, md->name, &old, ptemp)) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "loaded md %s", md->name); /* Some parts are kept from old, lacking new values */ if ((!md->contacts || apr_is_empty_array(md->contacts)) && old->contacts) { md->contacts = md_array_str_clone(p, old->contacts); @@ -880,17 +919,20 @@ apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool && !MD_VAL_UPDATE(md, old, renew_mode) && md_timeslice_eq(md->renew_window, old->renew_window) && md_timeslice_eq(md->warn_window, old->warn_window) - && md_pkey_spec_eq(md->pkey_spec, old->pkey_spec) + && md_pkeys_spec_eq(md->pks, old->pks) && !MD_VAL_UPDATE(md, old, require_https) && !MD_VAL_UPDATE(md, old, must_staple) && md_array_str_eq(md->acme_tls_1_domains, old->acme_tls_1_domains, 0) && !MD_VAL_UPDATE(md, old, stapling) && md_array_str_eq(md->contacts, old->contacts, 0) + && md_array_str_eq(md->cert_files, old->cert_files, 0) + && md_array_str_eq(md->pkey_files, old->pkey_files, 0) && md_array_str_eq(md->ca_challenges, old->ca_challenges, 0)) { changed = 0; } } if (changed) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "saving md %s", md->name); rv = md_save(reg->store, ptemp, MD_SG_DOMAINS, md, 0); } leave: @@ -984,13 +1026,14 @@ static apr_status_t run_init(void *baton, apr_pool_t *p, ...) driver->reg = reg; driver->store = md_reg_store_get(reg); driver->proxy_url = reg->proxy_url; + driver->ca_file = reg->ca_file; driver->md = md; driver->can_http = reg->can_http; driver->can_https = reg->can_https; s = apr_table_get(driver->env, MD_KEY_ACTIVATION_DELAY); if (!s || APR_SUCCESS != md_duration_parse(&driver->activation_delay, s, "d")) { - driver->activation_delay = apr_time_from_sec(MD_SECS_PER_DAY); + driver->activation_delay = 0; } if (!md->ca_proto) { @@ -1127,7 +1170,7 @@ static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *pte md_store_purge(reg->store, p, MD_SG_STAGING, md->name); md_store_purge(reg->store, p, MD_SG_CHALLENGES, md->name); md_result_set(result, APR_SUCCESS, "new certificate successfully saved in domains"); - md_job_notify(job, "installed", result); + md_event_holler("installed", md->name, job, result, ptemp); if (job->dirty) md_job_save(job, result, ptemp); out: @@ -1149,14 +1192,16 @@ apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds) apr_status_t rv = APR_SUCCESS; md_t *md; const md_pubcert_t *pubcert; - int i; + int i, j; assert(!reg->domains_frozen); /* prefill the certs cache for all mds */ for (i = 0; i < mds->nelts; ++i) { md = APR_ARRAY_IDX(mds, i, md_t*); - rv = md_reg_get_pubcert(&pubcert, reg, md, reg->p); - if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) goto leave; + for (j = 0; j < md_cert_count(md); ++j) { + rv = md_reg_get_pubcert(&pubcert, reg, md, i, reg->p); + if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) goto leave; + } } reg->domains_frozen = 1; leave: @@ -1173,17 +1218,7 @@ void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window) *reg->warn_window = *warn_window; } -void md_reg_set_notify_cb(md_reg_t *reg, md_job_notify_cb *cb, void *baton) -{ - reg->notify = cb; - reg->notify_ctx = baton; -} - md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p) { - md_job_t *job; - - job = md_job_make(p, reg->store, MD_SG_STAGING, mdomain); - md_job_set_notify_cb(job, reg->notify, reg->notify_ctx); - return job; + return md_job_make(p, reg->store, MD_SG_STAGING, mdomain); } diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h index b656d5c0a3d..46034b0fe68 100644 --- a/modules/md/md_reg.h +++ b/modules/md/md_reg.h @@ -22,6 +22,7 @@ struct apr_array_header_t; struct md_pkey_t; struct md_cert_t; struct md_result_t; +struct md_pkey_spec_t; #include "md_store.h" @@ -35,7 +36,7 @@ typedef struct md_reg_t md_reg_t; * Create the MD registry, using the pool and store. */ apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, md_store_t *store, - const char *proxy_url); + const char *proxy_url, const char *ca_file); md_store_t *md_reg_store_get(md_reg_t *reg); @@ -112,7 +113,7 @@ apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p, * of the domain and going up the issuers. Returns APR_ENOENT when not available. */ apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg, - const md_t *md, apr_pool_t *p); + const md_t *md, int i, apr_pool_t *p); /** * Get the filenames of private key and pubcert of the MD - if they exist. @@ -120,10 +121,10 @@ apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg, */ apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile, md_reg_t *reg, md_store_group_t group, - const md_t *md, apr_pool_t *p); + const md_t *md, struct md_pkey_spec_t *spec, apr_pool_t *p); /** - * Synchronise the give master mds with the store. + * Synchronize the given master mds with the store. */ apr_status_t md_reg_sync_start(md_reg_t *reg, apr_array_header_t *master_mds, apr_pool_t *p); @@ -172,6 +173,12 @@ int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p); */ apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p); +/** + * Return the timestamp up to which *all* certificates for the MD can be used. + * A value of 0 indicates that there is no certificate. + */ +apr_time_t md_reg_valid_until(md_reg_t *reg, const md_t *md, apr_pool_t *p); + /** * Return if a warning should be issued about the certificate expiration. * This applies the configured warn window to the remaining lifetime of the @@ -199,6 +206,7 @@ struct md_proto_driver_t { md_reg_t *reg; md_store_t *store; const char *proxy_url; + const char *ca_file; const md_t *md; int can_http; @@ -254,7 +262,6 @@ apr_status_t md_reg_load_staging(md_reg_t *reg, const md_t *md, struct apr_table void md_reg_set_renew_window_default(md_reg_t *reg, md_timeslice_t *renew_window); void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window); -void md_reg_set_notify_cb(md_reg_t *reg, md_job_notify_cb *cb, void *baton); struct md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p); #endif /* mod_md_md_reg_h */ diff --git a/modules/md/md_result.c b/modules/md/md_result.c index 29996e94ca6..64a2f70bac8 100644 --- a/modules/md/md_result.c +++ b/modules/md/md_result.c @@ -260,3 +260,26 @@ void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *dat result->on_change = cb; result->on_change_data = data; } + +apr_status_t md_result_raise(md_result_t *result, const char *event, apr_pool_t *p) +{ + if (result->on_raise) return result->on_raise(result, result->on_raise_data, event, p); + return APR_SUCCESS; +} + +void md_result_holler(md_result_t *result, const char *event, apr_pool_t *p) +{ + if (result->on_holler) result->on_holler(result, result->on_holler_data, event, p); +} + +void md_result_on_raise(md_result_t *result, md_result_raise_cb *cb, void *data) +{ + result->on_raise = cb; + result->on_raise_data = data; +} + +void md_result_on_holler(md_result_t *result, md_result_holler_cb *cb, void *data) +{ + result->on_holler = cb; + result->on_holler_data = data; +} diff --git a/modules/md/md_result.h b/modules/md/md_result.h index 58e903e08a0..e83bdd22236 100644 --- a/modules/md/md_result.h +++ b/modules/md/md_result.h @@ -23,6 +23,8 @@ struct md_t; typedef struct md_result_t md_result_t; typedef void md_result_change_cb(md_result_t *result, void *data); +typedef apr_status_t md_result_raise_cb(md_result_t *result, void *data, const char *event, apr_pool_t *p); +typedef void md_result_holler_cb(md_result_t *result, void *data, const char *event, apr_pool_t *p); struct md_result_t { apr_pool_t *p; @@ -35,6 +37,10 @@ struct md_result_t { apr_time_t ready_at; md_result_change_cb *on_change; void *on_change_data; + md_result_raise_cb *on_raise; + void *on_raise_data; + md_result_holler_cb *on_holler; + void *on_holler_data; }; md_result_t *md_result_make(apr_pool_t *p, apr_status_t status); @@ -70,4 +76,12 @@ void md_result_log(md_result_t *result, unsigned int level); void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data); +/* events in the context of a result genesis */ + +apr_status_t md_result_raise(md_result_t *result, const char *event, apr_pool_t *p); +void md_result_holler(md_result_t *result, const char *event, apr_pool_t *p); + +void md_result_on_raise(md_result_t *result, md_result_raise_cb *cb, void *data); +void md_result_on_holler(md_result_t *result, md_result_holler_cb *cb, void *data); + #endif /* mod_md_md_result_h */ diff --git a/modules/md/md_status.c b/modules/md/md_status.c index 3c3d801400b..ecc35c66d09 100644 --- a/modules/md/md_status.c +++ b/modules/md/md_status.c @@ -25,7 +25,9 @@ #include "md_json.h" #include "md.h" +#include "md_acme.h" #include "md_crypt.h" +#include "md_event.h" #include "md_log.h" #include "md_ocsp.h" #include "md_store.h" @@ -89,29 +91,6 @@ leave: /**************************************************************************************************/ /* md status information */ -static apr_status_t get_staging_cert_json(md_json_t **pjson, apr_pool_t *p, - md_reg_t *reg, const md_t *md) -{ - md_json_t *json = NULL; - apr_array_header_t *certs; - md_cert_t *cert; - apr_status_t rv = APR_SUCCESS; - - rv = md_pubcert_load(md_reg_store_get(reg), MD_SG_STAGING, md->name, &certs, p); - if (APR_STATUS_IS_ENOENT(rv)) { - rv = APR_SUCCESS; - goto leave; - } - else if (APR_SUCCESS != rv || certs->nelts == 0) { - goto leave; - } - cert = APR_ARRAY_IDX(certs, 0, md_cert_t *); - rv = status_get_cert_json(&json, cert, p); -leave: - *pjson = (APR_SUCCESS == rv)? json : NULL; - return rv; -} - static apr_status_t job_loadj(md_json_t **pjson, md_store_group_t group, const char *name, struct md_reg_t *reg, int with_log, apr_pool_t *p) { @@ -123,22 +102,25 @@ static apr_status_t job_loadj(md_json_t **pjson, md_store_group_t group, const c return rv; } -static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md, - md_reg_t *reg, md_ocsp_reg_t *ocsp, - int with_logs, apr_pool_t *p) +static apr_status_t status_get_certs_json(md_json_t **pjson, apr_array_header_t *certs, + const md_t *md, md_reg_t *reg, + md_ocsp_reg_t *ocsp, int with_logs, + apr_pool_t *p) { - md_json_t *mdj, *jobj, *certj; - int renew; - const md_pubcert_t *pubcert; - const md_cert_t *cert = NULL; + md_json_t *json, *certj, *jobj; + md_timeperiod_t certs_valid = {0, 0}, valid, ocsp_valid; + md_pkey_spec_t *spec; + md_cert_t *cert; md_ocsp_cert_stat_t cert_stat; - md_timeperiod_t ocsp_valid; - apr_status_t rv = APR_SUCCESS; - apr_time_t renew_at; - - mdj = md_to_json(md, p); - if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, p)) { - cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*); + int i; + apr_status_t rv = APR_SUCCESS; + + json = md_json_create(p); + for (i = 0; i < md_cert_count(md); ++i) { + spec = md_pkeys_spec_get(md->pks, i); + cert = APR_ARRAY_IDX(certs, i, md_cert_t*); + if (!cert) continue; + if (APR_SUCCESS != (rv = status_get_cert_json(&certj, cert, p))) goto leave; if (md->stapling && ocsp) { rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md); @@ -152,12 +134,71 @@ static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md, md_json_setj(jobj, certj, MD_KEY_OCSP, MD_KEY_RENEWAL, NULL); } } - md_json_setj(certj, mdj, MD_KEY_CERT, NULL); + valid = md_cert_get_valid(cert); + certs_valid = i? md_timeperiod_common(&certs_valid, &valid) : valid; + md_json_setj(certj, json, md_pkey_spec_name(spec), NULL); + } + + if (certs_valid.start) { + md_json_set_timeperiod(&certs_valid, json, MD_KEY_VALID, NULL); + } +leave: + *pjson = (APR_SUCCESS == rv)? json : NULL; + return rv; +} - renew_at = md_reg_renew_at(reg, md, p); - if (renew_at) { - md_json_set_time(renew_at, mdj, MD_KEY_RENEW_AT, NULL); +static apr_status_t get_staging_certs_json(md_json_t **pjson, const md_t *md, + md_reg_t *reg, apr_pool_t *p) +{ + md_pkey_spec_t *spec; + int i; + apr_array_header_t *chain, *certs; + const md_cert_t *cert; + apr_status_t rv; + + certs = apr_array_make(p, 5, sizeof(md_cert_t*)); + for (i = 0; i < md_cert_count(md); ++i) { + spec = md_pkeys_spec_get(md->pks, i); + cert = NULL; + rv = md_pubcert_load(md_reg_store_get(reg), MD_SG_STAGING, md->name, spec, &chain, p); + if (APR_SUCCESS == rv) { + cert = APR_ARRAY_IDX(chain, 0, const md_cert_t*); } + APR_ARRAY_PUSH(certs, const md_cert_t*) = cert; + } + return status_get_certs_json(pjson, certs, md, reg, NULL, 0, p); +} + +static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md, + md_reg_t *reg, md_ocsp_reg_t *ocsp, + int with_logs, apr_pool_t *p) +{ + md_json_t *mdj, *certsj, *jobj; + int renew; + const md_pubcert_t *pubcert; + const md_cert_t *cert = NULL; + apr_array_header_t *certs; + apr_status_t rv = APR_SUCCESS; + apr_time_t renew_at; + int i; + + mdj = md_to_json(md, p); + certs = apr_array_make(p, 5, sizeof(md_cert_t*)); + for (i = 0; i < md_cert_count(md); ++i) { + cert = NULL; + if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, i, p)) { + cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*); + } + APR_ARRAY_PUSH(certs, const md_cert_t*) = cert; + } + + rv = status_get_certs_json(&certsj, certs, md, reg, ocsp, with_logs, p); + if (APR_SUCCESS != rv) goto leave; + md_json_setj(certsj, mdj, MD_KEY_CERT, NULL); + + renew_at = md_reg_renew_at(reg, md, p); + if (renew_at > 0) { + md_json_set_time(renew_at, mdj, MD_KEY_RENEW_AT, NULL); } md_json_setb(md->stapling, mdj, MD_KEY_STAPLING, NULL); @@ -167,8 +208,8 @@ static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md, md_json_setb(renew, mdj, MD_KEY_RENEW, NULL); rv = job_loadj(&jobj, MD_SG_STAGING, md->name, reg, with_logs, p); if (APR_SUCCESS == rv) { - if (APR_SUCCESS == get_staging_cert_json(&certj, p, reg, md)) { - md_json_setj(certj, jobj, MD_KEY_CERT, NULL); + if (APR_SUCCESS == get_staging_certs_json(&certsj, md, reg, p)) { + md_json_setj(certsj, jobj, MD_KEY_CERT, NULL); } md_json_setj(jobj, mdj, MD_KEY_RENEWAL, NULL); } @@ -236,6 +277,7 @@ static void md_job_from_json(md_job_t *job, md_json_t *json, apr_pool_t *p) /*job->mdomain = md_json_gets(json, MD_KEY_NAME, NULL);*/ job->finished = md_json_getb(json, MD_KEY_FINISHED, NULL); job->notified = md_json_getb(json, MD_KEY_NOTIFIED, NULL); + job->notified_renewed = md_json_getb(json, MD_KEY_NOTIFIED_RENEWED, NULL); s = md_json_dups(p, json, MD_KEY_NEXT_RUN, NULL); if (s && *s) job->next_run = apr_date_parse_rfc(s); s = md_json_dups(p, json, MD_KEY_LAST_RUN, NULL); @@ -257,6 +299,7 @@ static void job_to_json(md_json_t *json, const md_job_t *job, md_json_sets(job->mdomain, json, MD_KEY_NAME, NULL); md_json_setb(job->finished, json, MD_KEY_FINISHED, NULL); md_json_setb(job->notified, json, MD_KEY_NOTIFIED, NULL); + md_json_setb(job->notified_renewed, json, MD_KEY_NOTIFIED_RENEWED, NULL); if (job->next_run > 0) { apr_rfc822_date(ts, job->next_run); md_json_sets(ts, json, MD_KEY_NEXT_RUN, NULL); @@ -447,6 +490,24 @@ static void job_result_update(md_result_t *result, void *data) } } +static apr_status_t job_result_raise(md_result_t *result, void *data, const char *event, apr_pool_t *p) +{ + md_job_result_ctx *ctx = data; + (void)p; + if (result == ctx->job->observing) { + return md_job_notify(ctx->job, event, result); + } + return APR_SUCCESS; +} + +static void job_result_holler(md_result_t *result, void *data, const char *event, apr_pool_t *p) +{ + md_job_result_ctx *ctx = data; + if (result == ctx->job->observing) { + md_event_holler(event, ctx->job->mdomain, ctx->job, result, p); + } +} + static void job_observation_start(md_job_t *job, md_result_t *result, md_store_t *store) { md_job_result_ctx *ctx; @@ -461,6 +522,8 @@ static void job_observation_start(md_job_t *job, md_result_t *result, md_store_t ctx->last = md_result_md_make(result->p, APR_SUCCESS); md_result_assign(ctx->last, result); md_result_on_change(result, job_result_update, ctx); + md_result_on_raise(result, job_result_raise, ctx); + md_result_on_holler(result, job_result_holler, ctx); } static void job_observation_end(md_job_t *job) @@ -477,17 +540,36 @@ void md_job_start_run(md_job_t *job, md_result_t *result, md_store_t *store) md_job_log_append(job, "starting", NULL, NULL); } -apr_time_t md_job_delay_on_errors(int err_count) +apr_time_t md_job_delay_on_errors(md_job_t *job, int err_count, const char *last_problem) { - apr_time_t delay = 0; - - if (err_count > 0) { + apr_time_t delay = 0, max_delay = apr_time_from_sec(24*60*60); /* daily */ + unsigned char c; + + if (last_problem && md_acme_problem_is_input_related(last_problem)) { + /* If ACME server reported a problem and that problem indicates that our + * input values, e.g. our configuration, has something wrong, we always + * go to max delay as frequent retries are unlikely to resolve the situation. + * However, we should nevertheless retry daily, bc. it might be that there + * is a bug in the server. Unlikely, but... */ + delay = max_delay; + } + else if (err_count > 0) { /* back off duration, depending on the errors we encounter in a row */ delay = apr_time_from_sec(5 << (err_count - 1)); - if (delay > apr_time_from_sec(60*60)) { - delay = apr_time_from_sec(60*60); + if (delay > max_delay) { + delay = max_delay; } } + if (delay > 0) { + /* jitter the delay by +/- 0-50%. + * Background: we see retries of jobs being too regular (e.g. all at midnight), + * possibly cumulating from many installations that restart their Apache at a + * fixed hour. This can contribute to an overload at the CA and a continuation + * of failure. + */ + md_rand_bytes(&c, sizeof(c), job->p); + delay += apr_time_from_sec((apr_time_sec(delay) * (c - 128)) / 256); + } return delay; } @@ -503,7 +585,7 @@ void md_job_end_run(md_job_t *job, md_result_t *result) else { ++job->error_runs; job->dirty = 1; - job->next_run = apr_time_now() + md_job_delay_on_errors(job->error_runs); + job->next_run = apr_time_now() + md_job_delay_on_errors(job, job->error_runs, result->problem); } job_observation_end(job); } @@ -516,31 +598,20 @@ void md_job_retry_at(md_job_t *job, apr_time_t later) apr_status_t md_job_notify(md_job_t *job, const char *reason, md_result_t *result) { - if (job->notify) return job->notify(job, reason, result, job->p, job->notify_ctx); + apr_status_t rv; + + md_result_set(result, APR_SUCCESS, NULL); + rv = md_event_raise(reason, job->mdomain, job, result, job->p); job->dirty = 1; - if (APR_SUCCESS == result->status) { + if (APR_SUCCESS == rv && APR_SUCCESS == result->status) { job->notified = 1; + if (!strcmp("renewed", reason)) job->notified_renewed = 1; job->error_runs = 0; } else { ++job->error_runs; - job->next_run = apr_time_now() + md_job_delay_on_errors(job->error_runs); + job->next_run = apr_time_now() + md_job_delay_on_errors(job, job->error_runs, result->problem); } return result->status; } -void md_job_holler(md_job_t *job, const char *reason) -{ - md_result_t *result; - - if (job->notify) { - result = md_result_make(job->p, APR_SUCCESS); - job->notify(job, reason, result, job->p, job->notify_ctx); - } -} - -void md_job_set_notify_cb(md_job_t *job, md_job_notify_cb *cb, void *baton) -{ - job->notify = cb; - job->notify_ctx = baton; -} diff --git a/modules/md/md_status.h b/modules/md/md_status.h index 3b25dbbff9c..cd358b0e8e1 100644 --- a/modules/md/md_status.h +++ b/modules/md/md_status.h @@ -59,6 +59,7 @@ struct md_job_t { struct md_result_t *last_result; /* Result from last run */ int finished; /* true iff the job finished successfully */ int notified; /* true iff notifications were handled successfully */ + int notified_renewed; /* true iff a 'renewed' notification was handled successfully */ apr_time_t valid_from; /* at which time the finished job results become valid, 0 if immediate */ int error_runs; /* Number of errored runs of an unfinished job */ int fatal_error; /* a fatal error is remedied by retrying */ @@ -67,9 +68,6 @@ struct md_job_t { apr_size_t max_log; /* max number of log entries, new ones replace oldest */ int dirty; struct md_result_t *observing; - - md_job_notify_cb *notify; - void *notify_ctx; }; /** @@ -115,12 +113,12 @@ void md_job_start_run(md_job_t *job, struct md_result_t *result, md_store_t *sto void md_job_end_run(md_job_t *job, struct md_result_t *result); void md_job_retry_at(md_job_t *job, apr_time_t later); -/* Given the number of errors encountered, recommend a delay for the next attempt */ -apr_time_t md_job_delay_on_errors(int err_count); +/** + * Given the number of errors and the last problem encountered, + * recommend a delay for the next attempt of job + */ +apr_time_t md_job_delay_on_errors(md_job_t *job, int err_count, const char *last_problem); -void md_job_set_notify_cb(md_job_t *job, md_job_notify_cb *cb, void *baton); apr_status_t md_job_notify(md_job_t *job, const char *reason, struct md_result_t *result); -/* Same as notify but without checks on success and no change to job */ -void md_job_holler(md_job_t *job, const char *reason); #endif /* md_status_h */ diff --git a/modules/md/md_store.c b/modules/md/md_store.c index ad5bb28cf2d..29f3632d922 100644 --- a/modules/md/md_store.c +++ b/modules/md/md_store.c @@ -254,29 +254,89 @@ typedef struct { apr_array_header_t *mds; } md_load_ctx; +static const char *pk_filename(const char *keyname, const char *base, apr_pool_t *p) +{ + char *s, *t; + /* We also run on various filesystems with difference upper/lower preserve matching + * rules. Normalize the names we use, since private key specifications are basically + * user input. */ + s = (keyname && apr_strnatcasecmp("rsa", keyname))? + apr_pstrcat(p, base, ".", keyname, ".pem", NULL) + : apr_pstrcat(p, base, ".pem", NULL); + for (t = s; *t; t++ ) + *t = (char)apr_tolower(*t); + return s; +} + +const char *md_pkey_filename(md_pkey_spec_t *spec, apr_pool_t *p) +{ + return pk_filename(md_pkey_spec_name(spec), "privkey", p); +} + +const char *md_chain_filename(md_pkey_spec_t *spec, apr_pool_t *p) +{ + return pk_filename(md_pkey_spec_name(spec), "pubcert", p); +} + apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group, const char *name, - md_pkey_t **ppkey, apr_pool_t *p) + md_pkey_spec_t *spec, md_pkey_t **ppkey, apr_pool_t *p) { - return md_store_load(store, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, (void**)ppkey, p); + const char *fname = md_pkey_filename(spec, p); + return md_store_load(store, group, name, fname, MD_SV_PKEY, (void**)ppkey, p); } apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name, - struct md_pkey_t *pkey, int create) + md_pkey_spec_t *spec, struct md_pkey_t *pkey, int create) { - return md_store_save(store, p, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, pkey, create); + const char *fname = md_pkey_filename(spec, p); + return md_store_save(store, p, group, name, fname, MD_SV_PKEY, pkey, create); } apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, - struct apr_array_header_t **ppubcert, apr_pool_t *p) + md_pkey_spec_t *spec, struct apr_array_header_t **ppubcert, + apr_pool_t *p) { - return md_store_load(store, group, name, MD_FN_PUBCERT, MD_SV_CHAIN, (void**)ppubcert, p); + const char *fname = md_chain_filename(spec, p); + return md_store_load(store, group, name, fname, MD_SV_CHAIN, (void**)ppubcert, p); } apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name, - struct apr_array_header_t *pubcert, int create) + md_pkey_spec_t *spec, struct apr_array_header_t *pubcert, int create) +{ + const char *fname = md_chain_filename(spec, p); + return md_store_save(store, p, group, name, fname, MD_SV_CHAIN, pubcert, create); +} + +apr_status_t md_creds_load(md_store_t *store, md_store_group_t group, const char *name, + md_pkey_spec_t *spec, md_credentials_t **pcreds, apr_pool_t *p) { - return md_store_save(store, p, group, name, MD_FN_PUBCERT, MD_SV_CHAIN, pubcert, create); + md_credentials_t *creds = apr_pcalloc(p, sizeof(*creds)); + apr_status_t rv; + + creds->spec = spec; + if (APR_SUCCESS != (rv = md_pkey_load(store, group, name, spec, &creds->pkey, p))) { + goto leave; + } + /* chain is optional */ + rv = md_pubcert_load(store, group, name, spec, &creds->chain, p); + if (APR_STATUS_IS_ENOENT(rv)) rv = APR_SUCCESS; +leave: + *pcreds = (APR_SUCCESS == rv)? creds : NULL; + return rv; +} + +apr_status_t md_creds_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, + const char *name, md_credentials_t *creds, int create) +{ + apr_status_t rv; + + if (APR_SUCCESS != (rv = md_pkey_save(store, p, group, name, creds->spec, creds->pkey, create))) { + goto leave; + } + rv = md_pubcert_save(store, p, group, name, creds->spec, creds->chain, create); +leave: + return rv; } typedef struct { diff --git a/modules/md/md_store.h b/modules/md/md_store.h index dfe9f32d6de..e252c279099 100644 --- a/modules/md/md_store.h +++ b/modules/md/md_store.h @@ -20,6 +20,7 @@ struct apr_array_header_t; struct md_cert_t; struct md_pkey_t; +struct md_pkey_spec_t; const char *md_store_group_name(unsigned int group); @@ -72,13 +73,19 @@ typedef enum { #define MD_FN_MD "md.json" #define MD_FN_JOB "job.json" +#define MD_FN_HTTPD_JSON "httpd.json" + +/* The corresponding names for current cert & key files are constructed + * in md_store and md_crypt. + */ + +/* These three legacy filenames are only used in md_store_fs to + * upgrade 1.0 directories. They should not be used for any other + * purpose. + */ #define MD_FN_PRIVKEY "privkey.pem" #define MD_FN_PUBCERT "pubcert.pem" #define MD_FN_CERT "cert.pem" -#define MD_FN_HTTPD_JSON "httpd.json" - -#define MD_FN_FALLBACK_PKEY "fallback-privkey.pem" -#define MD_FN_FALLBACK_CERT "fallback-cert.pem" /** * Load the JSON value at key "group/name/aspect", allocated from pool p. @@ -218,17 +225,39 @@ apr_status_t md_store_md_iter(md_store_md_inspect *inspect, void *baton, md_stor apr_pool_t *p, md_store_group_t group, const char *pattern); +const char *md_pkey_filename(struct md_pkey_spec_t *spec, apr_pool_t *p); +const char *md_chain_filename(struct md_pkey_spec_t *spec, apr_pool_t *p); + apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group, - const char *name, struct md_pkey_t **ppkey, apr_pool_t *p); + const char *name, struct md_pkey_spec_t *spec, + struct md_pkey_t **ppkey, apr_pool_t *p); apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, - const char *name, struct md_pkey_t *pkey, int create); + const char *name, struct md_pkey_spec_t *spec, + struct md_pkey_t *pkey, int create); apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, - struct apr_array_header_t **ppubcert, apr_pool_t *p); + struct md_pkey_spec_t *spec, struct apr_array_header_t **ppubcert, + apr_pool_t *p); apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name, + struct md_pkey_spec_t *spec, struct apr_array_header_t *pubcert, int create); +/**************************************************************************************************/ +/* X509 complete credentials */ + +typedef struct md_credentials_t md_credentials_t; +struct md_credentials_t { + struct md_pkey_spec_t *spec; + struct md_pkey_t *pkey; + struct apr_array_header_t *chain; +}; + +apr_status_t md_creds_load(md_store_t *store, md_store_group_t group, const char *name, + struct md_pkey_spec_t *spec, md_credentials_t **pcreds, apr_pool_t *p); +apr_status_t md_creds_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, + const char *name, md_credentials_t *creds, int create); + /**************************************************************************************************/ /* implementation interface */ diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c index 6553fdd706f..7ba822a4e25 100644 --- a/modules/md/md_store_fs.c +++ b/modules/md/md_store_fs.c @@ -256,6 +256,11 @@ static apr_status_t setup_store_file(void *baton, apr_pool_t *p, apr_pool_t *pte (void)ap; s_fs->plain_pkey[MD_SG_DOMAINS] = 1; + /* Added: the encryption of tls-alpn-01 certificate keys is not a security issue + * for these self-signed, short-lived certificates. Having them unencrypted let's + * use pass around the files insteak of an *SSL implementation dependent PKEY_something. + */ + s_fs->plain_pkey[MD_SG_CHALLENGES] = 1; s_fs->plain_pkey[MD_SG_TMP] = 1; if (!MD_OK(md_util_path_merge(&fname, ptemp, s_fs->base, FS_STORE_JSON, NULL))) { diff --git a/modules/md/md_time.c b/modules/md/md_time.c index 8076d5be468..268ca83c1b6 100644 --- a/modules/md/md_time.c +++ b/modules/md/md_time.c @@ -311,3 +311,15 @@ int md_timeslice_eq(const md_timeslice_t *ts1, const md_timeslice_t *ts2) if (!ts1 || !ts2) return 0; return (ts1->norm == ts2->norm) && (ts1->len == ts2->len); } + +md_timeperiod_t md_timeperiod_common(const md_timeperiod_t *a, const md_timeperiod_t *b) +{ + md_timeperiod_t c; + + c.start = (a->start > b->start)? a->start : b->start; + c.end = (a->end < b->end)? a->end : b->end; + if (c.start > c.end) { + c.start = c.end = 0; + } + return c; +} diff --git a/modules/md/md_time.h b/modules/md/md_time.h index 88e28b20cb8..92bd9d8aa97 100644 --- a/modules/md/md_time.h +++ b/modules/md/md_time.h @@ -36,6 +36,11 @@ int md_timeperiod_has_started(const md_timeperiod_t *period, apr_time_t time); int md_timeperiod_has_ended(const md_timeperiod_t *period, apr_time_t time); apr_interval_time_t md_timeperiod_remaining(const md_timeperiod_t *period, apr_time_t time); +/** + * Return the timeperiod common between a and b. If both do not overlap, return {0,0}. + */ +md_timeperiod_t md_timeperiod_common(const md_timeperiod_t *a, const md_timeperiod_t *b); + char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period); /** diff --git a/modules/md/md_util.c b/modules/md/md_util.c index 25830f2b92d..34e891d1ce6 100644 --- a/modules/md/md_util.c +++ b/modules/md/md_util.c @@ -25,6 +25,10 @@ #include #include +#if APR_HAVE_STDLIB_H +#include +#endif + #include "md.h" #include "md_log.h" #include "md_util.h" @@ -92,10 +96,10 @@ md_data_t *md_data_make(apr_pool_t *p, apr_size_t len) return d; } -void md_data_assign_pcopy(md_data_t *dest, const md_data_t *src, apr_pool_t *p) +void md_data_assign_pcopy(md_data_t *dest, const char *src, apr_size_t src_len, apr_pool_t *p) { - dest->data = (src->data && src->len)? apr_pmemdup(p, src->data, src->len) : NULL; - dest->len = dest->data? src->len : 0; + dest->data = (src && src_len)? apr_pmemdup(p, src, src_len) : NULL; + dest->len = dest->data? src_len : 0; } static const char * const hex_const[] = { @@ -153,7 +157,7 @@ int md_array_remove_at(struct apr_array_header_t *a, int idx) else { ps = (a->elts + (idx * a->elt_size)); pe = ps + a->elt_size; - memmove(ps, pe, (a->nelts - (idx+1)) * a->elt_size); + memmove(ps, pe, (size_t)((a->nelts - (idx+1)) * a->elt_size)); --a->nelts; } return 1; @@ -223,7 +227,7 @@ int md_array_str_eq(const struct apr_array_header_t *a1, const char *s1, *s2; if (a1 == a2) return 1; - if (!a1) return 0; + if (!a1 || !a2) return 0; if (a1->nelts != a2->nelts) return 0; for (i = 0; i < a1->nelts; ++i) { s1 = APR_ARRAY_IDX(a1, i, const char *); @@ -1010,23 +1014,31 @@ apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs, /* execute process ********************************************************************************/ apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv, - int *exit_code) + apr_array_header_t *env, int *exit_code) { apr_status_t rv; apr_procattr_t *procattr; apr_proc_t *proc; apr_exit_why_e ewhy; + const char * const *envp = NULL; char buffer[1024]; *exit_code = 0; if (!(proc = apr_pcalloc(p, sizeof(*proc)))) { return APR_ENOMEM; } + if (env && env->nelts > 0) { + apr_array_header_t *nenv; + + nenv = apr_array_copy(p, env); + APR_ARRAY_PUSH(nenv, const char *) = NULL; + envp = (const char * const *)nenv->elts; + } if ( APR_SUCCESS == (rv = apr_procattr_create(&procattr, p)) && APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE, APR_NO_PIPE, APR_FULL_BLOCK)) && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) - && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, NULL, procattr, p))) { + && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, envp, procattr, p))) { /* read stderr and log on INFO for possible fault analysis. */ while(APR_SUCCESS == (rv = apr_file_gets(buffer, sizeof(buffer)-1, proc->err))) { @@ -1472,3 +1484,23 @@ const char *md_link_find_relation(const apr_table_t *headers, return ctx.url; } +const char *md_util_parse_ct(apr_pool_t *pool, const char *cth) +{ + char *type; + const char *p; + apr_size_t hlen; + + if (!cth) return NULL; + + for( p = cth; *p && *p != ' ' && *p != ';'; ++p) + ; + hlen = (apr_size_t)(p - cth); + type = apr_pcalloc( pool, hlen + 1 ); + assert(type); + memcpy(type, cth, hlen); + type[hlen] = '\0'; + + return type; + /* Could parse and return parameters here, but we don't need any at present. + */ +} diff --git a/modules/md/md_util.h b/modules/md/md_util.h index 3d927564594..67d0e5128a6 100644 --- a/modules/md/md_util.h +++ b/modules/md/md_util.h @@ -46,7 +46,7 @@ struct md_data_t { md_data_t *md_data_make(apr_pool_t *p, apr_size_t len); md_data_t *md_data_create(apr_pool_t *p, const char *data, apr_size_t len); -void md_data_assign_pcopy(md_data_t *dest, const md_data_t *src, apr_pool_t *p); +void md_data_assign_pcopy(md_data_t *dest, const char *src, apr_size_t src_len, apr_pool_t *p); apr_status_t md_data_to_hex(const char **phex, char separator, apr_pool_t *p, const md_data_t *data); @@ -100,8 +100,9 @@ int md_array_str_add_missing(struct apr_array_header_t *dest, /**************************************************************************************************/ /* process execution */ + apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv, - int *exit_code); + struct apr_array_header_t *env, int *exit_code); /**************************************************************************************************/ /* dns name check */ @@ -207,6 +208,7 @@ apr_status_t md_util_abs_http_uri_check(apr_pool_t *p, const char *uri, const ch const char *md_link_find_relation(const struct apr_table_t *headers, apr_pool_t *pool, const char *relation); +const char *md_util_parse_ct(apr_pool_t *pool, const char *cth); /**************************************************************************************************/ /* retry logic */ diff --git a/modules/md/md_version.h b/modules/md/md_version.h index 21f5de1c737..909258c3f86 100644 --- a/modules/md/md_version.h +++ b/modules/md/md_version.h @@ -27,7 +27,7 @@ * @macro * Version number of the md module as c string */ -#define MOD_MD_VERSION "2.2.7" +#define MOD_MD_VERSION "2.4.2" /** * @macro @@ -35,7 +35,7 @@ * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ -#define MOD_MD_VERSION_NUM 0x020207 +#define MOD_MD_VERSION_NUM 0x020402 #define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory" diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c index 55413f5b67c..d8d44a47cbb 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #include #include #include @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include "md.h" #include "md_curl.h" #include "md_crypt.h" +#include "md_event.h" #include "md_http.h" #include "md_json.h" #include "md_store.h" @@ -86,12 +88,12 @@ static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level) #define LOG_BUF_LEN 16*1024 -static void log_print(const char *file, int line, md_log_level_t level, +static void log_print(const char *file, int line, md_log_level_t level, apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap) { if (log_is_level(baton, p, level)) { char buffer[LOG_BUF_LEN]; - + memset(buffer, 0, sizeof(buffer)); apr_vsnprintf(buffer, LOG_BUF_LEN-1, fmt, ap); buffer[LOG_BUF_LEN-1] = '\0'; @@ -108,11 +110,9 @@ static void log_print(const char *file, int line, md_log_level_t level, /**************************************************************************************************/ /* mod_ssl interface */ -static APR_OPTIONAL_FN_TYPE(ssl_is_https) *opt_ssl_is_https; - static void init_ssl(void) { - opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); + /* nop */ } /**************************************************************************************************/ @@ -125,7 +125,7 @@ static apr_status_t cleanup_setups(void *dummy) return APR_SUCCESS; } -static void init_setups(apr_pool_t *p, server_rec *base_server) +static void init_setups(apr_pool_t *p, server_rec *base_server) { log_server = base_server; apr_pool_cleanup_register(p, NULL, cleanup_setups, apr_pool_cleanup_null); @@ -140,7 +140,8 @@ typedef struct { } notify_rate; static notify_rate notify_rates[] = { - { "renewed", apr_time_from_sec(28 * MD_SECS_PER_DAY) }, /* once per month */ + { "renewing", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */ + { "renewed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */ { "installed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */ { "expiring", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */ { "errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */ @@ -148,7 +149,7 @@ static notify_rate notify_rates[] = { { "ocsp-errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */ }; -static apr_status_t notify(md_job_t *job, const char *reason, +static apr_status_t notify(md_job_t *job, const char *reason, md_result_t *result, apr_pool_t *p, void *baton) { md_mod_conf_t *mc = baton; @@ -160,7 +161,7 @@ static apr_status_t notify(md_job_t *job, const char *reason, md_timeperiod_t since_last; const char *log_msg_reason; int i; - + log_msg_reason = apr_psprintf(p, "message-%s", reason); for (i = 0; i < (int)(sizeof(notify_rates)/sizeof(notify_rates[0])); ++i) { if (!strcmp(reason, notify_rates[i].reason)) { @@ -170,69 +171,79 @@ static apr_status_t notify(md_job_t *job, const char *reason, if (min_interim > 0) { since_last.start = md_job_log_get_time_of_latest(job, log_msg_reason); since_last.end = apr_time_now(); - if (md_timeperiod_length(&since_last) < min_interim) { + if (since_last.start > 0 && md_timeperiod_length(&since_last) < min_interim) { /* not enough time has passed since we sent the last notification * for this reason. */ + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, APLOGNO(10267) + "%s: rate limiting notification about '%s'", job->mdomain, reason); return APR_SUCCESS; } } - + if (!strcmp("renewed", reason)) { if (mc->notify_cmd) { - cmdline = apr_psprintf(p, "%s %s", mc->notify_cmd, job->mdomain); + cmdline = apr_psprintf(p, "%s %s", mc->notify_cmd, job->mdomain); apr_tokenize_to_argv(cmdline, (char***)&argv, p); - rv = md_util_exec(p, argv[0], argv, &exit_code); - + rv = md_util_exec(p, argv[0], argv, NULL, &exit_code); + if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL; if (APR_SUCCESS != rv) { - md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)), - "MDNotifyCmd %s failed with exit code %d.", + md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)), + "MDNotifyCmd %s failed with exit code %d.", mc->notify_cmd, exit_code); md_result_log(result, MD_LOG_ERR); md_job_log_append(job, "notify-error", result->problem, result->detail); return rv; } } - md_log_perror(MD_LOG_MARK, MD_LOG_NOTICE, 0, p, APLOGNO(10059) + md_log_perror(MD_LOG_MARK, MD_LOG_NOTICE, 0, p, APLOGNO(10059) "The Managed Domain %s has been setup and changes " "will be activated on next (graceful) server restart.", job->mdomain); } if (mc->message_cmd) { - cmdline = apr_psprintf(p, "%s %s %s", mc->message_cmd, reason, job->mdomain); + cmdline = apr_psprintf(p, "%s %s %s", mc->message_cmd, reason, job->mdomain); apr_tokenize_to_argv(cmdline, (char***)&argv, p); - rv = md_util_exec(p, argv[0], argv, &exit_code); - + rv = md_util_exec(p, argv[0], argv, NULL, &exit_code); + if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL; if (APR_SUCCESS != rv) { - md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)), - "MDMessageCmd %s failed with exit code %d.", + md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)), + "MDMessageCmd %s failed with exit code %d.", mc->message_cmd, exit_code); md_result_log(result, MD_LOG_ERR); md_job_log_append(job, "message-error", reason, result->detail); return rv; } } + md_job_log_append(job, log_msg_reason, NULL, NULL); return APR_SUCCESS; } +static apr_status_t on_event(const char *event, const char *mdomain, void *baton, + md_job_t *job, md_result_t *result, apr_pool_t *p) +{ + (void)mdomain; + return notify(job, event, result, p, baton); +} + /**************************************************************************************************/ /* store setup */ static apr_status_t store_file_ev(void *baton, struct md_store_t *store, - md_store_fs_ev_t ev, unsigned int group, - const char *fname, apr_filetype_e ftype, + md_store_fs_ev_t ev, unsigned int group, + const char *fname, apr_filetype_e ftype, apr_pool_t *p) { server_rec *s = baton; apr_status_t rv; - + (void)store; - ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)", + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)", ev, (ftype == APR_DIR)? "dir" : "file", fname, group); - - /* Directories in group CHALLENGES, STAGING and OCSP are written to - * under a different user. Give her ownership. + + /* Directories in group CHALLENGES, STAGING and OCSP are written to + * under a different user. Give her ownership. */ if (ftype == APR_DIR) { switch (group) { @@ -244,19 +255,19 @@ static apr_status_t store_file_ev(void *baton, struct md_store_t *store, return rv; } break; - default: + default: break; } } return APR_SUCCESS; } -static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group, +static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group, apr_pool_t *p, server_rec *s) { const char *dir; apr_status_t rv; - + if (APR_SUCCESS == (rv = md_store_get_fname(&dir, store, group, NULL, NULL, p)) && APR_SUCCESS == (rv = apr_dir_make_recursive(dir, MD_FPROT_D_UALL_GREAD, p))) { rv = store_file_ev(s, store, MD_S_FS_EV_CREATED, group, dir, APR_DIR, p); @@ -264,14 +275,14 @@ static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group, return rv; } -static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc, +static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc, apr_pool_t *p, server_rec *s) { const char *base_dir; apr_status_t rv; - + base_dir = ap_server_root_relative(p, mc->base_dir); - + if (APR_SUCCESS != (rv = md_store_fs_init(pstore, p, base_dir))) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10046)"setup store for %s", base_dir); goto leave; @@ -283,11 +294,11 @@ static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc, || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_ACCOUNTS, p, s)) || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_OCSP, p, s)) ) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) "setup challenges directory"); goto leave; } - + leave: return rv; } @@ -332,10 +343,9 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) } if (!md->ca_challenges && md->sc->ca_challenges) { md->ca_challenges = apr_array_copy(p, md->sc->ca_challenges); - } - if (!md->pkey_spec) { - md->pkey_spec = md->sc->pkey_spec; - + } + if (md_pkeys_spec_is_empty(md->pks)) { + md->pks = md->sc->pks; } if (md->require_https < 0) { md->require_https = md_config_geti(md->sc, MD_CONFIG_REQUIRE_HTTPS); @@ -348,7 +358,7 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) } } -static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, +static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, int *pupdates, apr_pool_t *p) { if (md_contains(md, domain, 0)) { @@ -374,16 +384,16 @@ static apr_status_t md_cover_server(md_t *md, server_rec *s, int *pupdates, apr_ apr_status_t rv; const char *name; int i; - + if (APR_SUCCESS == (rv = check_coverage(md, s->server_hostname, s, pupdates, p))) { - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "md[%s]: auto add, covers name %s", md->name, s->server_hostname); for (i = 0; s->names && i < s->names->nelts; ++i) { name = APR_ARRAY_IDX(s->names, i, const char*); if (APR_SUCCESS != (rv = check_coverage(md, name, s, pupdates, p))) { break; } - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "md[%s]: auto add, covers alias %s", md->name, name); } } @@ -407,7 +417,7 @@ static int uses_port(server_rec *s, int port) return match; } -static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, +static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p, int log_level) { ap_listen_rec *lr; @@ -415,11 +425,11 @@ static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, int can_http, can_https; if (mc->can_http >= 0 && mc->can_https >= 0) goto set_and_leave; - + can_http = can_https = 0; for (lr = ap_listeners; lr; lr = lr->next) { for (sa = lr->bind_addr; sa; sa = sa->next) { - if (sa->port == mc->local_80 + if (sa->port == mc->local_80 && (!lr->protocol || !strncmp("http", lr->protocol, 4))) { can_http = 1; } @@ -429,13 +439,13 @@ static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, } } } - if (mc->can_http < 0) mc->can_http = can_http; + if (mc->can_http < 0) mc->can_http = can_http; if (mc->can_https < 0) mc->can_https = can_https; ap_log_error(APLOG_MARK, log_level, 0, s, APLOGNO(10037) "server seems%s reachable via http: and%s reachable via https:", mc->can_http? "" : " not", mc->can_https? "" : " not"); set_and_leave: - return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https); + return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https); } static server_rec *get_public_https_server(md_t *md, const char *domain, server_rec *base_server) @@ -493,9 +503,9 @@ static apr_status_t auto_add_domains(md_t *md, server_rec *base_server, apr_pool server_rec *s; apr_status_t rv = APR_SUCCESS; int updates; - + /* Ad all domain names used in SSL VirtualHosts, if not already there */ - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, base_server, + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, base_server, "md[%s]: auto add domains", md->name); updates = 0; for (s = base_server; s; s = s->next) { @@ -516,12 +526,12 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server) server_rec *s; int i; const char *domain; - + /* Collect those domains that support the "acme-tls/1" protocol. This * is part of the MD (and not tested dynamically), since challenge selection * may be done outside the server, e.g. in the a2md command. */ sc = md_config_get(base_server); - mc = sc->mc; + mc = sc->mc; apr_array_clear(md->acme_tls_1_domains); for (i = 0; i < md->domains->nelts; ++i) { domain = APR_ARRAY_IDX(md->domains, i, const char*); @@ -536,7 +546,7 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server) } if (!ap_is_allowed_protocol(NULL, NULL, s, PROTO_ACME_TLS_1)) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10169) - "%s: https server_rec for %s does not have protocol %s enabled", + "%s: https server_rec for %s does not have protocol %s enabled", md->name, domain, PROTO_ACME_TLS_1); continue; } @@ -544,7 +554,7 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server) } } -static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *base_server, +static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *base_server, apr_pool_t *p) { server_rec *s; @@ -552,7 +562,7 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec * md_srv_conf_t *sc; int i; const char *domain, *uri; - + sc = md_config_get(base_server); /* Assign the MD to all server_rec configs that it matches. If there already @@ -564,23 +574,24 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec * /* we shall not assign ourselves to the base server */ continue; } - + r.server = s; for (i = 0; i < md->domains->nelts; ++i) { domain = APR_ARRAY_IDX(md->domains, i, const char*); - - if (ap_matches_request_vhost(&r, domain, s->port)) { + + if (ap_matches_request_vhost(&r, domain, s->port) + || (md_dns_is_wildcard(p, domain) && md_dns_matches(domain, s->server_hostname))) { /* Create a unique md_srv_conf_t record for this server, if there is none yet */ sc = md_config_get_unique(s, p); if (!sc->assigned) sc->assigned = apr_array_make(p, 2, sizeof(md_t*)); - + APR_ARRAY_PUSH(sc->assigned, md_t*) = md; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10041) "Server %s:%d matches md %s (config %s) for domain %s, " - "has now %d MDs", + "has now %d MDs", s->server_hostname, s->port, md->name, sc->name, domain, (int)sc->assigned->nelts); - + if (sc->ca_contact && sc->ca_contact[0]) { uri = md_util_schemify(p, sc->ca_contact, "mailto"); if (md_array_str_index(md->contacts, uri, 0, 0) < 0) { @@ -608,7 +619,7 @@ static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, apr_po int i; md_t *md; apr_status_t rv = APR_SUCCESS; - + apr_array_clear(mc->unused_names); for (i = 0; i < mc->mds->nelts; ++i) { md = APR_ARRAY_IDX(mc->mds, i, md_t*); @@ -620,7 +631,7 @@ leave: return rv; } -static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, +static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, server_rec *base_server, int log_level) { md_srv_conf_t *base_conf; @@ -634,13 +645,13 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, * in the server. This list is collected during configuration processing and, * in the post config phase, get updated from all merged server configurations * before the server starts processing. - */ + */ base_conf = md_config_get(base_server); md_config_get_timespan(&ts, base_conf, MD_CONFIG_RENEW_WINDOW); if (ts) md_reg_set_renew_window_default(mc->reg, ts); md_config_get_timespan(&ts, base_conf, MD_CONFIG_WARN_WINDOW); if (ts) md_reg_set_warn_window_default(mc->reg, ts); - + /* Complete the properties of the MDs, now that we have the complete, merged * server configurations. */ @@ -660,18 +671,21 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, return APR_EINVAL; } } - - if (md->cert_file && !md->pkey_file) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10170) - "The Managed Domain '%s', defined in %s(line %d), " - "has a MDCertificateFile but no MDCertificateKeyFile.", - md->name, md->defn_name, md->defn_line_number); - return APR_EINVAL; + + if (md->cert_files && md->cert_files->nelts) { + if (!md->pkey_files || (md->cert_files->nelts != md->pkey_files->nelts)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10170) + "The Managed Domain '%s', defined in %s(line %d), " + "needs one MDCertificateKeyFile for each MDCertificateFile.", + md->name, md->defn_name, md->defn_line_number); + return APR_EINVAL; + } } - if (!md->cert_file && md->pkey_file) { + else if (md->pkey_files && md->pkey_files->nelts + && (!md->cert_files || !md->cert_files->nelts)) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10171) "The Managed Domain '%s', defined in %s(line %d), " - "has a MDCertificateKeyFile but no MDCertificateFile.", + "has MDCertificateKeyFile(s) but no MDCertificateFile.", md->name, md->defn_name, md->defn_line_number); return APR_EINVAL; } @@ -694,12 +708,12 @@ static void load_staged_data(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p) md_t *md; md_result_t *result; int i; - + for (i = 0; i < mc->mds->nelts; ++i) { md = APR_ARRAY_IDX(mc->mds, i, md_t *); result = md_result_md_make(p, md->name); if (APR_SUCCESS == (rv = md_reg_load_staging(mc->reg, md, mc->env, result, p))) { - ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) + ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) "%s: staged set activated", md->name); } else if (!APR_STATUS_IS_ENOENT(rv)) { @@ -713,13 +727,13 @@ static apr_status_t check_invalid_duplicates(server_rec *base_server) { server_rec *s; md_srv_conf_t *sc; - - ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server, + + ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server, "checking duplicate ssl assignments"); for (s = base_server; s; s = s->next) { sc = md_config_get(s); if (!sc || !sc->assigned) continue; - + if (sc->assigned->nelts > 1 && sc->is_ssl) { /* duplicate assignment to SSL VirtualHost, not allowed */ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10042) @@ -731,7 +745,7 @@ static apr_status_t check_invalid_duplicates(server_rec *base_server) return APR_SUCCESS; } -static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server, +static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server, apr_pool_t *p, apr_pool_t *ptemp) { server_rec *s; @@ -776,7 +790,7 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t * md_t *md; md_result_t *result; int i, count; - + /* Calculate the list of MD names which we need to watch: * - all MDs that are used somewhere * - all MDs in drive mode 'AUTO' that are not in 'unused_names' @@ -788,7 +802,7 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t * md_result_set(result, APR_SUCCESS, NULL); md->watched = 0; if (md->state == MD_S_ERROR) { - md_result_set(result, APR_EGENERAL, + md_result_set(result, APR_EGENERAL, "in error state, unable to drive forward. This " "indicates an incomplete or inconsistent configuration. " "Please check the log for warnings in this regard."); @@ -800,22 +814,22 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t * /* This MD is not used in any virtualhost, do not watch */ continue; } - + if (md_will_renew_cert(md)) { /* make a test init to detect early errors. */ md_reg_test_init(mc->reg, md, mc->env, result, p); if (APR_SUCCESS != result->status && result->detail) { apr_hash_set(mc->init_errors, md->name, APR_HASH_KEY_STRING, apr_pstrdup(p, result->detail)); - ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10173) + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10173) "md[%s]: %s", md->name, result->detail); } } - + md->watched = 1; ++count; } return count; -} +} static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) @@ -833,7 +847,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, /* At the first start, httpd makes a config check dry run. It * runs all config hooks to check if it can. If so, it does * this all again and starts serving requests. - * + * * On a dry run, we therefore do all the cheap config things we * need to do to find out if the settings are ok. More expensive * things we delay to the real run. @@ -859,12 +873,14 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, mc = sc->mc; mc->dry_run = dry_run; + md_event_init(p); + md_event_subscribe(on_event, mc); + if (APR_SUCCESS != (rv = setup_store(&store, mc, p, s)) - || APR_SUCCESS != (rv = md_reg_create(&mc->reg, p, store, mc->proxy_url))) { + || APR_SUCCESS != (rv = md_reg_create(&mc->reg, p, store, mc->proxy_url, mc->ca_certs))) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) "setup md registry"); goto leave; } - md_reg_set_notify_cb(mc->reg, notify, mc); /* renew on 30% remaining /*/ rv = md_ocsp_reg_make(&mc->ocsp, p, store, mc->ocsp_renew_window, @@ -873,8 +889,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10196) "setup ocsp registry"); goto leave; } - md_ocsp_set_notify_cb(mc->ocsp, notify, mc); - + init_ssl(); /* How to bootstrap this module: @@ -884,17 +899,17 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, * 4. Update the store with the MDs. Change domain names, create new MDs, etc. * Basically all MD properties that are configured directly. * WARNING: this may change the name of an MD. If an MD loses the first - * of its domain names, it first gets the new first one as name. The + * of its domain names, it first gets the new first one as name. The * store will find the old settings and "recover" the previous name. * 5. Load any staged data from previous driving. * 6. on a dry run, this is all we do * 7. Read back the MD properties that reflect the existence and aspect of - * credentials that are in the store (or missing there). + * credentials that are in the store (or missing there). * Expiry times, MD state, etc. * 8. Determine the list of MDs that need driving/supervision. * 9. Cleanup any left-overs in registry/store that are no longer needed for * the list of MDs as we know it now. - * 10. If this list is non-empty, setup a watchdog to run. + * 10. If this list is non-empty, setup a watchdog to run. */ /*1*/ if (APR_SUCCESS != (rv = detect_supported_protocols(mc, s, p, log_level))) goto leave; @@ -930,7 +945,7 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog, /*6*/ if (!sc || !sc->mc || sc->mc->dry_run) goto leave; mc = sc->mc; - + /*7*/ if (APR_SUCCESS != (rv = check_invalid_duplicates(s))) { goto leave; @@ -939,13 +954,16 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog, for (i = 0; i < mc->mds->nelts; ++i) { md = APR_ARRAY_IDX(mc->mds, i, md_t *); + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: auto_add", md->name); if (APR_SUCCESS != (rv = auto_add_domains(md, s, p))) { goto leave; } init_acme_tls_1_domains(md, s); + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: check_usage", md->name); if (APR_SUCCESS != (rv = check_usage(mc, md, s, p, ptemp))) { goto leave; } + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: sync_finish", md->name); if (APR_SUCCESS != (rv = md_reg_sync_finish(mc->reg, md, p, ptemp))) { ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10172) "md[%s]: error syncing to store", md->name); @@ -953,19 +971,21 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog, } } /*8*/ + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "init_cert_watch"); watched = init_cert_watch_status(mc, p, ptemp, s); /*9*/ + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "cleanup challenges"); md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds); - - /* From here on, the domains in the registry are readonly + + /* From here on, the domains in the registry are readonly * and only staging/challenges may be manipulated */ md_reg_freeze_domains(mc->reg, mc->mds); - + if (watched) { /*10*/ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10074) "%d out of %d mds need watching", watched, mc->mds->nelts); - + md_http_use_implementation(md_curl_get_impl(p)); rv = md_renew_start_watching(mc, s, p); } @@ -973,12 +993,16 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog, ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to supervise"); } - if (!mc->ocsp || md_ocsp_count(mc->ocsp) == 0) goto leave; - + if (!mc->ocsp || md_ocsp_count(mc->ocsp) == 0) { + ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, s, "no ocsp to manage"); + goto leave; + } + md_http_use_implementation(md_curl_get_impl(p)); rv = md_ocsp_start_watching(mc, s, p); - + leave: + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "post_config done"); return rv; } @@ -1006,7 +1030,7 @@ static int md_protocol_propose(conn_rec *c, request_rec *r, apr_array_header_t *proposals) { (void)s; - if (!r && offers && opt_ssl_is_https && opt_ssl_is_https(c) + if (!r && offers && ap_ssl_conn_is_ssl(c) && ap_array_str_contains(offers, PROTO_ACME_TLS_1)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "proposing protocol '%s'", PROTO_ACME_TLS_1); @@ -1020,9 +1044,9 @@ static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, const char *protocol) { md_conn_ctx *ctx; - + (void)s; - if (!r && opt_ssl_is_https && opt_ssl_is_https(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) { + if (!r && ap_ssl_conn_is_ssl(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "switching protocol '%s'", PROTO_ACME_TLS_1); ctx = apr_pcalloc(c->pool, sizeof(*ctx)); @@ -1035,57 +1059,64 @@ static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, return DECLINED; } - + /**************************************************************************************************/ /* Access API to other httpd components */ -static apr_status_t setup_fallback_cert(md_store_t *store, const md_t *md, - server_rec *s, apr_pool_t *p) +static void fallback_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn ) +{ + *keyfn = apr_pstrcat(p, "fallback-", md_pkey_filename(kspec, p), NULL); + *certfn = apr_pstrcat(p, "fallback-", md_chain_filename(kspec, p), NULL); +} + +static apr_status_t make_fallback_cert(md_store_t *store, const md_t *md, md_pkey_spec_t *kspec, + server_rec *s, apr_pool_t *p, char *keyfn, char *crtfn) { md_pkey_t *pkey; md_cert_t *cert; - md_pkey_spec_t spec; apr_status_t rv; - - spec.type = MD_PKEY_TYPE_RSA; - spec.params.rsa.bits = MD_PKEY_RSA_BITS_DEF; - - if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, &spec)) - || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, - MD_FN_FALLBACK_PKEY, MD_SV_PKEY, (void*)pkey, 0)) - || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback", + + if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, kspec)) + || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, + keyfn, MD_SV_PKEY, (void*)pkey, 0)) + || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback", md->domains, pkey, apr_time_from_sec(14 * MD_SECS_PER_DAY), p)) - || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, - MD_FN_FALLBACK_CERT, MD_SV_CERT, (void*)cert, 0))) { + || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, + crtfn, MD_SV_CERT, (void*)cert, 0))) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10174) - "%s: setup fallback certificate", md->name); + "%s: make fallback %s certificate", md->name, md_pkey_spec_name(kspec)); } return rv; } -static apr_status_t get_certificate(server_rec *s, apr_pool_t *p, int fallback, - const char **pcertfile, const char **pkeyfile) +static apr_status_t get_certificates(server_rec *s, apr_pool_t *p, int fallback, + apr_array_header_t **pcert_files, + apr_array_header_t **pkey_files) { - apr_status_t rv = APR_ENOENT; + apr_status_t rv = APR_ENOENT; md_srv_conf_t *sc; md_reg_t *reg; md_store_t *store; const md_t *md; - - *pkeyfile = NULL; - *pcertfile = NULL; + apr_array_header_t *key_files, *chain_files; + const char *keyfile, *chainfile; + int i; + + *pkey_files = *pcert_files = NULL; + key_files = apr_array_make(p, 5, sizeof(const char*)); + chain_files = apr_array_make(p, 5, sizeof(const char*)); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10113) - "get_certificate called for vhost %s.", s->server_hostname); + "get_certificates called for vhost %s.", s->server_hostname); sc = md_config_get(s); if (!sc) { - ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s, - "asked for certificate of server %s which has no md config", + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s, + "asked for certificate of server %s which has no md config", s->server_hostname); return APR_ENOENT; } - + assert(sc->mc); reg = sc->mc->reg; assert(reg); @@ -1107,141 +1138,181 @@ static apr_status_t get_certificate(server_rec *s, apr_pool_t *p, int fallback, return APR_EINVAL; } md = APR_ARRAY_IDX(sc->assigned, 0, const md_t*); - - rv = md_reg_get_cred_files(pkeyfile, pcertfile, reg, MD_SG_DOMAINS, md, p); - if (APR_STATUS_IS_ENOENT(rv)) { - if (fallback) { - /* Provide temporary, self-signed certificate as fallback, so that - * clients do not get obscure TLS handshake errors or will see a fallback - * virtual host that is not intended to be served here. */ - store = md_reg_store_get(reg); - assert(store); - - md_store_get_fname(pkeyfile, store, MD_SG_DOMAINS, md->name, MD_FN_FALLBACK_PKEY, p); - md_store_get_fname(pcertfile, store, MD_SG_DOMAINS, md->name, MD_FN_FALLBACK_CERT, p); - if (!md_file_exists(*pkeyfile, p) || !md_file_exists(*pcertfile, p)) { - if (APR_SUCCESS != (rv = setup_fallback_cert(store, md, s, p))) { - return rv; + + if (md->cert_files && md->cert_files->nelts) { + apr_array_cat(chain_files, md->cert_files); + apr_array_cat(key_files, md->pkey_files); + rv = APR_SUCCESS; + } + else { + md_pkey_spec_t *spec; + + for (i = 0; i < md_cert_count(md); ++i) { + spec = md_pkeys_spec_get(md->pks, i); + rv = md_reg_get_cred_files(&keyfile, &chainfile, reg, MD_SG_DOMAINS, md, spec, p); + if (APR_SUCCESS == rv) { + APR_ARRAY_PUSH(key_files, const char*) = keyfile; + APR_ARRAY_PUSH(chain_files, const char*) = chainfile; + } + else if (!APR_STATUS_IS_ENOENT(rv)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110) + "retrieving credentials for MD %s (%s)", + md->name, md_pkey_spec_name(spec)); + return rv; + } + } + + if (md_array_is_empty(key_files)) { + if (fallback) { + /* Provide temporary, self-signed certificate as fallback, so that + * clients do not get obscure TLS handshake errors or will see a fallback + * virtual host that is not intended to be served here. */ + char *kfn, *cfn; + + store = md_reg_store_get(reg); + assert(store); + + for (i = 0; i < md_cert_count(md); ++i) { + spec = md_pkeys_spec_get(md->pks, i); + fallback_fnames(p, spec, &kfn, &cfn); + + md_store_get_fname(&keyfile, store, MD_SG_DOMAINS, md->name, kfn, p); + md_store_get_fname(&chainfile, store, MD_SG_DOMAINS, md->name, cfn, p); + if (!md_file_exists(keyfile, p) || !md_file_exists(chainfile, p)) { + if (APR_SUCCESS != (rv = make_fallback_cert(store, md, spec, s, p, kfn, cfn))) { + return rv; + } + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116) + "%s: providing %s fallback certificate for server %s", + md->name, md_pkey_spec_name(spec), s->server_hostname); + APR_ARRAY_PUSH(key_files, const char*) = keyfile; + APR_ARRAY_PUSH(chain_files, const char*) = chainfile; } + rv = APR_EAGAIN; + goto leave; } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116) - "%s: providing fallback certificate for server %s", - md->name, s->server_hostname); - return APR_EAGAIN; } } - else if (APR_SUCCESS != rv) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110) - "retrieving credentials for MD %s", md->name); - return rv; - } - - ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077) - "%s[state=%d]: providing certificate for server %s", + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077) + "%s[state=%d]: providing certificates for server %s", md->name, md->state, s->server_hostname); +leave: + if (!md_array_is_empty(key_files) && !md_array_is_empty(chain_files)) { + *pkey_files = key_files; + *pcert_files = chain_files; + } return rv; } static int md_add_cert_files(server_rec *s, apr_pool_t *p, - apr_array_header_t *cert_files, + apr_array_header_t *cert_files, apr_array_header_t *key_files) { - const char *certfile, *keyfile; + apr_array_header_t *md_cert_files; + apr_array_header_t *md_key_files; apr_status_t rv; - + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_cert_files for %s", s->server_hostname); - rv = get_certificate(s, p, 0, &certfile, &keyfile); + rv = get_certificates(s, p, 0, &md_cert_files, &md_key_files); if (APR_SUCCESS == rv) { if (!apr_is_empty_array(cert_files)) { - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10084) + /* downgraded fromm WARNING to DEBUG, since installing separate certificates + * may be a valid use case. */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10084) "host '%s' is covered by a Managed Domain, but " "certificate/key files are already configured " - "for it (most likely via SSLCertificateFile).", + "for it (most likely via SSLCertificateFile).", s->server_hostname); - } - APR_ARRAY_PUSH(cert_files, const char*) = certfile; - APR_ARRAY_PUSH(key_files, const char*) = keyfile; + } + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, + "host '%s' is covered by a Managed Domaina and " + "is being provided with %d key/certificate files.", + s->server_hostname, md_cert_files->nelts); + apr_array_cat(cert_files, md_cert_files); + apr_array_cat(key_files, md_key_files); return DONE; } return DECLINED; } static int md_add_fallback_cert_files(server_rec *s, apr_pool_t *p, - apr_array_header_t *cert_files, + apr_array_header_t *cert_files, apr_array_header_t *key_files) { - const char *certfile, *keyfile; + apr_array_header_t *md_cert_files; + apr_array_header_t *md_key_files; apr_status_t rv; - + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_fallback_cert_files for %s", s->server_hostname); - rv = get_certificate(s, p, 1, &certfile, &keyfile); + rv = get_certificates(s, p, 1, &md_cert_files, &md_key_files); if (APR_EAGAIN == rv) { - APR_ARRAY_PUSH(cert_files, const char*) = certfile; - APR_ARRAY_PUSH(key_files, const char*) = keyfile; + apr_array_cat(cert_files, md_cert_files); + apr_array_cat(key_files, md_key_files); return DONE; } return DECLINED; } -static int md_is_challenge(conn_rec *c, const char *servername, - X509 **pcert, EVP_PKEY **pkey) +static int md_answer_challenge(conn_rec *c, const char *servername, + const char **pcert_pem, const char **pkey_pem) { + const char *protocol; + int hook_rv = DECLINED; + apr_status_t rv = APR_ENOENT; md_srv_conf_t *sc; - const char *protocol, *challenge, *cert_name, *pkey_name; - apr_status_t rv; + md_store_t *store; + char *cert_name, *pkey_name; + const char *cert_pem, *key_pem; + int i; - if (!servername) goto out; - - challenge = NULL; - if ((protocol = md_protocol_get(c)) && !strcmp(PROTO_ACME_TLS_1, protocol)) { - challenge = "tls-alpn-01"; - cert_name = MD_FN_TLSALPN01_CERT; - pkey_name = MD_FN_TLSALPN01_PKEY; - - sc = md_config_get(c->base_server); - if (sc && sc->mc->reg) { - md_store_t *store = md_reg_store_get(sc->mc->reg); - md_cert_t *mdcert; - md_pkey_t *mdpkey; - - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "%s: load certs/keys %s/%s", - servername, cert_name, pkey_name); - rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, - MD_SV_CERT, (void**)&mdcert, c->pool); - if (APR_SUCCESS == rv && (*pcert = md_cert_get_X509(mdcert))) { - rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, - MD_SV_PKEY, (void**)&mdpkey, c->pool); - if (APR_SUCCESS == rv && (*pkey = md_pkey_get_EVP_PKEY(mdpkey))) { - ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10078) - "%s: is a %s challenge host", servername, challenge); - return 1; - } - ap_log_cerror(APLOG_MARK, APLOG_WARNING, rv, c, APLOGNO(10079) - "%s: challenge data not complete, key unavailable", servername); - } - else { - ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080) - "%s: unknown %s challenge host", servername, challenge); - } - } + if (!servername + || !(protocol = md_protocol_get(c)) + || strcmp(PROTO_ACME_TLS_1, protocol)) { + goto cleanup; } -out: - *pcert = NULL; - *pkey = NULL; - return 0; -} + sc = md_config_get(c->base_server); + if (!sc || !sc->mc->reg) goto cleanup; -static int md_answer_challenge(conn_rec *c, const char *servername, - X509 **pcert, EVP_PKEY **pkey) -{ - if (md_is_challenge(c, servername, pcert, pkey)) { - return APR_SUCCESS; + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, + "Answer challenge[tls-alpn-01] for %s", servername); + store = md_reg_store_get(sc->mc->reg); + + for (i = 0; i < md_pkeys_spec_count( sc->pks ); i++) { + tls_alpn01_fnames(c->pool, md_pkeys_spec_get(sc->pks,i), + &pkey_name, &cert_name); + + rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, MD_SV_TEXT, + (void**)&cert_pem, c->pool); + if (APR_STATUS_IS_ENOENT(rv)) continue; + if (APR_SUCCESS != rv) goto cleanup; + + rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, MD_SV_TEXT, + (void**)&key_pem, c->pool); + if (APR_STATUS_IS_ENOENT(rv)) continue; + if (APR_SUCCESS != rv) goto cleanup; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "Found challenge cert %s, key %s for %s", + cert_name, pkey_name, servername); + *pcert_pem = cert_pem; + *pkey_pem = key_pem; + hook_rv = OK; + break; } - return DECLINED; + + if (DECLINED == hook_rv) { + ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080) + "%s: unknown tls-alpn-01 challenge host", servername); + } + +cleanup: + return hook_rv; } + /**************************************************************************************************/ /* ACME 'http-01' challenge responses */ @@ -1256,28 +1327,28 @@ static int md_http_challenge_pr(request_rec *r) md_reg_t *reg; const md_t *md; apr_status_t rv; - - if (r->parsed_uri.path + + if (r->parsed_uri.path && !strncmp(ACME_CHALLENGE_PREFIX, r->parsed_uri.path, sizeof(ACME_CHALLENGE_PREFIX)-1)) { sc = ap_get_module_config(r->server->module_config, &md_module); if (sc && sc->mc) { - ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, - "access inside /.well-known/acme-challenge for %s%s", + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "access inside /.well-known/acme-challenge for %s%s", r->hostname, r->parsed_uri.path); md = md_get_by_domain(sc->mc->mds, r->hostname); name = r->parsed_uri.path + sizeof(ACME_CHALLENGE_PREFIX)-1; reg = sc && sc->mc? sc->mc->reg : NULL; - + if (strlen(name) && !ap_strchr_c(name, '/') && reg) { md_store_t *store = md_reg_store_get(reg); - - rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname, + + rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname, MD_FN_HTTP01, MD_SV_TEXT, (void**)&data, r->pool); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "loading challenge for %s (%s)", r->hostname, r->uri); if (APR_SUCCESS == rv) { apr_size_t len = strlen(data); - + if (r->method_number != M_GET) { return HTTP_NOT_IMPLEMENTED; } @@ -1285,16 +1356,17 @@ static int md_http_challenge_pr(request_rec *r) * configured for. Let's send the content back */ r->status = HTTP_OK; apr_table_setn(r->headers_out, "Content-Length", apr_ltoa(r->pool, (long)len)); - + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_brigade_write(bb, NULL, NULL, data, len); ap_pass_brigade(r->output_filters, bb); apr_brigade_cleanup(bb); - + return DONE; } else if (!md || md->renew_mode == MD_RENEW_MANUAL - || (md->cert_file && md->renew_mode == MD_RENEW_AUTO)) { + || (md->cert_files && md->cert_files->nelts + && md->renew_mode == MD_RENEW_AUTO)) { /* The request hostname is not for a domain - or at least not for * a domain that we renew ourselves. We are not * the sole authority here for /.well-known/acme-challenge (see PR62189). @@ -1325,25 +1397,24 @@ static int md_require_https_maybe(request_rec *r) const char *s, *host; const md_t *md; int status; - + /* Requests outside the /.well-known path are subject to possible * https: redirects or HSTS header additions. */ sc = ap_get_module_config(r->server->module_config, &md_module); - if (!sc || !sc->assigned || !sc->assigned->nelts - || !opt_ssl_is_https || !r->parsed_uri.path + if (!sc || !sc->assigned || !sc->assigned->nelts || !r->parsed_uri.path || !strncmp(WELL_KNOWN_PREFIX, r->parsed_uri.path, sizeof(WELL_KNOWN_PREFIX)-1)) { goto declined; } - + host = ap_get_server_name_for_url(r); md = md_get_for_domain(r->server, host); if (!md) goto declined; - - if (opt_ssl_is_https(r->connection)) { + + if (ap_ssl_conn_is_ssl(r->connection)) { /* Using https: * if 'permanent' and no one else set a HSTS header already, do it */ - if (md->require_https == MD_REQUIRE_PERMANENT + if (md->require_https == MD_REQUIRE_PERMANENT && sc->mc->hsts_header && !apr_table_get(r->headers_out, MD_HSTS_HEADER)) { apr_table_setn(r->headers_out, MD_HSTS_HEADER, sc->mc->hsts_header); } @@ -1353,15 +1424,15 @@ static int md_require_https_maybe(request_rec *r) /* Not using https:, but require it. Redirect. */ if (r->method_number == M_GET) { /* safe to use the old-fashioned codes */ - status = ((MD_REQUIRE_PERMANENT == md->require_https)? + status = ((MD_REQUIRE_PERMANENT == md->require_https)? HTTP_MOVED_PERMANENTLY : HTTP_MOVED_TEMPORARILY); } else { /* these should keep the method unchanged on retry */ - status = ((MD_REQUIRE_PERMANENT == md->require_https)? + status = ((MD_REQUIRE_PERMANENT == md->require_https)? HTTP_PERMANENT_REDIRECT : HTTP_TEMPORARY_REDIRECT); } - + s = ap_construct_url(r->pool, r->uri, r); if (APR_SUCCESS == apr_uri_parse(r->pool, s, &uri)) { uri.scheme = (char*)"https"; @@ -1381,7 +1452,7 @@ declined: return DECLINED; } -/* Runs once per created child process. Perform any process +/* Runs once per created child process. Perform any process * related initialization here. */ static void md_child_init(apr_pool_t *pool, server_rec *s) @@ -1394,19 +1465,20 @@ static void md_child_init(apr_pool_t *pool, server_rec *s) */ static void md_hooks(apr_pool_t *pool) { - static const char *const mod_ssl[] = { "mod_ssl.c", NULL}; + static const char *const mod_ssl[] = { "mod_ssl.c", "mod_tls.c", NULL}; + static const char *const mod_wd[] = { "mod_watchdog.c", NULL}; /* Leave the ssl initialization to mod_ssl or friends. */ md_acme_init(pool, AP_SERVER_BASEVERSION, 0); - + ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks"); - + /* Run once after configuration is set, before mod_ssl. * Run again after mod_ssl is done. */ ap_hook_post_config(md_post_config_before_ssl, NULL, mod_ssl, APR_HOOK_MIDDLE); - ap_hook_post_config(md_post_config_after_ssl, mod_ssl, NULL, APR_HOOK_MIDDLE); - + ap_hook_post_config(md_post_config_after_ssl, mod_ssl, mod_wd, APR_HOOK_LAST); + /* Run once after a child process has been created. */ ap_hook_child_init(md_child_init, NULL, mod_ssl, APR_HOOK_MIDDLE); @@ -1425,14 +1497,20 @@ static void md_hooks(apr_pool_t *pool) APR_OPTIONAL_HOOK(ap, status_hook, md_ocsp_status_hook, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(md_status_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_answer_challenge(md_answer_challenge, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_add_cert_files(md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_add_fallback_cert_files(md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE); + +#if AP_MODULE_MAGIC_AT_LEAST(20120211, 105) + ap_hook_ssl_ocsp_prime_hook(md_ocsp_prime_status, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_ocsp_get_resp_hook(md_ocsp_provide_status, NULL, NULL, APR_HOOK_MIDDLE); +#else #ifndef SSL_CERT_HOOKS #error "This version of mod_md requires Apache httpd 2.4.41 or newer." #endif - APR_OPTIONAL_HOOK(ssl, add_cert_files, md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE); - APR_OPTIONAL_HOOK(ssl, add_fallback_cert_files, md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE); - APR_OPTIONAL_HOOK(ssl, answer_challenge, md_answer_challenge, NULL, NULL, APR_HOOK_MIDDLE); APR_OPTIONAL_HOOK(ssl, init_stapling_status, md_ocsp_init_stapling_status, NULL, NULL, APR_HOOK_MIDDLE); APR_OPTIONAL_HOOK(ssl, get_stapling_status, md_ocsp_get_stapling_status, NULL, NULL, APR_HOOK_MIDDLE); +#endif /* AP_MODULE_MAGIC_AT_LEAST() */ } diff --git a/modules/md/mod_md.dsp b/modules/md/mod_md.dsp index d05b7a02919..e141ffc10f7 100644 --- a/modules/md/mod_md.dsp +++ b/modules/md/mod_md.dsp @@ -145,10 +145,6 @@ SOURCE=./md_acme_order.c # End Source File # Begin Source File -SOURCE=./md_acmev1_drive.c -# End Source File -# Begin Source File - SOURCE=./md_acmev2_drive.c # End Source File # Begin Source File @@ -169,6 +165,10 @@ SOURCE=./md_http.c # End Source File # Begin Source File +SOURCE=./md_event.c +# End Source File +# Begin Source File + SOURCE=./md_json.c # End Source File # Begin Source File diff --git a/modules/md/mod_md.h b/modules/md/mod_md.h index cf043c45306..39b62f853fd 100644 --- a/modules/md/mod_md.h +++ b/modules/md/mod_md.h @@ -39,4 +39,9 @@ APR_DECLARE_OPTIONAL_FN(int, md_is_challenge, (struct conn_rec *, const char *, X509 **pcert, EVP_PKEY **pkey)); +APR_DECLARE_OPTIONAL_FN(apr_status_t, + md_answer_challenges, (conn_rec *c, const char *servername, + apr_array_header_t *certs, + apr_array_header_t *pkeys)); + #endif /* mod_md_mod_md_h */ diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index 66684213aa7..63b2e7ab811 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -81,6 +81,7 @@ static md_mod_conf_t defmc = { &def_ocsp_renew_window, /* default time to renew ocsp responses */ "crt.sh", /* default cert checker site name */ "https://crt.sh?q=", /* default cert checker site url */ + NULL, /* CA cert file to use */ }; static md_timeslice_t def_renew_window = { @@ -153,7 +154,7 @@ static void srv_conf_props_clear(md_srv_conf_t *sc) sc->require_https = MD_REQUIRE_UNSET; sc->renew_mode = DEF_VAL; sc->must_staple = DEF_VAL; - sc->pkey_spec = NULL; + sc->pks = NULL; sc->renew_window = NULL; sc->warn_window = NULL; sc->ca_url = NULL; @@ -171,7 +172,7 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from) to->require_https = from->require_https; to->renew_mode = from->renew_mode; to->must_staple = from->must_staple; - to->pkey_spec = from->pkey_spec; + to->pks = from->pks; to->warn_window = from->warn_window; to->renew_window = from->renew_window; to->ca_url = from->ca_url; @@ -189,7 +190,7 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t if (from->transitive != DEF_VAL) md->transitive = from->transitive; if (from->renew_mode != DEF_VAL) md->renew_mode = from->renew_mode; if (from->must_staple != DEF_VAL) md->must_staple = from->must_staple; - if (from->pkey_spec) md->pkey_spec = from->pkey_spec; + if (from->pks) md->pks = md_pkeys_spec_clone(p, from->pks); if (from->renew_window) md->renew_window = from->renew_window; if (from->warn_window) md->warn_window = from->warn_window; if (from->ca_url) md->ca_url = from->ca_url; @@ -227,7 +228,7 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv) nsc->require_https = (add->require_https != MD_REQUIRE_UNSET)? add->require_https : base->require_https; nsc->renew_mode = (add->renew_mode != DEF_VAL)? add->renew_mode : base->renew_mode; nsc->must_staple = (add->must_staple != DEF_VAL)? add->must_staple : base->must_staple; - nsc->pkey_spec = add->pkey_spec? add->pkey_spec : base->pkey_spec; + nsc->pks = (!md_pkeys_spec_is_empty(add->pks))? add->pks : base->pks; nsc->renew_window = add->renew_window? add->renew_window : base->renew_window; nsc->warn_window = add->warn_window? add->warn_window : base->warn_window; @@ -769,6 +770,7 @@ static const char *md_config_set_pkeys(cmd_parms *cmd, void *dc, md_srv_conf_t *config = md_config_get(cmd->server); const char *err, *ptype; apr_int64_t bits; + int i; (void)dc; if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) { @@ -778,42 +780,63 @@ static const char *md_config_set_pkeys(cmd_parms *cmd, void *dc, return "needs to specify the private key type"; } - ptype = argv[0]; - if (!apr_strnatcasecmp("Default", ptype)) { - if (argc > 1) { - return "type 'Default' takes no parameter"; - } - if (!config->pkey_spec) { - config->pkey_spec = apr_pcalloc(cmd->pool, sizeof(*config->pkey_spec)); + config->pks = md_pkeys_spec_make(cmd->pool); + for (i = 0; i < argc; ++i) { + ptype = argv[i]; + if (!apr_strnatcasecmp("Default", ptype)) { + if (argc > 1) { + return "'Default' allows no other parameter"; + } + md_pkeys_spec_add_default(config->pks); } - config->pkey_spec->type = MD_PKEY_TYPE_DEFAULT; - return NULL; - } - else if (!apr_strnatcasecmp("RSA", ptype)) { - if (argc == 1) { - bits = MD_PKEY_RSA_BITS_DEF; + else if (strlen(ptype) > 3 + && (ptype[0] == 'R' || ptype[0] == 'r') + && (ptype[1] == 'S' || ptype[1] == 's') + && (ptype[2] == 'A' || ptype[2] == 'a') + && isdigit(ptype[3])) { + bits = (int)apr_atoi64(ptype+3); + if (bits < MD_PKEY_RSA_BITS_MIN) { + return apr_psprintf(cmd->pool, + "must be %d or higher in order to be considered safe.", + MD_PKEY_RSA_BITS_MIN); + } + if (bits >= INT_MAX) { + return apr_psprintf(cmd->pool, "is too large for an RSA key length."); + } + if (md_pkeys_spec_contains_rsa(config->pks)) { + return "two keys of type 'RSA' are not possible."; + } + md_pkeys_spec_add_rsa(config->pks, (unsigned int)bits); } - else if (argc == 2) { - bits = (int)apr_atoi64(argv[1]); - if (bits < MD_PKEY_RSA_BITS_MIN || bits >= INT_MAX) { - return apr_psprintf(cmd->pool, "must be %d or higher in order to be considered " - "safe. Too large a value will slow down everything. Larger then 4096 probably does " - "not make sense unless quantum cryptography really changes spin.", - MD_PKEY_RSA_BITS_MIN); + else if (!apr_strnatcasecmp("RSA", ptype)) { + if (i+1 >= argc || !isdigit(argv[i+1][0])) { + bits = MD_PKEY_RSA_BITS_DEF; + } + else { + ++i; + bits = (int)apr_atoi64(argv[i]); + if (bits < MD_PKEY_RSA_BITS_MIN) { + return apr_psprintf(cmd->pool, + "must be %d or higher in order to be considered safe.", + MD_PKEY_RSA_BITS_MIN); + } + if (bits >= INT_MAX) { + return apr_psprintf(cmd->pool, "is too large for an RSA key length."); + } } + if (md_pkeys_spec_contains_rsa(config->pks)) { + return "two keys of type 'RSA' are not possible."; + } + md_pkeys_spec_add_rsa(config->pks, (unsigned int)bits); } else { - return "key type 'RSA' has only one optional parameter, the number of bits"; - } - - if (!config->pkey_spec) { - config->pkey_spec = apr_pcalloc(cmd->pool, sizeof(*config->pkey_spec)); + if (md_pkeys_spec_contains_ec(config->pks, argv[i])) { + return apr_psprintf(cmd->pool, "two keys of type '%s' are not possible.", argv[i]); + } + md_pkeys_spec_add_ec(config->pks, argv[i]); } - config->pkey_spec->type = MD_PKEY_TYPE_RSA; - config->pkey_spec->params.rsa.bits = (unsigned int)bits; - return NULL; } - return apr_pstrcat(cmd->pool, "unsupported private key type \"", ptype, "\"", NULL); + return NULL; } static const char *md_config_set_notify_cmd(cmd_parms *cmd, void *mconfig, const char *arg) @@ -855,27 +878,37 @@ static const char *md_config_set_dns01_cmd(cmd_parms *cmd, void *mconfig, const return NULL; } -static const char *md_config_set_cert_file(cmd_parms *cmd, void *mconfig, const char *arg) +static const char *md_config_add_cert_file(cmd_parms *cmd, void *mconfig, const char *arg) { md_srv_conf_t *sc = md_config_get(cmd->server); - const char *err; + const char *err, *fpath; (void)mconfig; if ((err = md_conf_check_location(cmd, MD_LOC_MD))) return err; assert(sc->current); - sc->current->cert_file = arg; + fpath = ap_server_root_relative(cmd->pool, arg); + if (!fpath) return apr_psprintf(cmd->pool, "certificate file not found: %s", arg); + if (!sc->current->cert_files) { + sc->current->cert_files = apr_array_make(cmd->pool, 3, sizeof(char*)); + } + APR_ARRAY_PUSH(sc->current->cert_files, const char*) = fpath; return NULL; } -static const char *md_config_set_key_file(cmd_parms *cmd, void *mconfig, const char *arg) +static const char *md_config_add_key_file(cmd_parms *cmd, void *mconfig, const char *arg) { md_srv_conf_t *sc = md_config_get(cmd->server); - const char *err; + const char *err, *fpath; (void)mconfig; if ((err = md_conf_check_location(cmd, MD_LOC_MD))) return err; assert(sc->current); - sc->current->pkey_file = arg; + fpath = ap_server_root_relative(cmd->pool, arg); + if (!fpath) return apr_psprintf(cmd->pool, "certificate key file not found: %s", arg); + if (!sc->current->pkey_files) { + sc->current->pkey_files = apr_array_make(cmd->pool, 3, sizeof(char*)); + } + APR_ARRAY_PUSH(sc->current->pkey_files, const char*) = fpath; return NULL; } @@ -967,6 +1000,15 @@ static const char *md_config_set_activation_delay(cmd_parms *cmd, void *mconfig, return NULL; } +static const char *md_config_set_ca_certs(cmd_parms *cmd, void *dc, const char *path) +{ + md_srv_conf_t *sc = md_config_get(cmd->server); + + (void)dc; + sc->mc->ca_certs = path; + return NULL; +} + const command_rec md_cmds[] = { AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF, "URL of CA issuing the certificates"), @@ -1017,9 +1059,9 @@ const command_rec md_cmds[] = { "Allow managing of base server outside virtual hosts."), AP_INIT_RAW_ARGS("MDChallengeDns01", md_config_set_dns01_cmd, NULL, RSRC_CONF, "Set the command for setup/teardown of dns-01 challenges"), - AP_INIT_TAKE1("MDCertificateFile", md_config_set_cert_file, NULL, RSRC_CONF, + AP_INIT_TAKE1("MDCertificateFile", md_config_add_cert_file, NULL, RSRC_CONF, "set the static certificate (chain) file to use for this domain."), - AP_INIT_TAKE1("MDCertificateKeyFile", md_config_set_key_file, NULL, RSRC_CONF, + AP_INIT_TAKE1("MDCertificateKeyFile", md_config_add_key_file, NULL, RSRC_CONF, "set the static private key file to use for this domain."), AP_INIT_TAKE1("MDServerStatus", md_config_set_server_status, NULL, RSRC_CONF, "On to see Managed Domains in server-status."), @@ -1041,6 +1083,8 @@ const command_rec md_cmds[] = { "Set name and URL pattern for a certificate monitoring site."), AP_INIT_TAKE1("MDActivationDelay", md_config_set_activation_delay, NULL, RSRC_CONF, "How long to delay activation of new certificates"), + AP_INIT_TAKE1("MDCACertificateFile", md_config_set_ca_certs, NULL, RSRC_CONF, + "Set the CA file to use for connections"), AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) }; diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h index 2be0f68d303..76423bd602a 100644 --- a/modules/md/mod_md_config.h +++ b/modules/md/mod_md_config.h @@ -21,7 +21,7 @@ struct apr_hash_t; struct md_store_t; struct md_reg_t; struct md_ocsp_reg_t; -struct md_pkey_spec_t; +struct md_pkeys_spec_t; typedef enum { MD_CONFIG_CA_URL, @@ -70,6 +70,7 @@ struct md_mod_conf_t { md_timeslice_t *ocsp_renew_window; /* time before exp. that we start renewing ocsp resp. */ const char *cert_check_name; /* name of the linked certificate check site */ const char *cert_check_url; /* url "template for" checking a certificate */ + const char *ca_certs; /* root certificates to use for connections */ }; typedef struct md_srv_conf_t { @@ -81,9 +82,9 @@ typedef struct md_srv_conf_t { md_require_t require_https; /* If MDs require https: access */ int renew_mode; /* mode of obtaining credentials */ int must_staple; /* certificates should set the OCSP Must Staple extension */ - struct md_pkey_spec_t *pkey_spec; /* specification for generating private keys */ - md_timeslice_t *renew_window; /* time before expiration that starts renewal */ - md_timeslice_t *warn_window; /* time before expiration that warning are sent out */ + struct md_pkeys_spec_t *pks; /* specification for private keys */ + md_timeslice_t *renew_window; /* time before expiration that starts renewal */ + md_timeslice_t *warn_window; /* time before expiration that warning are sent out */ const char *ca_url; /* url of CA certificate service */ const char *ca_contact; /* contact email registered to account */ diff --git a/modules/md/mod_md_drive.c b/modules/md/mod_md_drive.c index 1191a5fc3db..942256f3cfd 100644 --- a/modules/md/mod_md_drive.c +++ b/modules/md/mod_md_drive.c @@ -31,6 +31,7 @@ #include "md.h" #include "md_curl.h" #include "md_crypt.h" +#include "md_event.h" #include "md_http.h" #include "md_json.h" #include "md_status.h" @@ -79,7 +80,7 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p if (apr_time_now() < job->next_run) return; job->next_run = 0; - if (job->finished && job->notified) { + if (job->finished && job->notified_renewed) { /* finished and notification handled, nothing to do. */ goto leave; } @@ -101,7 +102,7 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p if (md_will_renew_cert(md)) { /* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly * without discarding previous/intermediate results. - * Only returns SUCCESS when the renewal is complete, e.g. STAGING as a + * Only returns SUCCESS when the renewal is complete, e.g. STAGING has a * complete set of new credentials. */ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052) @@ -113,7 +114,17 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p goto expiry; } - md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg)); + /* The (possibly configured) event handler may veto renewals. This + * is used in cluster installtations, see #233. */ + rv = md_event_raise("renewing", md->name, job, result, ptemp); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10060) + "%s: event-handler for 'renewing' returned %d, preventing renewal to proceed.", + job->mdomain, rv); + goto leave; + } + + md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg)); md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, result, ptemp); md_job_end_run(job, result); @@ -125,13 +136,15 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p goto leave; } - if (!job->notified) md_job_notify(job, "renewed", result); + if (!job->notified_renewed) { + md_job_notify(job, "renewed", result); + } } else { ap_log_error( APLOG_MARK, APLOG_ERR, result->status, dctx->s, APLOGNO(10056) "processing %s: %s", job->mdomain, result->detail); md_job_log_append(job, "renewal-error", result->problem, result->detail); - md_job_holler(job, "errored"); + md_event_holler("errored", job->mdomain, job, result, ptemp); ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10057) "%s: encountered error for the %d. time, next run in %s", job->mdomain, job->error_runs, @@ -144,9 +157,7 @@ expiry: ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s, "md(%s): warn about expiration", md->name); md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg)); - if (APR_SUCCESS == md_job_notify(job, "expiring", result)) { - md_result_set(result, APR_SUCCESS, NULL); - } + md_job_notify(job, "expiring", result); md_job_end_run(job, result); } @@ -162,7 +173,7 @@ int md_will_renew_cert(const md_t *md) if (md->renew_mode == MD_RENEW_MANUAL) { return 0; } - else if (md->renew_mode == MD_RENEW_AUTO && md->cert_file) { + else if (md->renew_mode == MD_RENEW_AUTO && md->cert_files && md->cert_files->nelts) { return 0; } return 1; diff --git a/modules/md/mod_md_ocsp.c b/modules/md/mod_md_ocsp.c index 2a01d5a8467..0f646760258 100644 --- a/modules/md/mod_md_ocsp.c +++ b/modules/md/mod_md_ocsp.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "mod_watchdog.h" @@ -53,7 +54,7 @@ static int staple_here(md_srv_conf_t *sc) } int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, - X509 *cert, X509 *issuer) + X509 *cert, X509 *issuer) { md_srv_conf_t *sc; const md_t *md; @@ -61,10 +62,10 @@ int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, sc = md_config_get(s); if (!staple_here(sc)) goto declined; - md = ((sc->assigned && sc->assigned->nelts == 1)? APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL); - rv = md_ocsp_prime(sc->mc->ocsp, md_cert_wrap(p, cert), + + rv = md_ocsp_prime(sc->mc->ocsp, NULL, 0, md_cert_wrap(p, cert), md_cert_wrap(p, issuer), md); ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s", md? md->name : s->server_hostname); @@ -75,13 +76,72 @@ declined: return DECLINED; } -int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, - conn_rec *c, server_rec *s, X509 *cert) +int md_ocsp_prime_status(server_rec *s, apr_pool_t *p, + const char *id, apr_size_t id_len, const char *pem) +{ + md_srv_conf_t *sc; + const md_t *md; + apr_array_header_t *chain; + apr_status_t rv = APR_ENOENT; + + sc = md_config_get(s); + if (!staple_here(sc)) goto cleanup; + + md = ((sc->assigned && sc->assigned->nelts == 1)? + APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL); + chain = apr_array_make(p, 5, sizeof(md_cert_t*)); + rv = md_cert_read_chain(chain, p, pem, strlen(pem)); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10268) "init stapling for: %s, " + "unable to parse PEM data", md? md->name : s->server_hostname); + goto cleanup; + } + else if (chain->nelts < 2) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10269) "init stapling for: %s, " + "need at least 2 certificates in PEM data", md? md->name : s->server_hostname); + rv = APR_EINVAL; + goto cleanup; + } + + rv = md_ocsp_prime(sc->mc->ocsp, id, id_len, + APR_ARRAY_IDX(chain, 0, md_cert_t*), + APR_ARRAY_IDX(chain, 1, md_cert_t*), md); + ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s", + md? md->name : s->server_hostname); + +cleanup: + return (APR_SUCCESS == rv)? OK : DECLINED; +} + +typedef struct { + unsigned char *der; + apr_size_t der_len; +} ocsp_copy_ctx_t; + +static void ocsp_copy_der(const unsigned char *der, apr_size_t der_len, void *userdata) +{ + ocsp_copy_ctx_t *ctx = userdata; + + memset(ctx, 0, sizeof(*ctx)); + if (der && der_len > 0) { + ctx->der = OPENSSL_malloc(der_len); + if (ctx->der != NULL) { + ctx->der_len = der_len; + memcpy(ctx->der, der, der_len); + } + } +} + +int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, + conn_rec *c, server_rec *s, X509 *x) { md_srv_conf_t *sc; const md_t *md; + md_cert_t *cert; + md_data_t id; apr_status_t rv; - + ocsp_copy_ctx_t ctx; + sc = md_config_get(s); if (!staple_here(sc)) goto declined; @@ -89,15 +149,45 @@ int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s", md? md->name : s->server_hostname); - rv = md_ocsp_get_status(pder, pderlen, sc->mc->ocsp, - md_cert_wrap(c->pool, cert), c->pool, md); + cert = md_cert_wrap(c->pool, x); + rv = md_ocsp_init_id(&id, c->pool, cert); + if (APR_SUCCESS != rv) goto declined; + + rv = md_ocsp_get_status(ocsp_copy_der, &ctx, sc->mc->ocsp, id.data, id.len, c->pool, md); if (APR_STATUS_IS_ENOENT(rv)) goto declined; - return rv; + *pder = ctx.der; + *pderlen = (int)ctx.der_len; + return OK; declined: return DECLINED; } - + +int md_ocsp_provide_status(server_rec *s, conn_rec *c, + const char *id, apr_size_t id_len, + ap_ssl_ocsp_copy_resp *cb, void *userdata) +{ + md_srv_conf_t *sc; + const md_t *md; + apr_status_t rv; + + sc = md_config_get(s); + if (!staple_here(sc)) goto declined; + + md = ((sc->assigned && sc->assigned->nelts == 1)? + APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s", + md? md->name : s->server_hostname); + + rv = md_ocsp_get_status(cb, userdata, sc->mc->ocsp, id, id_len, c->pool, md); + if (APR_STATUS_IS_ENOENT(rv)) goto declined; + return OK; + +declined: + return DECLINED; +} + + /**************************************************************************************************/ /* watchdog based impl. */ diff --git a/modules/md/mod_md_ocsp.h b/modules/md/mod_md_ocsp.h index ee58df678a7..dcc6e49c5ad 100644 --- a/modules/md/mod_md_ocsp.h +++ b/modules/md/mod_md_ocsp.h @@ -24,6 +24,12 @@ int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, conn_rec *c, server_rec *s, X509 *cert); +int md_ocsp_prime_status(server_rec *s, apr_pool_t *p, + const char *id, apr_size_t id_len, const char *pem); + +int md_ocsp_provide_status(server_rec *s, conn_rec *c, const char *id, apr_size_t id_len, + ap_ssl_ocsp_copy_resp *cb, void *userdata); + /** * Start watchdog for retrieving/updating ocsp status. */ diff --git a/modules/md/mod_md_status.c b/modules/md/mod_md_status.c index cde13325c75..cb0e2677f06 100644 --- a/modules/md/mod_md_status.c +++ b/modules/md/mod_md_status.c @@ -58,9 +58,12 @@ int md_http_cert_status(request_rec *r) { - md_json_t *resp, *j, *mdj, *certj; + int i; + md_json_t *resp, *mdj, *cj; const md_srv_conf_t *sc; const md_t *md; + md_pkey_spec_t *spec; + const char *keyname; apr_bucket_brigade *bb; apr_status_t rv; @@ -96,32 +99,40 @@ int md_http_cert_status(request_rec *r) "status for MD: %s is %s", md->name, md_json_writep(mdj, r->pool, MD_JSON_FMT_INDENT)); resp = md_json_create(r->pool); - - if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL)) { - md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL), - resp, MD_KEY_VALID, MD_KEY_UNTIL, NULL); - } - if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_FROM, NULL)) { - md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_FROM, NULL), - resp, MD_KEY_VALID, MD_KEY_FROM, NULL); - } - if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_SERIAL, NULL)) { - md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SERIAL, NULL), - resp, MD_KEY_SERIAL, NULL); - } - if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL)) { - md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL), - resp, MD_KEY_SHA256_FINGERPRINT, NULL); + + if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID, NULL)) { + md_json_setj(md_json_getj(mdj, MD_KEY_CERT, MD_KEY_VALID, NULL), resp, MD_KEY_VALID, NULL); + } + + for (i = 0; i < md_cert_count(md); ++i) { + spec = md_pkeys_spec_get(md->pks, i); + keyname = md_pkey_spec_name(spec); + cj = md_json_create(r->pool); + + if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_VALID, NULL)) { + md_json_setj(md_json_getj(mdj, MD_KEY_CERT, keyname, MD_KEY_VALID, NULL), + cj, MD_KEY_VALID, NULL); + } + + if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL)) { + md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL), + cj, MD_KEY_SERIAL, NULL); + } + if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL)) { + md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL), + cj, MD_KEY_SHA256_FINGERPRINT, NULL); + } + md_json_setj(cj, resp, keyname, NULL ); } if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) { - /* copy over the information we want to make public about this: - * - when not finished, add an empty object to indicate something is going on - * - when a certificate is staged, add the information from that */ - certj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL); - j = certj? certj : md_json_create(r->pool);; - md_json_setj(j, resp, MD_KEY_RENEWAL, NULL); - } + /* copy over the information we want to make public about this: + * - when not finished, add an empty object to indicate something is going on + * - when a certificate is staged, add the information from that */ + cj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL); + cj = cj? cj : md_json_create(r->pool);; + md_json_setj(cj, resp, MD_KEY_RENEWAL, MD_KEY_CERT, NULL); + } ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md[%s]: sending status", md->name); apr_table_set(r->headers_out, "Content-Type", "application/json"); @@ -329,7 +340,7 @@ static void print_job_summary(apr_bucket_brigade *bb, md_json_t *mdj, const char return; } - finished = (int)md_json_getl(mdj, key, MD_KEY_FINISHED, NULL); + finished = md_json_getb(mdj, key, MD_KEY_FINISHED, NULL); errors = (int)md_json_getl(mdj, key, MD_KEY_ERRORS, NULL); rv = (apr_status_t)md_json_getl(mdj, key, MD_KEY_LAST, MD_KEY_STATUS, NULL); @@ -388,16 +399,26 @@ static void si_val_activity(status_ctx *ctx, md_json_t *mdj, const status_info * } } -static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_info *info) +static int cert_check_iter(void *baton, const char *key, md_json_t *json) { + status_ctx *ctx = baton; const char *fingerprint; + fingerprint = md_json_gets(json, MD_KEY_SHA256_FINGERPRINT, NULL); + if (fingerprint) { + apr_brigade_printf(ctx->bb, NULL, NULL, + "%s[%s]
", + ctx->mc->cert_check_url, fingerprint, + ctx->mc->cert_check_name, key); + } + return 1; +} + +static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_info *info) +{ (void)info; if (ctx->mc->cert_check_name && ctx->mc->cert_check_url) { - fingerprint = md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL); - apr_brigade_printf(ctx->bb, NULL, NULL, - "%s ", - ctx->mc->cert_check_url, fingerprint, ctx->mc->cert_check_name); + md_json_iterkey(cert_check_iter, ctx, mdj, MD_KEY_CERT, NULL); } } @@ -436,6 +457,13 @@ static void add_json_val(status_ctx *ctx, md_json_t *j) } } +static void si_val_names(status_ctx *ctx, md_json_t *mdj, const status_info *info) +{ + apr_brigade_puts(ctx->bb, NULL, NULL, "
"); + add_json_val(ctx, md_json_getj(mdj, info->key, NULL)); + apr_brigade_puts(ctx->bb, NULL, NULL, "
"); +} + static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *info) { if (info->fn) { @@ -448,7 +476,7 @@ static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info * static const status_info status_infos[] = { { "Domain", MD_KEY_NAME, NULL }, - { "Names", MD_KEY_DOMAINS, NULL }, + { "Names", MD_KEY_DOMAINS, si_val_names }, { "Status", MD_KEY_STATE, si_val_status }, { "Valid", MD_KEY_CERT, si_val_cert_valid_time }, { "CA", MD_KEY_CA, si_val_ca_url }, diff --git a/modules/metadata/mod_headers.c b/modules/metadata/mod_headers.c index f7bda1552a3..ef812cd3edc 100644 --- a/modules/metadata/mod_headers.c +++ b/modules/metadata/mod_headers.c @@ -78,13 +78,12 @@ #include "httpd.h" #include "http_config.h" #include "http_request.h" +#include "http_ssl.h" #include "http_log.h" #include "util_filter.h" #include "http_protocol.h" #include "ap_expr.h" -#include "mod_ssl.h" /* for the ssl_var_lookup optional function defn */ - /* format_tag_hash is initialized during pre-config */ static apr_hash_t *format_tag_hash; @@ -161,9 +160,6 @@ typedef struct { module AP_MODULE_DECLARE_DATA headers_module; -/* Pointer to ssl_var_lookup, if available. */ -static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *header_ssl_lookup = NULL; - /* * Tag formatting functions */ @@ -210,17 +206,12 @@ static const char *header_request_env_var(request_rec *r, char *a) static const char *header_request_ssl_var(request_rec *r, char *name) { - if (header_ssl_lookup) { - const char *val = header_ssl_lookup(r->pool, r->server, - r->connection, r, name); - if (val && val[0]) - return unwrap_header(r->pool, val); - else - return "(null)"; - } - else { + const char *val = ap_ssl_var_lookup(r->pool, r->server, + r->connection, r, name); + if (val && val[0]) + return unwrap_header(r->pool, val); + else return "(null)"; - } } static const char *header_request_loadavg(request_rec *r, char *a) @@ -989,7 +980,6 @@ static int header_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) static int header_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { - header_ssl_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); return OK; } diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index 1cc7b754fd2..0f787a6b45a 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -29,10 +29,6 @@ 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)); -APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); -APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup, - (apr_pool_t *, server_rec *, - conn_rec *, request_rec *, char *)); #endif #ifndef MAX @@ -2816,8 +2812,6 @@ static const command_rec proxy_cmds[] = static APR_OPTIONAL_FN_TYPE(ssl_proxy_enable) *proxy_ssl_enable = NULL; static APR_OPTIONAL_FN_TYPE(ssl_engine_disable) *proxy_ssl_disable = NULL; static APR_OPTIONAL_FN_TYPE(ssl_engine_set) *proxy_ssl_engine = NULL; -static APR_OPTIONAL_FN_TYPE(ssl_is_https) *proxy_is_https = NULL; -static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *proxy_ssl_val = NULL; PROXY_DECLARE(int) ap_proxy_ssl_enable(conn_rec *c) { @@ -2867,23 +2861,14 @@ PROXY_DECLARE(int) ap_proxy_ssl_engine(conn_rec *c, PROXY_DECLARE(int) ap_proxy_conn_is_https(conn_rec *c) { - if (proxy_is_https) { - return proxy_is_https(c); - } - else - return 0; + return ap_ssl_conn_is_ssl(c); } PROXY_DECLARE(const char *) ap_proxy_ssl_val(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *var) { - if (proxy_ssl_val) { - /* XXX Perhaps the casting useless */ - return (const char *)proxy_ssl_val(p, s, c, r, (char *)var); - } - else - return NULL; + return ap_ssl_var_lookup(p, s, c, r, var); } static int proxy_post_config(apr_pool_t *pconf, apr_pool_t *plog, @@ -2901,8 +2886,6 @@ static int proxy_post_config(apr_pool_t *pconf, apr_pool_t *plog, proxy_ssl_enable = APR_RETRIEVE_OPTIONAL_FN(ssl_proxy_enable); proxy_ssl_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable); proxy_ssl_engine = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_set); - proxy_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); - proxy_ssl_val = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); ap_proxy_strmatch_path = apr_strmatch_precompile(pconf, "path=", 0); ap_proxy_strmatch_domain = apr_strmatch_precompile(pconf, "domain=", 0); diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 1ba54cd49e1..56461707cd2 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -58,6 +58,7 @@ #include "http_main.h" #include "http_log.h" #include "http_connection.h" +#include "http_ssl.h" #include "util_filter.h" #include "util_ebcdic.h" #include "ap_provider.h" diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index 0c3163f05f0..4da24eddcc5 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -195,13 +195,18 @@ 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) + X509 **pcert, EVP_PKEY **pkey, + const char **pcert_pem, const char **pkey_pem) { - if (APR_SUCCESS == ssl_run_answer_challenge(c, servername, pcert, pkey)) { - return 1; - } *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; } @@ -1805,10 +1810,12 @@ static apr_status_t ssl_init_server_ctx(server_rec *s, /* 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; diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 29646b878b6..b99dcf19d4a 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -2316,16 +2316,34 @@ 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) + 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); - return APR_EGENERAL; + rv = APR_EGENERAL; + goto cleanup; } if (!SSL_use_PrivateKey(ssl, key)) { @@ -2333,15 +2351,21 @@ 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; } 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; } @@ -2351,9 +2375,6 @@ static apr_status_t set_challenge_creds(conn_rec *c, const char *servername, */ static apr_status_t init_vhost(conn_rec *c, SSL *ssl, const char *servername) { - X509 *cert; - EVP_PKEY *key; - if (c) { SSLConnRec *sslcon = myConnConfig(c); @@ -2376,15 +2397,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)) { - /* 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) != 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 " @@ -2759,9 +2771,11 @@ int ssl_callback_alpn_select(SSL *ssl, const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); X509 *cert; EVP_PKEY *key; - - if (ssl_is_challenge(c, servername, &cert, &key)) { - if (set_challenge_creds(c, servername, ssl, cert, key) != APR_SUCCESS) { + 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); diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index 560c2fadfc8..a6fc7513a2e 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -36,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" @@ -1122,7 +1123,8 @@ 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. */ diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c index 74088f5e298..38079a9eaa8 100644 --- a/modules/ssl/ssl_util_ssl.c +++ b/modules/ssl/ssl_util_ssl.c @@ -511,3 +511,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 ec89185b1b0..443c1b7ee73 100644 --- a/modules/ssl/ssl_util_ssl.h +++ b/modules/ssl/ssl_util_ssl.h @@ -82,7 +82,26 @@ 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 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 4ba071e463e..7bd16d41228 100644 --- a/modules/ssl/ssl_util_stapling.c +++ b/modules/ssl/ssl_util_stapling.c @@ -134,6 +134,7 @@ 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) @@ -153,7 +154,18 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, return 1; } - if (ssl_run_init_stapling_status(s, p, x, issuer) == OK) { + 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); @@ -167,11 +179,6 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, goto cleanup; } - if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) { - rv = 0; - goto cleanup; - } - cinf = apr_hash_get(stapling_certinfo, idx, sizeof(idx)); if (cinf) { /* @@ -232,14 +239,11 @@ cleanup: return rv; } -static certinfo *stapling_get_certinfo(server_rec *s, X509 *x, 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; - UCHAR idx[SHA_DIGEST_LENGTH]; - if (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) @@ -777,6 +781,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. * @@ -791,13 +812,14 @@ static int stapling_cb(SSL *ssl, void *arg) SSLSrvConfigRec *sc = mySrvConfig(s); SSLConnRec *sslconn = myConnConfig(conn); modssl_ctx_t *mctx = myCtxConfig(sslconn, sc); + UCHAR idx[SHA_DIGEST_LENGTH]; + ocsp_resp resp; certinfo *cinf = NULL; OCSP_RESPONSE *rsp = NULL; int rv; BOOL ok = TRUE; X509 *x; - unsigned char *rspder = NULL; - int rspderlen; + int rspderlen, provided = 0; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01951) "stapling_cb: OCSP Stapling callback called"); @@ -807,12 +829,25 @@ static int stapling_cb(SSL *ssl, void *arg) return SSL_TLSEXT_ERR_NOACK; } - if (ssl_run_get_stapling_status(&rspder, &rspderlen, conn, s, x) == APR_SUCCESS) { + 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 (rspder == NULL || rspderlen <= 0) { + if (resp.data == NULL || resp.len == 0) { return SSL_TLSEXT_ERR_NOACK; } - SSL_set_tlsext_status_ocsp_resp(ssl, rspder, rspderlen); + SSL_set_tlsext_status_ocsp_resp(ssl, resp.data, (int)resp.len); return SSL_TLSEXT_ERR_OK; } @@ -822,7 +857,7 @@ static int stapling_cb(SSL *ssl, void *arg) return SSL_TLSEXT_ERR_NOACK; } - if ((cinf = stapling_get_certinfo(s, x, mctx, ssl)) == NULL) { + if ((cinf = stapling_get_certinfo(s, idx, sizeof(idx), mctx, ssl)) == NULL) { return SSL_TLSEXT_ERR_NOACK; } diff --git a/server/Makefile.in b/server/Makefile.in index 1fa334467d0..81118775208 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -13,7 +13,7 @@ LTLIBRARY_SOURCES = \ mpm_common.c mpm_unix.c mpm_fdqueue.c \ util_charset.c util_cookies.c util_debug.c util_xml.c \ util_filter.c util_pcre.c util_regex.c exports.c \ - scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \ + scoreboard.c error_bucket.c protocol.c core.c request.c ssl.c provider.c \ eoc_bucket.c eor_bucket.c core_filters.c \ util_expr_parse.c util_expr_scan.c util_expr_eval.c diff --git a/server/core.c b/server/core.c index f3f6abc3adc..d135764fef2 100644 --- a/server/core.c +++ b/server/core.c @@ -38,6 +38,7 @@ #include "http_core.h" #include "http_protocol.h" /* For index_of_response(). Grump. */ #include "http_request.h" +#include "http_ssl.h" #include "http_vhost.h" #include "http_main.h" /* For the default_handler below... */ #include "http_log.h" @@ -5076,6 +5077,7 @@ static int core_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pte set_banner(pconf); ap_setup_make_content_type(pconf); ap_setup_auth_internal(ptemp); + ap_setup_ssl_optional_fns(pconf); if (!sys_privileges) { ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(00136) "Server MUST relinquish startup privileges before " diff --git a/server/ssl.c b/server/ssl.c new file mode 100644 index 00000000000..d62cfe01767 --- /dev/null +++ b/server/ssl.c @@ -0,0 +1,193 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * ssl.c --- routines for SSL/TLS server infrastructure. + * + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_buckets.h" +#include "apr_lib.h" +#include "apr_signal.h" +#include "apr_strmatch.h" + +#define APR_WANT_STDIO /* for sscanf */ +#define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC +#include "apr_want.h" + +#include "util_filter.h" +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_main.h" +#include "http_ssl.h" +#include "http_log.h" /* For errors detected in basic auth common + * support code... */ +#include "mod_core.h" + + +#if APR_HAVE_STDARG_H +#include +#endif +#if APR_HAVE_UNISTD_H +#include +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +APR_HOOK_STRUCT( + APR_HOOK_LINK(ssl_conn_is_ssl) + APR_HOOK_LINK(ssl_var_lookup) + APR_HOOK_LINK(ssl_add_cert_files) + APR_HOOK_LINK(ssl_add_fallback_cert_files) + APR_HOOK_LINK(ssl_answer_challenge) + APR_HOOK_LINK(ssl_ocsp_prime_hook) + APR_HOOK_LINK(ssl_ocsp_get_resp_hook) +) + +APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); +static APR_OPTIONAL_FN_TYPE(ssl_is_https) *module_ssl_is_https; + +static int ssl_is_https(conn_rec *c) +{ + /* Someone retrieved the optional function., not knowing about the + * new API. We redirect them to what they should have invoked. */ + return ap_ssl_conn_is_ssl(c); +} + +AP_DECLARE(int) ap_ssl_conn_is_ssl(conn_rec *c) +{ + int r = (ap_run_ssl_conn_is_ssl(c) == OK); + if (r == 0 && module_ssl_is_https) { + r = module_ssl_is_https(c); + } + return r; +} + +APR_DECLARE_OPTIONAL_FN(const char *, ssl_var_lookup, + (apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, + const char *name)); +static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *module_ssl_var_lookup; + +static const char *ssl_var_lookup(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, + const char *name) +{ + /* Someone retrieved the optional function., not knowing about the + * new API. We redirect them to what they should have invoked. */ + return ap_ssl_var_lookup(p, s, c, r, name); +} + +AP_DECLARE(const char *) ap_ssl_var_lookup(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, + const char *name) +{ + const char *val = ap_run_ssl_var_lookup(p, s, c, r, name); + if (val == NULL && module_ssl_var_lookup) { + val = module_ssl_var_lookup(p, s, c, r, name); + } + return val; +} + +AP_DECLARE(void) ap_setup_ssl_optional_fns(apr_pool_t *pool) +{ + /* Run as core's very early 'post config' hook, check for any already + * installed optional functions related to SSL and save them. Install + * our own instances that invoke the new hooks. */ + APR_OPTIONAL_FN_TYPE(ssl_is_https) *fn_is_https; + APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *fn_ssl_var_lookup; + + fn_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); + module_ssl_is_https = (fn_is_https + && fn_is_https != ssl_is_https)? fn_is_https : NULL; + APR_REGISTER_OPTIONAL_FN(ssl_is_https); + + fn_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); + module_ssl_var_lookup = (fn_ssl_var_lookup + && fn_ssl_var_lookup != ssl_var_lookup)? fn_ssl_var_lookup : NULL; + APR_REGISTER_OPTIONAL_FN(ssl_var_lookup); +} + +AP_DECLARE(apr_status_t) ap_ssl_add_cert_files(server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files) +{ + int rv = ap_run_ssl_add_cert_files(s, p, cert_files, key_files); + return (rv == OK || rv == DECLINED)? APR_SUCCESS : APR_EGENERAL; +} + +AP_DECLARE(apr_status_t) ap_ssl_add_fallback_cert_files(server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files) +{ + int rv = ap_run_ssl_add_fallback_cert_files(s, p, cert_files, key_files); + return (rv == OK || rv == DECLINED)? APR_SUCCESS : APR_EGENERAL; +} + +AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name, + const char **pcert_pem, const char **pkey_pem) +{ + return (ap_run_ssl_answer_challenge(c, server_name, pcert_pem, pkey_pem) == OK); +} + +AP_DECLARE(apr_status_t) ap_ssl_ocsp_prime(server_rec *s, apr_pool_t *p, + const char *id, apr_size_t id_len, + const char *pem) +{ + int rv = ap_run_ssl_ocsp_prime_hook(s, p, id, id_len, pem); + return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL); +} + +AP_DECLARE(apr_status_t) ap_ssl_ocsp_get_resp(server_rec *s, conn_rec *c, + const char *id, apr_size_t id_len, + ap_ssl_ocsp_copy_resp *cb, void *userdata) +{ + int rv = ap_run_ssl_ocsp_get_resp_hook(s, c, id, id_len, cb, userdata); + return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL); +} + +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_conn_is_ssl, + (conn_rec *c), (c), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,ssl_var_lookup, + (apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name), + (p, s, c, r, name), NULL) +AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_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) +AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_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) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_answer_challenge, + (conn_rec *c, const char *server_name, const char **pcert_pem, const char **pkey_pem), + (c, server_name, pcert_pem, pkey_pem), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_prime_hook, + (server_rec *s, apr_pool_t *p, const char *id, apr_size_t id_len, const char *pem), + (s, p, id, id_len, pem), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_get_resp_hook, + (server_rec *s, conn_rec *c, const char *id, apr_size_t id_len, + ap_ssl_ocsp_copy_resp *cb, void *userdata), + (s, c, id, id_len, cb, userdata), DECLINED) diff --git a/server/util_expr_eval.c b/server/util_expr_eval.c index 85c9c11e275..2e031d0c7be 100644 --- a/server/util_expr_eval.c +++ b/server/util_expr_eval.c @@ -23,6 +23,7 @@ #include "http_core.h" #include "http_protocol.h" #include "http_request.h" +#include "http_ssl.h" #include "ap_provider.h" #include "util_expr_private.h" #include "util_md5.h" @@ -1256,9 +1257,6 @@ static int op_file_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *a } -APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); -static APR_OPTIONAL_FN_TYPE(ssl_is_https) *is_https = NULL; - APR_DECLARE_OPTIONAL_FN(int, http2_is_h2, (conn_rec *)); static APR_OPTIONAL_FN_TYPE(http2_is_h2) *is_http2 = NULL; @@ -1280,7 +1278,7 @@ static const char *conn_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) switch (index) { case 0: - if (is_https && is_https(c)) + if (ap_ssl_conn_is_ssl(c)) return "on"; else return "off"; @@ -1806,10 +1804,7 @@ static int expr_lookup_not_found(ap_expr_lookup_parms *parms) static int ap_expr_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { - is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); is_http2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2); - apr_pool_cleanup_register(pconf, &is_https, ap_pool_cleanup_set_null, - apr_pool_cleanup_null); return OK; }