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 [2/3] - in /httpd/httpd/trunk: ./ modules/md/

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

Modified: httpd/httpd/trunk/modules/md/md_core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_core.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_core.c (original)
+++ httpd/httpd/trunk/modules/md/md_core.c Mon Mar  8 18:05:50 2021
@@ -33,7 +33,10 @@
 
 int md_contains(const md_t *md, const char *domain, int case_sensitive)
 {
-   return md_array_str_index(md->domains, domain, 0, case_sensitive) >= 0;
+    if (md_array_str_index(md->domains, domain, 0, case_sensitive) >= 0) {
+        return 1;
+    }
+    return md_dns_domains_match(md->domains, domain);
 }
 
 const char *md_common_name(const md_t *md1, const md_t *md2)
@@ -207,6 +210,7 @@ md_t *md_copy(apr_pool_t *p, const md_t
             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 +227,7 @@ md_t *md_clone(apr_pool_t *p, const md_t
         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);
@@ -260,8 +264,8 @@ md_json_t *md_to_json(const md_t *md, ap
         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);
@@ -306,8 +310,8 @@ md_t *md_from_json(md_json_t *json, apr_
         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;

Modified: httpd/httpd/trunk/modules/md/md_crypt.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_crypt.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_crypt.c (original)
+++ httpd/httpd/trunk/modules/md/md_crypt.c Mon Mar  8 18:05:50 2021
@@ -35,6 +35,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 +191,10 @@ static int pem_passwd(char *buf, int siz
             size = (int)ctx->pass_len;
         }
         memcpy(buf, ctx->pass_phrase, (size_t)size);
+    } else {
+        return 0;
     }
-    return ctx->pass_len;
+    return size;
 }
 
 /**************************************************************************************************/
@@ -258,10 +261,93 @@ apr_time_t md_asn1_generalized_time_get(
     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 +362,12 @@ md_json_t *md_pkey_spec_to_json(const md
                     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 +376,27 @@ md_json_t *md_pkey_spec_to_json(const md
     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 +418,161 @@ md_pkey_spec_t *md_pkey_spec_from_json(s
                 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 +663,11 @@ static apr_status_t pkey_to_buffer(md_da
     }
     
     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 +700,24 @@ apr_status_t md_pkey_fsave(md_pkey_t *pk
     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 +743,125 @@ static apr_status_t gen_rsa(md_pkey_t **
     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 +870,8 @@ apr_status_t md_pkey_gen(md_pkey_t **ppk
             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 +1115,14 @@ apr_time_t md_cert_get_not_before(const
     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 +1195,12 @@ apr_status_t md_cert_get_alt_names(apr_a
     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 +1208,29 @@ apr_status_t md_cert_get_alt_names(apr_a
             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);
+                    ip = ASN1_STRING_get0_data(cval->d.iPAddress);
+                    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;
             }
@@ -964,6 +1380,7 @@ apr_status_t md_cert_read_http(md_cert_t
     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 +1401,8 @@ apr_status_t md_cert_read_http(md_cert_t
             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);
             }
         }
     }
@@ -1015,7 +1433,10 @@ apr_status_t md_cert_chain_read_http(str
         rv = APR_ENOENT;
         goto out;
     }
-    else if (!strcmp("application/pem-certificate-chain", ct)) {
+    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 */
         if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool))) {
             int added = 0;
             md_cert_t *cert;
@@ -1033,7 +1454,8 @@ apr_status_t md_cert_chain_read_http(str
                 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);
     }
     else if (!strcmp("application/pkix-cert", ct)) {
         md_cert_t *cert;
@@ -1211,23 +1633,10 @@ static apr_status_t sk_add_alt_names(STA
 #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 +1645,7 @@ static apr_status_t add_must_staple(STAC
     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 +1686,18 @@ apr_status_t md_cert_req_create(const ch
 
     /* 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 +1725,7 @@ apr_status_t md_cert_req_create(const ch
         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 +1796,7 @@ static apr_status_t mk_x509(X509 **px, m
         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 +1841,19 @@ apr_status_t md_cert_self_sign(md_cert_t
         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 +1890,7 @@ apr_status_t md_cert_make_tls_alpn_01(md
     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 +1905,7 @@ apr_status_t md_cert_make_tls_alpn_01(md
     }
 
     /* 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;
     }

Modified: httpd/httpd/trunk/modules/md/md_crypt.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_crypt.h?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_crypt.h (original)
+++ httpd/httpd/trunk/modules/md/md_crypt.h Mon Mar  8 18:05:50 2021
@@ -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
 
 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 */
 
@@ -142,6 +170,7 @@ int md_cert_covers_md(md_cert_t *cert, c
 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 +212,6 @@ apr_status_t md_cert_make_tls_alpn_01(md
 
 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 */
 

Modified: httpd/httpd/trunk/modules/md/md_curl.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_curl.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_curl.c (original)
+++ httpd/httpd/trunk/modules/md/md_curl.c Mon Mar  8 18:05:50 2021
@@ -244,22 +244,29 @@ static apr_status_t internals_setup(md_h
     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_h
         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_
 {
     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)

Added: httpd/httpd/trunk/modules/md/md_event.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_event.c?rev=1887337&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/md_event.c (added)
+++ httpd/httpd/trunk/modules/md/md_event.c Mon Mar  8 18:05:50 2021
@@ -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;
+    }
+}

Added: httpd/httpd/trunk/modules/md/md_event.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_event.h?rev=1887337&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/md_event.h (added)
+++ httpd/httpd/trunk/modules/md/md_event.h Mon Mar  8 18:05:50 2021
@@ -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 */

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

Modified: httpd/httpd/trunk/modules/md/md_http.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_http.h?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_http.h (original)
+++ httpd/httpd/trunk/modules/md/md_http.h Mon Mar  8 18:05:50 2021
@@ -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;
@@ -110,6 +111,13 @@ void md_http_set_stalling_default(md_htt
 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_ht
 /* 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 */

Modified: httpd/httpd/trunk/modules/md/md_json.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_json.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_json.c (original)
+++ httpd/httpd/trunk/modules/md/md_json.c Mon Mar  8 18:05:50 2021
@@ -832,6 +832,32 @@ int md_json_itera(md_json_itera_cb *cb,
     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 */
 
@@ -1159,8 +1185,13 @@ apr_status_t md_json_readf(md_json_t **p
 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;

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

Modified: httpd/httpd/trunk/modules/md/md_ocsp.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_ocsp.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_ocsp.c (original)
+++ httpd/httpd/trunk/modules/md/md_ocsp.c Mon Mar  8 18:05:50 2021
@@ -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"
@@ -745,16 +746,16 @@ static apr_status_t ostat_on_req_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");
+        md_event_holler("ocsp-errored", update->job->mdomain, update->job, update->result, update->p);
         goto leave;
     }
-    md_job_notify(update->job, "ocsp-renewed", update->result);
+    md_event_holler("ocsp-renewed", update->job->mdomain, update->job, update->result, update->p);
 
 leave:
     md_job_save(update->job, update->result, update->p);
@@ -892,7 +893,7 @@ leave:
     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;
@@ -1032,17 +1033,7 @@ void md_ocsp_get_status_all(md_json_t **
     *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);
 }

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

Modified: httpd/httpd/trunk/modules/md/md_reg.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_reg.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_reg.c (original)
+++ httpd/httpd/trunk/modules/md/md_reg.c Mon Mar  8 18:05:50 2021
@@ -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
 }
 
 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 **pr
     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,49 @@ static apr_status_t check_values(md_reg_
 
 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;
+    md_pkey_spec_t *spec;
+    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_pkeys_spec_count(md->pks); ++i) {
+        spec = md_pkeys_spec_get(md->pks, i);
+        if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, spec, 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(%s) does not cover all domains.",
+                              md->name, md_pkey_spec_name(spec));
+                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(%s) has it%s enabled.", 
+                              md->name, md_pkey_spec_name(spec), 
+                              md->must_staple? "" : " not", 
+                              !md->must_staple? "" : " not");
+                goto out;
+            }
+            state = MD_S_COMPLETE;
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: certificate(%s) is ok", 
+                          md->name, md_pkey_spec_name(spec));
         }
-        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(%s) is missing", md->name,
+                          md_pkey_spec_name(spec));
             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:    
@@ -465,10 +476,7 @@ static apr_status_t p_md_update(void *ba
     }
     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 +532,7 @@ static apr_status_t pubcert_load(void *b
     apr_array_header_t *certs;
     md_pubcert_t *pubcert, **ppubcert;
     const md_t *md;
+    md_pkey_spec_t *spec;
     const md_cert_t *cert;
     md_cert_state_t cert_state;
     md_store_group_t group;
@@ -532,12 +541,13 @@ static apr_status_t pubcert_load(void *b
     ppubcert = va_arg(ap, md_pubcert_t **);
     group = (md_store_group_t)va_arg(ap, int);
     md = va_arg(ap, const md_t *);
+    spec = va_arg(ap, md_pkey_spec_t *);
     
     if (md->cert_file) {
         rv = md_chain_fload(&certs, p, md->cert_file);
     }
     else {
-        rv = md_pubcert_load(reg->store, group, md->name, &certs, p);
+        rv = md_pubcert_load(reg->store, group, md->name, spec, &certs, p);
     }
     if (APR_SUCCESS != rv) goto leave;
             
@@ -561,21 +571,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, md_pkey_spec_t *spec, 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_pstrcat(p, md->name, "[", md_pkey_spec_name(spec), "]", NULL);
+    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, spec, 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,7 +599,7 @@ 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;
     
@@ -598,40 +609,73 @@ apr_status_t md_reg_get_cred_files(const
         *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;
+    md_pkey_spec_t *spec;
+    int i;
+    apr_time_t t, valid_until = 0;
+    apr_status_t rv;
+    
+    for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+        spec = md_pkeys_spec_get(md->pks, i);
+        rv = md_reg_get_pubcert(&pub, reg, md, spec, 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;
+    md_pkey_spec_t *spec;
+    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_pkeys_spec_count(md->pks); ++i) {
+        spec = md_pkeys_spec_get(md->pks, i);
+        rv = md_reg_get_pubcert(&pub, reg, md, spec, 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(%s) valid[%s] renewal[%s]", 
+                              md->name, md_pkey_spec_name(spec),  
+                              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 +691,32 @@ int md_reg_should_warn(md_reg_t *reg, co
     const md_pubcert_t *pub;
     const md_cert_t *cert;
     md_timeperiod_t certlife, warn;
+    md_pkey_spec_t *spec;
+    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_pkeys_spec_count(md->pks); ++i) {
+        spec = md_pkeys_spec_get(md->pks, i);
+        rv = md_reg_get_pubcert(&pub, reg, md, spec, 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(%s) life[%s] warn[%s]", 
+                              md->name, md_pkey_spec_name(spec),  
+                              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;
 }
@@ -880,7 +932,7 @@ apr_status_t md_reg_sync_finish(md_reg_t
             && !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)
@@ -984,13 +1036,14 @@ static apr_status_t run_init(void *baton
     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 +1180,7 @@ static apr_status_t run_load_staging(voi
     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 +1202,18 @@ apr_status_t md_reg_freeze_domains(md_re
     apr_status_t rv = APR_SUCCESS;
     md_t *md;
     const md_pubcert_t *pubcert;
-    int i;
+    md_pkey_spec_t *spec;
+    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_pkeys_spec_count(md->pks); ++j) {
+            spec = md_pkeys_spec_get(md->pks, j);
+            rv = md_reg_get_pubcert(&pubcert, reg, md, spec, reg->p);
+            if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) goto leave;
+        }
     }
     reg->domains_frozen = 1;
 leave:
@@ -1173,17 +1230,7 @@ void md_reg_set_warn_window_default(md_r
     *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);
 }

Modified: httpd/httpd/trunk/modules/md/md_reg.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_reg.h?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_reg.h (original)
+++ httpd/httpd/trunk/modules/md/md_reg.h Mon Mar  8 18:05:50 2021
@@ -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
  * 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, struct md_pkey_spec_t *spec, apr_pool_t *p);
 
 /**
  * Get the filenames of private key and pubcert of the MD - if they exist.
@@ -120,7 +121,7 @@ apr_status_t md_reg_get_pubcert(const md
  */
 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.
@@ -173,6 +174,12 @@ int md_reg_should_renew(md_reg_t *reg, c
 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 
  * current certiciate. If no certificate is present, this returns 0.
@@ -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_
 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 */

Modified: httpd/httpd/trunk/modules/md/md_result.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_result.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_result.c (original)
+++ httpd/httpd/trunk/modules/md/md_result.c Mon Mar  8 18:05:50 2021
@@ -260,3 +260,26 @@ void md_result_on_change(md_result_t *re
     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;
+}

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

Modified: httpd/httpd/trunk/modules/md/md_status.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_status.c?rev=1887337&r1=1887336&r2=1887337&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_status.c (original)
+++ httpd/httpd/trunk/modules/md/md_status.c Mon Mar  8 18:05:50 2021
@@ -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
     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)
-{
-    md_json_t *mdj, *jobj, *certj;
-    int renew;
-    const md_pubcert_t *pubcert;
-    const md_cert_t *cert = NULL;
+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 *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_pkeys_spec_count(md->pks); ++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,73 @@ static apr_status_t status_get_md_json(m
                 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_pkeys_spec_count(md->pks); ++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;
+    md_pkey_spec_t *spec;
+    int i;
+
+    mdj = md_to_json(md, p);
+    certs = apr_array_make(p, 5, sizeof(md_cert_t*));
+    for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+        spec = md_pkeys_spec_get(md->pks, i);
+        cert = NULL;
+        if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, spec, 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 +210,8 @@ static apr_status_t status_get_md_json(m
         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 +279,7 @@ static void md_job_from_json(md_job_t *j
     /*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 +301,7 @@ static void job_to_json(md_json_t *json,
     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 +492,24 @@ static void job_result_update(md_result_
     }
 }
 
+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 +524,8 @@ static void job_observation_start(md_job
     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 +542,36 @@ void md_job_start_run(md_job_t *job, md_
     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 +587,7 @@ void md_job_end_run(md_job_t *job, md_re
     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 +600,20 @@ void md_job_retry_at(md_job_t *job, apr_
 
 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;
-}