]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
Merge of [r1886840, r1887085, r1887087, r1887134, r1887151, r1887152,
authorStefan Eissing <icing@apache.org>
Wed, 12 May 2021 10:14:42 +0000 (10:14 +0000)
committerStefan Eissing <icing@apache.org>
Wed, 12 May 2021 10:14:42 +0000 (10:14 +0000)
       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

78 files changed:
.gitignore [new file with mode: 0644]
CHANGES
CMakeLists.txt
build/nw_export.inc
include/ap_mmn.h
include/http_protocol.h
include/http_ssl.h [new file with mode: 0644]
libhttpd.dsp
modules/filters/mod_deflate.c
modules/http2/h2_alt_svc.c
modules/http2/h2_conn_io.c
modules/http2/h2_h2.c
modules/http2/h2_h2.h
modules/http2/h2_switch.c
modules/lua/mod_lua.c
modules/lua/mod_lua.h
modules/mappers/mod_rewrite.c
modules/md/config2.m4
modules/md/md.h
modules/md/md_acme.c
modules/md/md_acme.h
modules/md/md_acme_acct.c
modules/md/md_acme_authz.c
modules/md/md_acme_authz.h
modules/md/md_acme_drive.c
modules/md/md_acme_drive.h
modules/md/md_acme_order.c
modules/md/md_acmev1_drive.c [deleted file]
modules/md/md_acmev1_drive.h [deleted file]
modules/md/md_acmev2_drive.c
modules/md/md_core.c
modules/md/md_crypt.c
modules/md/md_crypt.h
modules/md/md_curl.c
modules/md/md_event.c [new file with mode: 0644]
modules/md/md_event.h [new file with mode: 0644]
modules/md/md_http.c
modules/md/md_http.h
modules/md/md_json.c
modules/md/md_json.h
modules/md/md_ocsp.c
modules/md/md_ocsp.h
modules/md/md_reg.c
modules/md/md_reg.h
modules/md/md_result.c
modules/md/md_result.h
modules/md/md_status.c
modules/md/md_status.h
modules/md/md_store.c
modules/md/md_store.h
modules/md/md_store_fs.c
modules/md/md_time.c
modules/md/md_time.h
modules/md/md_util.c
modules/md/md_util.h
modules/md/md_version.h
modules/md/mod_md.c
modules/md/mod_md.dsp
modules/md/mod_md.h
modules/md/mod_md_config.c
modules/md/mod_md_config.h
modules/md/mod_md_drive.c
modules/md/mod_md_ocsp.c
modules/md/mod_md_ocsp.h
modules/md/mod_md_status.c
modules/metadata/mod_headers.c
modules/proxy/mod_proxy.c
modules/proxy/mod_proxy.h
modules/ssl/ssl_engine_init.c
modules/ssl/ssl_engine_kernel.c
modules/ssl/ssl_private.h
modules/ssl/ssl_util_ssl.c
modules/ssl/ssl_util_ssl.h
modules/ssl/ssl_util_stapling.c
server/Makefile.in
server/core.c
server/ssl.c [new file with mode: 0644]
server/util_expr_eval.c

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..2970e54
--- /dev/null
@@ -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 2ce1bd582a59d582c54bace6695d06c567e8c512..5dbf3af2807313cdecc6c5b4cd127628d52e921f 100644 (file)
--- 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 (<https://github.com/root360-AndreasUlm>) 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:<type>:<domain>```, 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 <https://bz.apache.org/bugzilla/show_bug.cgi?id=64297>.
+     - 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
index a7b18419ddfb08806d13a900bea7cf14809d46c4..dfef19e8e480712d7ce112489b620ecc4b7dafec 100644 (file)
@@ -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
index a97a18191b6130e6f031fb74f9a77c3913d8899a..f0a26f567e582abd5347ca13ab8e13a4237062e1 100644 (file)
@@ -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"
index a204e438bd5c5a59858f0212cb644605f26c6f7c..7a6c7c68e0613dae7e70bcd65817d4e707da1ca5 100644 (file)
  *                           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" */
 #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
index 0047f4762b314fcbf3e3a5e58c58700c631d791c..c01c8a67e97a0ba2dc2cf69beef94e3f45b6b2b1 100644 (file)
@@ -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 (file)
index 0000000..556a58b
--- /dev/null
@@ -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 */
+/** @} */
index a1f7e92d4e3e114e394523972b2508eb9f63d82b..579c5f86327eb79ded1472e1b9e292cf5966871c 100644 (file)
@@ -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
index 99aa0a5ed35d744b391fb0d75af9189af09a8bc5..46148cd1ea05acdacd8ca3f9d7685498d1ffb95a 100644 (file)
 #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;
 }
 
index 2c3253c2cca0161ae3836264e00d1c6c860f5507..c22f7eba836d883a1aef1310ead678d00ec60022 100644 (file)
@@ -19,6 +19,7 @@
 #include <http_core.h>
 #include <http_connection.h>
 #include <http_protocol.h>
+#include <http_ssl.h>
 #include <http_log.h>
 
 #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);
index 5f17e85951a19bd8611b355c396909275d84390c..d0e099b421f8150cbe20c8421b16052121aaca74 100644 (file)
@@ -22,7 +22,9 @@
 #include <http_core.h>
 #include <http_log.h>
 #include <http_connection.h>
+#include <http_protocol.h>
 #include <http_request.h>
+#include <http_ssl.h>
 
 #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);
 
index c05b93093d0ea7fc96081f18b56a0716fc582115..1f0a5df7d3893c7b0fed35a7bbceaa1e14f7edec 100644 (file)
 #include <http_connection.h>
 #include <http_protocol.h>
 #include <http_request.h>
+#include <http_ssl.h>
 #include <http_log.h>
 
-#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,
index 339e898a108d839e674708a251ce3e8324cf3454..8cfb9864fe89afaccdd0d73b3aff3ed5704350b4 100644 (file)
@@ -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);
index 9ec658b8e173da297746c764bfc0f22d331f2b93..eb050150c9fe8576384bd44cd2669e2404375df5 100644 (file)
@@ -25,6 +25,7 @@
 #include <http_config.h>
 #include <http_connection.h>
 #include <http_protocol.h>
+#include <http_ssl.h>
 #include <http_log.h>
 
 #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;
index 53612f9bb7d9c1d0288b37007da95bff3ba717e8..23114304b356297ddd88ea6a76e10ab0cd0b3ce6 100644 (file)
@@ -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;
 
index 46395da784108ff4a67016796215867d15deced3..33807fbdb56531f311f1630f452d7eea48212de1 100644 (file)
@@ -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"
index 989244d43e036bd356272521809913e72a1c6cbc..102a93c4335a75d4eb744e0285ab5cf1bf73f95c 100644 (file)
 #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;
 }
 
index 9184a3995def896f298e0c8ca508e77beea71c00..bcce501633a26f408244a82caacd3e1974c0c60d 100644 (file)
@@ -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
index 182d00b484d17e351e0e9b0588da0b2260db1103..78b7ef863be90c4c7a54db89725eda08539733d1 100644 (file)
@@ -17,6 +17,8 @@
 #ifndef mod_md_md_h
 #define mod_md_md_h
 
+#include <apr_time.h>
+
 #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"
 
index c085ba351846e0e1eda725310571ba83e69e2770..52fab0af1eadf467819e72a7ffbf294ad91a7915 100644 (file)
@@ -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 <https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.6.3>
          * and <https://mailarchive.ietf.org/arch/msg/acme/sotffSQ0OWV-qQJodLwWYWcEVKI>
          * and <https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380>
@@ -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);
     
index f6af75fb049ccf9c35b31613b5fc0eafbccedbc3..004e7867ea476ba3b5ed4933177185c3de288c06 100644 (file)
@@ -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 */
index 98443d2fd8d3fe088b61426994407ede877bc404..3c0f4536059d5e9824601a508ecaab5faa456de5 100644 (file)
@@ -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);
 } 
index b76a5a6ac6e089f179e189d76b7d6f3d4c0f96c9..883e319b48a56fda2e38bdc72daaf4d94ad35f93 100644 (file)
@@ -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, &notify_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, &notify_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;
             }
index fe1abe552839d04ea528171d2ac93ace472a638b..90193af14c5695780dc8cd550e774143af4311a0 100644 (file)
@@ -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 */
index b88da757c510c11cbd441b85035d031bb8a87b69..4bdaf6bf65243c42421a5466cc22ad85a918d9a1 100644 (file)
@@ -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:
index 5111408c8ccf03e506895e5d25ed1fa2e7a85e4f..88761fab95dbbdb060912758f0d8027dd4009c43 100644 (file)
 
 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 */
index 974334a79c98ae4a0a9b1dcd41ea7f7992763966..5dde962afad0f1aa6afee3bddbd5c12ed43c7e5d 100644 (file)
@@ -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 (file)
index 027d494..0000000
+++ /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 <assert.h>
-#include <stdlib.h>
-
-#include <apr_lib.h>
-#include <apr_strings.h>
-#include <apr_buckets.h>
-#include <apr_hash.h>
-#include <apr_uri.h>
-
-#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 (file)
index 9dfa729..0000000
+++ /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 */
index 342e60a90f523129f12b09d0f237735e21a3eeb5..40add6f56559a0e8970ac695dc72743c39e790ba 100644 (file)
@@ -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);
index 5d59b3d5931d68f8fc88ff07776c2f256517ab60..9b696e2b995274d7e09b90f998606902a6cdb342 100644 (file)
 
 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;
index 55155975de46ce6024b637288eb8bf8e6ed88e6a..5c4d9f047e13b5a5a6b93fcd47b59c9d2f6d66ad 100644 (file)
@@ -22,6 +22,8 @@
 #include <apr_buckets.h>
 #include <apr_file_io.h>
 #include <apr_strings.h>
+#include <httpd.h>
+#include <http_core.h>
 
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -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;
     }
index 2fba40912b9c40d5f517cf664f94b080151a98db..cd1db294419f27174e8a6c7c9d2e6e081fe4c862 100644 (file)
@@ -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 */
 
index ed40e7604e83f46d23a1a69165b6431d9969781a..e93bea4d42976a3f389a4fc6f68caa5a8e282536 100644 (file)
@@ -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 (file)
index 0000000..c731d55
--- /dev/null
@@ -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 <assert.h>
+#include <apr_optional.h>
+#include <apr_strings.h>
+
+#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 (file)
index 0000000..e66c3c2
--- /dev/null
@@ -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 */
index 9c2790cd66a99f014bf4712d06f96226190d0e91..53e4f89d99ffc419cddb655ae7f5f28674ad701f 100644 (file)
@@ -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;
 }
index 78668b31d2a2fb9a06c9a55fb5657b4534f2c5ef..e24de0311399cbd058c1c538cad971ec97e2f6ea 100644 (file)
@@ -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 */
index 73120d78ed20db863d900c3c3825562543264c87..c81fb0f786e9a9f0992f4a0b82039b9d80ad95a3 100644 (file)
@@ -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;
index 95a828b3514cb2df8e0740ec1cc2f8260d2d430c..50b882804d16b6c58e9c42f1aade2cd35b4c1792 100644 (file)
@@ -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, ...);
index dc95393249ed49717b2a4c43df1c74137867fe03..d3f0e1577e5d94a6a0fb1dde29d1da06e20ff41f 100644 (file)
@@ -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(&reg->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);
 }
index 9f0c0fd0351afde9fd32de5680a8603d80d7aa5e..d6ee0f1d7dd20a473d8ba3c652ef6482f229c82d 100644 (file)
@@ -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 */
index 9c57c29046b78a0b8eaab53a11f1d355be38e79b..7d61f0c5d457ae8865e2acf1434facb7d7b2b5cf 100644 (file)
@@ -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(&reg->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF); 
     md_timeslice_create(&reg->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);
 }
index b656d5c0a3d6cbf435e72c114ebc08832dde2553..46034b0fe68d954e15504e27f992c79a99cf623e 100644 (file)
@@ -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 */
index 29996e94ca65e9b526b04d487f5433a8e8ccd971..64a2f70bac8c0a81f940cf3e734962be78725d24 100644 (file)
@@ -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;
+}
index 58e903e08a05e4e95c4c86c9d0b50cf62d076252..e83bdd22236f228574d2028618b2b32eb9bbc79f 100644 (file)
@@ -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 */
index 3c3d801400ba5adc30dbb2cb103de271a764ff08..ecc35c66d094445bbf9d03f19ed1d6bbeb371d33 100644 (file)
@@ -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;
-}
index 3b25dbbff9c6fd77cfe2ae6077b5c9715d83ffbf..cd358b0e8e1affcb5ae06ba2539a4904d5756248 100644 (file)
@@ -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 */
index ad5bb28cf2d72a3430adf6af3a8bb311bee1e1d5..29f3632d92242ccc2d801171e6f6484fc9a13551 100644 (file)
@@ -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 {
index dfe9f32d6deb89664826637f597c9a0e6ba018c6..e252c27909917231208d779806dcf5d5cd785cad 100644 (file)
@@ -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 */
 
index 6553fdd706f904fa23e719058c6f4fb17cf867c2..7ba822a4e2571f2be3b273b56a039381e17c9a2d 100644 (file)
@@ -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))) {
index 8076d5be468a272b52ec2ef316d07bdbfea69118..268ca83c1b69d9597b29cdc2a9118c5e9671a1a8 100644 (file)
@@ -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;
+}
index 88e28b20cb8735b23dcf0ecfc6e0daf8fd76db33..92bd9d8aa97064c0317c444ec12802e8be2e376a 100644 (file)
@@ -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);
 
 /**
index 25830f2b92ddce37945f00ddac313212c33ed72d..34e891d1ce6d47a21f17e5d19e101f8fb758a424 100644 (file)
 #include <apr_tables.h>
 #include <apr_uri.h>
 
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#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.
+     */
+}
index 3d927564594829c62bba68d0eb05722320841e4d..67d0e5128a6599b89fc361ba5d369eeb40e86afa 100644 (file)
@@ -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 */
 
index 21f5de1c7377b37e6ecc232b4189c0b30cf4234f..909258c3f8670260a2c0278bbceb55a8ff92d325 100644 (file)
@@ -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"
 
index 55413f5b67c4db42743a6e4419e30692da0ef048..d8d44a47cbbc0dbe38c40e1873e0066280711113 100644 (file)
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #include <assert.h>
 #include <apr_optional.h>
 #include <apr_strings.h>
@@ -23,6 +23,7 @@
 #include <http_core.h>
 #include <http_protocol.h>
 #include <http_request.h>
+#include <http_ssl.h>
 #include <http_log.h>
 #include <http_vhost.h>
 #include <ap_listen.h>
@@ -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() */
 }
 
index d05b7a02919da3d6bac6a43486f6ce963795acb5..e141ffc10f79edc446789f60491530f4ebd52488 100644 (file)
@@ -145,10 +145,6 @@ SOURCE=./md_acme_order.c
 # End Source File\r
 # Begin Source File\r
 \r
-SOURCE=./md_acmev1_drive.c\r
-# End Source File\r
-# Begin Source File\r
-\r
 SOURCE=./md_acmev2_drive.c\r
 # End Source File\r
 # Begin Source File\r
@@ -169,6 +165,10 @@ SOURCE=./md_http.c
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=./md_event.c\r
+# End Source File\r
+# Begin Source File\r
+\r
 SOURCE=./md_json.c\r
 # End Source File\r
 # Begin Source File\r
index cf043c45306fdee1a70752dd4c0ad974a9b3fb49..39b62f853fd48a1268f20883cbad4e2e5f26612d 100644 (file)
@@ -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 */
index 66684213aa7821cbf66baac0a0382c9220a535c2..63b2e7ab8110e9fac1b7c9b0b6a56ddadfbd262b 100644 (file)
@@ -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)
 };
index 2be0f68d30399ad823a3d99f3cb580b8c89b264a..76423bd602ab4d4a43984e3473efa8dc51af32d6 100644 (file)
@@ -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 */
index 1191a5fc3dbcd12cc1e2846d7ba4b1ee18ac234c..942256f3cfd9aa49addec1e7d3f20980a2dae3ae 100644 (file)
@@ -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;
index 2a01d5a846729dfa572433aa1d985278c460d52d..0f646760258c583535519a7b0b983664726c77cf 100644 (file)
@@ -23,6 +23,7 @@
 #include <httpd.h>
 #include <http_core.h>
 #include <http_log.h>
+#include <http_ssl.h>
 
 #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. */
 
index ee58df678a7a746b412eb8cef81eb55c04060ff0..dcc6e49c5ad0be7cb945ee56bee0eaa123b78a00 100644 (file)
@@ -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.
  */
index cde13325c750e2c7e9410b0c5541571d1f37a9bc..cb0e2677f06d8534d3d2d2304f034ef18d00a611 100644 (file)
 
 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, 
+                           "<a href=\"%s%s\">%s[%s]</a><br>", 
+                           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, 
-                           "<a href=\"%s%s\">%s</a> ", 
-                           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, "<div style=\"max-width:400px;\">");
+    add_json_val(ctx, md_json_getj(mdj, info->key, NULL));
+    apr_brigade_puts(ctx->bb, NULL, NULL, "</div>");
+}
+
 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 },
index f7bda1552a3e12503f426f1431fcd663d9a718ad..ef812cd3edc4e60b22b86014635e9a17785b4f13 100644 (file)
 #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;
 }
 
index 1cc7b754fd2d3453f04e977dee2d07abff4ae5fc..0f787a6b45a9f6625dfc9eb1d3f25e31fc7f8154 100644 (file)
@@ -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);
 
index 1ba54cd49e109d7648086d2bee0ad206a775b048..56461707cd24e920fe84078a8221e23c60acb93f 100644 (file)
@@ -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"
index 0c3163f05f03095da7728173cda3531ff705902b..4da24eddcc5fa463348312ce8fd759975813bee5 100644 (file)
@@ -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;
index 29646b878b6f266614e655029cb63052d4870b94..b99dcf19d4a3e014ad1383c3e2bc2e8fd2702acb 100644 (file)
@@ -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);
index 560c2fadfc8ac7e5cd85c5e0c33e3cfedda09f01..a6fc7513a2ecb4b1904f79c74ccd56bd64c7de0e 100644 (file)
@@ -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. */
index 74088f5e298e83f58204087f52a818764ca23507..38079a9eaa86ded17d3f10b2bdfd5e9522a1126e 100644 (file)
@@ -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;
+}
index ec89185b1b0845c3e0dbfc4103588f019ceabbef..443c1b7ee73344257b9c685dd757b41b90291cda 100644 (file)
@@ -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__ */
 /** @} */
 
index 4ba071e463e028a16c9521cf558bb12239b51fd1..7bd16d412282e6ad30e1e243ab3db4b64d3b2bb2 100644 (file)
@@ -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;
     }
 
index 1fa334467d0877e014f6b0e29ca581d1ebe79724..81118775208bbe6e78225cc971e6786115d1e7eb 100644 (file)
@@ -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
 
index f3f6abc3adcf21415602957467d40d6e4345b68d..d135764fef2e13f6c2f7b9c7ce42d3e071471df5 100644 (file)
@@ -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 (file)
index 0000000..d62cfe0
--- /dev/null
@@ -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 <stdarg.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#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)
index 85c9c11e275aaa8706b9acf0b46514f4a84139d4..2e031d0c7be57e12147addeb9ba75aae953bbdfe 100644 (file)
@@ -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;
 }