You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2021/03/08 18:05:51 UTC

svn commit: r1887337 [1/3] - in /httpd/httpd/trunk: ./ modules/md/

Author: icing
Date: Mon Mar  8 18:05:50 2021
New Revision: 1887337

URL: http://svn.apache.org/viewvc?rev=1887337&view=rev
Log:
  *) 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 exist 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.


Added:
    httpd/httpd/trunk/modules/md/md_event.c
    httpd/httpd/trunk/modules/md/md_event.h
Removed:
    httpd/httpd/trunk/modules/md/md_acmev1_drive.c
    httpd/httpd/trunk/modules/md/md_acmev1_drive.h
Modified:
    httpd/httpd/trunk/CHANGES
    httpd/httpd/trunk/CMakeLists.txt
    httpd/httpd/trunk/modules/md/config2.m4
    httpd/httpd/trunk/modules/md/md.h
    httpd/httpd/trunk/modules/md/md_acme.c
    httpd/httpd/trunk/modules/md/md_acme.h
    httpd/httpd/trunk/modules/md/md_acme_acct.c
    httpd/httpd/trunk/modules/md/md_acme_authz.c
    httpd/httpd/trunk/modules/md/md_acme_authz.h
    httpd/httpd/trunk/modules/md/md_acme_drive.c
    httpd/httpd/trunk/modules/md/md_acme_drive.h
    httpd/httpd/trunk/modules/md/md_acme_order.c
    httpd/httpd/trunk/modules/md/md_acmev2_drive.c
    httpd/httpd/trunk/modules/md/md_core.c
    httpd/httpd/trunk/modules/md/md_crypt.c
    httpd/httpd/trunk/modules/md/md_crypt.h
    httpd/httpd/trunk/modules/md/md_curl.c
    httpd/httpd/trunk/modules/md/md_http.c
    httpd/httpd/trunk/modules/md/md_http.h
    httpd/httpd/trunk/modules/md/md_json.c
    httpd/httpd/trunk/modules/md/md_json.h
    httpd/httpd/trunk/modules/md/md_ocsp.c
    httpd/httpd/trunk/modules/md/md_ocsp.h
    httpd/httpd/trunk/modules/md/md_reg.c
    httpd/httpd/trunk/modules/md/md_reg.h
    httpd/httpd/trunk/modules/md/md_result.c
    httpd/httpd/trunk/modules/md/md_result.h
    httpd/httpd/trunk/modules/md/md_status.c
    httpd/httpd/trunk/modules/md/md_status.h
    httpd/httpd/trunk/modules/md/md_store.c
    httpd/httpd/trunk/modules/md/md_store.h
    httpd/httpd/trunk/modules/md/md_store_fs.c
    httpd/httpd/trunk/modules/md/md_time.c
    httpd/httpd/trunk/modules/md/md_time.h
    httpd/httpd/trunk/modules/md/md_util.c
    httpd/httpd/trunk/modules/md/md_util.h
    httpd/httpd/trunk/modules/md/md_version.h
    httpd/httpd/trunk/modules/md/mod_md.c
    httpd/httpd/trunk/modules/md/mod_md.dsp
    httpd/httpd/trunk/modules/md/mod_md.h
    httpd/httpd/trunk/modules/md/mod_md_config.c
    httpd/httpd/trunk/modules/md/mod_md_config.h
    httpd/httpd/trunk/modules/md/mod_md_drive.c
    httpd/httpd/trunk/modules/md/mod_md_status.c

Modified: httpd/httpd/trunk/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CHANGES?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/CHANGES [utf-8] (original)
+++ httpd/httpd/trunk/CHANGES [utf-8] Mon Mar  8 18:05:50 2021
@@ -1,6 +1,67 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.1
 
+ 
+  *) 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 exist 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]
+
   *) mod_session: Improve session parsing.  [Yann Yalvic]
 
   *) mod_proxy_hcheck: Don't pile up health checks if the previous one did

Modified: httpd/httpd/trunk/CMakeLists.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CMakeLists.txt?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/CMakeLists.txt (original)
+++ httpd/httpd/trunk/CMakeLists.txt Mon Mar  8 18:05:50 2021
@@ -504,7 +504,7 @@ SET(mod_md_extra_libs                ${O
 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
@@ -515,7 +515,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)

Modified: httpd/httpd/trunk/modules/md/config2.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/config2.m4?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/config2.m4 (original)
+++ httpd/httpd/trunk/modules/md/config2.m4 Mon Mar  8 18:05:50 2021
@@ -140,12 +140,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

Modified: httpd/httpd/trunk/modules/md/md.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md.h?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md.h (original)
+++ httpd/httpd/trunk/modules/md/md.h Mon Mar  8 18:05:50 2021
@@ -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,10 +82,10 @@ 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) */
@@ -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,6 +158,7 @@ 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"

Modified: httpd/httpd/trunk/modules/md/md_acme.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme.c (original)
+++ httpd/httpd/trunk/modules/md/md_acme.c Mon Mar  8 18:05:50 2021
@@ -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(c
     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 *
 
 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
     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,
 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_a
 /**************************************************************************************************/
 /* 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_
         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_
          * 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_
         }
         
         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
             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
     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 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 **
     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,21 +682,7 @@ static apr_status_t update_directory(con
     }
     
     /* 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);
@@ -729,7 +691,10 @@ static apr_status_t update_directory(con
         /* 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. */
+         * 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) {
@@ -740,7 +705,19 @@ static apr_status_t update_directory(con
         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>. "
@@ -770,6 +747,7 @@ apr_status_t md_acme_setup(md_acme_t *ac
     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);
     

Modified: httpd/httpd/trunk/modules/md/md_acme.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme.h?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme.h (original)
+++ httpd/httpd/trunk/modules/md/md_acme.h Mon Mar  8 18:05:50 2021
@@ -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 *po
  * @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_ac
 
 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 */

Modified: httpd/httpd/trunk/modules/md/md_acme_acct.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme_acct.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme_acct.c (original)
+++ httpd/httpd/trunk/modules/md/md_acme_acct.c Mon Mar  8 18:05:50 2021
@@ -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_
     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_
 
     (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
     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);
 } 

Modified: httpd/httpd/trunk/modules/md/md_acme_authz.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme_authz.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme_authz.c (original)
+++ httpd/httpd/trunk/modules/md/md_acme_authz.c Mon Mar  8 18:05:50 2021
@@ -76,57 +76,6 @@ static void authz_req_ctx_init(authz_req
     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_jso
 
 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_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
     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_setu
     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_
     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_
     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(
     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 *bato
 }
 
 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_ac
                 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(stru
         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;
             }

Modified: httpd/httpd/trunk/modules/md/md_acme_authz.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme_authz.h?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme_authz.h (original)
+++ httpd/httpd/trunk/modules/md/md_acme_authz.h Mon Mar  8 18:05:50 2021
@@ -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 */

Modified: httpd/httpd/trunk/modules/md/md_acme_drive.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme_drive.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme_drive.c (original)
+++ httpd/httpd/trunk/modules/md/md_acme_drive.c Mon Mar  8 18:05:50 2021
@@ -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_p
         /* 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_
 {
     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,7 @@ static apr_status_t add_http_certs(apr_a
     const char *ct;
     
     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)) {
         /* this looks like a root cert and we do not want those in our chain */
         goto out; 
@@ -225,10 +225,10 @@ static apr_status_t on_add_cert(md_acme_
     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 +276,6 @@ static apr_status_t on_init_csr_req(md_a
     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 +305,11 @@ static apr_status_t csr_req(md_acme_t *a
     }
     
     /* 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 +326,7 @@ static apr_status_t csr_req(md_acme_t *a
 /**
  * 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 +337,39 @@ static apr_status_t csr_req(md_acme_t *a
  * - 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 +387,13 @@ static apr_status_t on_add_chain(md_acme
     
     (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 +407,26 @@ static apr_status_t get_chain(void *bato
     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 +440,7 @@ static apr_status_t get_chain(void *bato
         }
     }
     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 +450,16 @@ static apr_status_t ad_chain_retrieve(md
     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 +491,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 +504,15 @@ static apr_status_t acme_driver_preload_
     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 +584,7 @@ static apr_status_t acme_driver_init(md_
                              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 +598,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 +686,15 @@ static apr_status_t acme_renew(md_proto_
         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 +722,63 @@ static apr_status_t acme_renew(md_proto_
         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 +788,44 @@ static apr_status_t acme_renew(md_proto_
 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 +862,13 @@ static apr_status_t acme_preload(md_prot
                                  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 +883,22 @@ static apr_status_t acme_preload(md_prot
         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 +942,7 @@ static apr_status_t acme_preload(md_prot
             }
         }
         
-        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 +959,15 @@ static apr_status_t acme_preload(md_prot
         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:

Modified: httpd/httpd/trunk/modules/md/md_acme_drive.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme_drive.h?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme_drive.h (original)
+++ httpd/httpd/trunk/modules/md/md_acme_drive.h Mon Mar  8 18:05:50 2021
@@ -18,23 +18,25 @@
 
 struct apr_array_header_t;
 struct md_acme_order_t;
+struct md_credentials_t;
 struct md_result_t;
 
 typedef struct md_acme_driver_t {
     md_proto_driver_t *driver;
     void *sub_driver;
     
-    int complete;
-
-    md_pkey_t *privkey;              /* the new private key */
-    apr_array_header_t *certs;       /* the certifiacte chain, starting with the new one */
-    const char *next_up_link;        /* where the next chain cert is */
-    
     md_acme_t *acme;
     md_t *md;
     struct apr_array_header_t *domains;
-    
     apr_array_header_t *ca_challenges;
+    
+    int complete;
+    apr_array_header_t *creds;       /* the new md_credentials_t */
+
+    struct md_credentials_t *cred;   /* credentials currently being processed */ 
+    const char *chain_up_link;       /* Link header "up" from last chain retrieval,
+                                        needs to be followed */
+
     struct md_acme_order_t *order;
     apr_interval_time_t authz_monitor_timeout;
     
@@ -45,8 +47,8 @@ typedef struct md_acme_driver_t {
 
 apr_status_t md_acme_drive_set_acct(struct md_proto_driver_t *d, 
                                     struct md_result_t *result);
-apr_status_t md_acme_drive_setup_certificate(struct md_proto_driver_t *d, 
-                                             struct md_result_t *result);
+apr_status_t md_acme_drive_setup_cred_chain(struct md_proto_driver_t *d, 
+                                            struct md_result_t *result);
 apr_status_t md_acme_drive_cert_poll(struct md_proto_driver_t *d, int only_once);
 
 #endif /* md_acme_drive_h */

Modified: httpd/httpd/trunk/modules/md/md_acme_order.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme_order.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme_order.c (original)
+++ httpd/httpd/trunk/modules/md/md_acme_order.c Mon Mar  8 18:05:50 2021
@@ -241,7 +241,7 @@ static apr_status_t p_purge(void *baton,
             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_challen
         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_challen
                 
             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;