From c9f60810cf026f000acd77a97eaff31821d018a3 Mon Sep 17 00:00:00 2001
From: Stefan Eissing <icing@apache.org>
Date: Fri, 1 Sep 2017 12:11:38 +0000
Subject: [PATCH] On the trunk:

mod_md: v0.8.1 from github, new feats in CHANGES



git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1806939 13f79535-47bb-0310-9956-ffa450edef68
---
 CHANGES                       |  24 +++++-
 docs/manual/mod/mod_md.xml    |  34 ++++++++
 modules/md/md.h               |  15 +++-
 modules/md/md_acme.c          |  15 ++--
 modules/md/md_acme.h          |   7 +-
 modules/md/md_acme_acct.c     |  16 ++--
 modules/md/md_acme_acct.h     |   4 +
 modules/md/md_acme_authz.c    |  26 ++++---
 modules/md/md_acme_authz.h    |   5 +-
 modules/md/md_acme_drive.c    | 106 +++++++++++++++++--------
 modules/md/md_cmd_reg.c       |   6 +-
 modules/md/md_core.c          |  27 +++++--
 modules/md/md_crypt.c         | 122 ++++++++++++++++++++++-------
 modules/md/md_crypt.h         |  23 +++++-
 modules/md/md_curl.c          |   9 ++-
 modules/md/md_reg.c           |  88 +++++++++++----------
 modules/md/md_store.c         |  17 +++-
 modules/md/md_store.h         |   6 ++
 modules/md/md_store_fs.c      | 141 +++++++++++++++++++++++++---------
 modules/md/md_util.c          | 115 +++++++++++++--------------
 modules/md/md_version.h       |   4 +-
 modules/md/mod_md.c           | 100 ++++++++++++++----------
 modules/md/mod_md_config.c    | 120 ++++++++++++++++++++++++++---
 modules/md/mod_md_config.h    |   6 ++
 modules/md/mod_md_os.c        |   2 +-
 modules/md/mod_md_os.h        |   2 +-
 modules/ssl/ssl_engine_init.c |   5 +-
 27 files changed, 748 insertions(+), 297 deletions(-)

diff --git a/CHANGES b/CHANGES
index c018a9cb3b..e2133fab55 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,29 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.0
 
-  *) mod_md: v0.7.0:
+  *) mod_md: v0.8.1:
+     - New directive ```MDPrivateKeys``` to specify the type and parameter to private key generation.
+       Currently only 'RSA' is supported as type with an option number of bits >= 2048 as parameter.
+       Simple test cases for config handling added.
+     - Private RSA keys are now generated with 2048 bits by default. Use ```MDPrivateKeys``` for
+       higher security. 
+     - IMPORTANT: store format change. The following changes will be made to an existing md store on 
+       first start with a new version (be it by mod_md in the server or a run by a new 'a2md'):
+         - pkey.pem will be renamed to privkey.pem
+         - cert.pem and chain.pem will be concatenated to pubcert.pem. The former files will remain,
+           but no longer be used. They will disappear on next renewal.
+       ADVICE: If the current store data is vital to you, please make a backup first!
+     - Fixed test case clearing of store to keep key alive, enabling true random store key again.
+     - Removed pun "Something, like certbot" from the User-Agent request header. Refs issue #34
+     - Cleaned up reporting of missing/mismatched MDCertificateAgreement in the logs. This will
+       no longer trigger early retries.
+     - badNonce encounters are no longer reported as errors. Retries are attempted now silently.
+       Refs github issue #35
+     - new default MDRenewWindow. Instead of 14 days, the default is now a third before the end of
+       the certificates lifetime. For the usual 90 days of Let's Encrypt certificates, this makes
+       an effective renewal window of 30 days - as recommended by LE. Refs issue #30
+     - Enabled conversion warnings if supported by compiler, eliminated several signed/unsigned
+       warnings.
      - LIVE: the real Let's Encrypt CA is now live by default! If you need to experiment, configure
            MDCertificateAuthority https://acme-staging.api.letsencrypt.org/directory   
      - When existing, complete certificates are renewed, the activation of the new ones is
diff --git a/docs/manual/mod/mod_md.xml b/docs/manual/mod/mod_md.xml
index 7b084ce3a6..e78d9b03a0 100644
--- a/docs/manual/mod/mod_md.xml
+++ b/docs/manual/mod/mod_md.xml
@@ -341,6 +341,40 @@ MDPortMap 80:- 443:5002
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>MDPrivateKeys</name>
+        <description></description>
+        <syntax>MDPrivateKeys type [ params... ]</syntax>
+        <default>MDPrivateKeys RSA 2048</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                Defines what kind of private keys are generated for a managed domain and with
+                what parameters. The only supported type right now is 'RSA' and the only parameter
+                it takes is the number of bits used for the key.
+            </p><p>
+                The current (2017) recommendation is at least 2048 bits and a smaller number is
+                not accepted here. Higher numbers offer longer security, but are computationally more 
+                expensive, e.g. increase the load on your server. That might or might not be an
+                issue for you.
+            </p><p>
+                Other key types will be defined in the future.
+            </p>
+            <example><title>Example</title>
+                <highlight language="config">
+MDPrivateKeys RSA 3072
+                </highlight>
+            </example>
+            <p>
+                Please note that this setting only has an effect on new keys. Any existing
+                private key you have remains unaffected. Also, this only affects private keys
+                generated for certificates. ACME account keys are unaffected by this.
+            </p>
+        </usage>
+    </directivesynopsis>
+
     <directivesynopsis>
         <name>MDRenewWindow</name>
         <description></description>
diff --git a/modules/md/md.h b/modules/md/md.h
index d1a7b518df..f723309ba5 100644
--- a/modules/md/md.h
+++ b/modules/md/md.h
@@ -25,8 +25,10 @@ struct md_cert_t;
 struct md_pkey_t;
 struct md_store_t;
 struct md_srv_conf_t;
+struct md_pkey_spec_t;
 
 #define MD_TLSSNI01_DNS_SUFFIX     ".acme.invalid"
+#define MD_PKEY_RSA_BITS_DEF       2048U
 
 typedef enum {
     MD_S_UNKNOWN,                   /* MD has not been analysed yet */
@@ -34,6 +36,7 @@ typedef enum {
     MD_S_COMPLETE,                  /* MD has all necessary information, can go live */
     MD_S_EXPIRED,                   /* MD is complete, but credentials have expired */
     MD_S_ERROR,                     /* MD data is flawed, unable to be processed as is */ 
+    MD_S_MISSING,                   /* MD is missing config information, cannot proceed */
 } md_state_t;
 
 typedef enum {
@@ -70,7 +73,9 @@ struct md_t {
 
     int transitive;                 /* != 0 iff VirtualHost names/aliases are auto-added */
     int drive_mode;                 /* mode of obtaining credentials */
+    struct md_pkey_spec_t *pkey_spec;/* specification for generating new private keys */
     int must_staple;                /* certificates should set the OCSP Must Staple extension */
+    apr_interval_time_t renew_norm; /* if > 0, normalized cert lifetime */
     apr_interval_time_t renew_window;/* time before expiration that starts renewal */
     
     const char *ca_url;             /* url of CA certificate service */
@@ -91,6 +96,7 @@ struct md_t {
 
 #define MD_KEY_ACCOUNT          "account"
 #define MD_KEY_AGREEMENT        "agreement"
+#define MD_KEY_BITS             "bits"
 #define MD_KEY_CA               "ca"
 #define MD_KEY_CA_URL           "ca-url"
 #define MD_KEY_CERT             "cert"
@@ -112,8 +118,10 @@ struct md_t {
 #define MD_KEY_KEYAUTHZ         "keyAuthorization"
 #define MD_KEY_LOCATION         "location"
 #define MD_KEY_NAME             "name"
+#define MD_KEY_PKEY             "privkey"
 #define MD_KEY_PROTO            "proto"
 #define MD_KEY_REGISTRATION     "registration"
+#define MD_KEY_RENEW_NORM       "renew-norm"
 #define MD_KEY_RENEW_WINDOW     "renew-window"
 #define MD_KEY_RESOURCE         "resource"
 #define MD_KEY_STATE            "state"
@@ -129,7 +137,8 @@ struct md_t {
 #define MD_KEY_VERSION          "version"
 
 #define MD_FN_MD                "md.json"
-#define MD_FN_PKEY              "pkey.pem"
+#define MD_FN_PRIVKEY           "privkey.pem"
+#define MD_FN_PUBCERT           "pubcert.pem"
 #define MD_FN_CERT              "cert.pem"
 #define MD_FN_CHAIN             "chain.pem"
 #define MD_FN_HTTPD_JSON        "httpd.json"
@@ -230,9 +239,9 @@ md_t *md_from_json(struct md_json_t *json, apr_pool_t *p);
 
 typedef struct md_creds_t md_creds_t;
 struct md_creds_t {
+    struct md_pkey_t *privkey;
+    struct apr_array_header_t *pubcert;    /* complete md_cert* chain */
     struct md_cert_t *cert;
-    struct md_pkey_t *pkey;
-    struct apr_array_header_t *chain;      /* list of md_cert* */
     int expired;
 };
 
diff --git a/modules/md/md_acme.c b/modules/md/md_acme.c
index 6eedb55d2a..d4b5c8a57a 100644
--- a/modules/md/md_acme.c
+++ b/modules/md/md_acme.c
@@ -111,9 +111,8 @@ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url)
     acme = apr_pcalloc(p, sizeof(*acme));
     acme->url = url;
     acme->p = p;
-    acme->user_agent = apr_psprintf(p, "%s mod_md/%s (Something, like certbot)", 
+    acme->user_agent = apr_psprintf(p, "%s mod_md/%s", 
                                     base_product, MOD_MD_VERSION);
-    acme->pkey_bits = 4096;
     acme->max_retries = 3;
     
     if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) {
@@ -260,9 +259,15 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
             ptype = md_json_gets(problem, "type", NULL); 
             pdetail = md_json_gets(problem, "detail", NULL);
             req->rv = problem_status_get(ptype);
-             
-            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, req->rv, req->p,
-                          "acme problem %s: %s", ptype, pdetail);
+            
+            if (APR_STATUS_IS_EAGAIN(req->rv)) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, req->rv, req->p,
+                              "acme reports %s: %s", ptype, pdetail);
+            }
+            else {
+                md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, req->rv, req->p,
+                              "acme problem %s: %s", ptype, pdetail);
+            }
             return req->rv;
         }
     }
diff --git a/modules/md/md_acme.h b/modules/md/md_acme.h
index a5a57aab88..25af1da058 100644
--- a/modules/md/md_acme.h
+++ b/modules/md/md_acme.h
@@ -62,7 +62,6 @@ struct md_acme_t {
     
     const char *nonce;
     int max_retries;
-    unsigned int pkey_bits;
 };
 
 /**
@@ -126,8 +125,12 @@ apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *tos);
  * accounces the Tos URL it wants. If this is equal to the agreement specified,
  * the server is notified of this. If the server requires a ToS that the account
  * thinks it has already given, it is resend.
+ *
+ * If an agreement is required, different from the current one, APR_INCOMPLETE is
+ * returned and the agreement url is returned in the parameter.
  */
-apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, const char *agreement);
+apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, 
+                                     const char *agreement, const char **prequired);
 
 /**
  * Get the ToS agreement for current account.
diff --git a/modules/md/md_acme_acct.c b/modules/md/md_acme_acct.c
index 71246c3f86..5ad419ff63 100644
--- a/modules/md/md_acme_acct.c
+++ b/modules/md/md_acme_acct.c
@@ -327,6 +327,7 @@ static apr_status_t acct_register(md_acme_t *acme, apr_pool_t *p,
     apr_status_t rv;
     md_pkey_t *pkey;
     const char *err = NULL, *uri;
+    md_pkey_spec_t spec;
     int i;
     
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account");
@@ -347,7 +348,10 @@ static apr_status_t acct_register(md_acme_t *acme, apr_pool_t *p,
         }
     }
     
-    if (APR_SUCCESS == (rv = md_pkey_gen_rsa(&pkey, acme->p, acme->pkey_bits))
+    spec.type = MD_PKEY_TYPE_RSA;
+    spec.params.rsa.bits = MD_ACME_ACCT_PKEY_BITS;
+    
+    if (APR_SUCCESS == (rv = md_pkey_gen(&pkey, acme->p, &spec))
         && APR_SUCCESS == (rv = acct_make(&acme->acct,  p, acme->url, NULL, contacts))) {
         acct_ctx_t ctx;
 
@@ -614,11 +618,13 @@ static int agreement_required(md_acme_acct_t *acct)
             || (acct->tos_required && strcmp(acct->tos_required, acct->agreement)));
 }
 
-apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, const char *agreement)
+apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, 
+                                     const char *agreement, const char **prequired)
 {
     apr_status_t rv = APR_SUCCESS;
     
     /* Check if (correct) Terms-of-Service for account were accepted */
+    *prequired = NULL;
     if (agreement_required(acme->acct)) {
         const char *tos = acme->acct->tos_required;
         if (!tos) {
@@ -642,10 +648,8 @@ apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, const char
             rv = md_acme_agree(acme, p, tos);
         }
         else {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, acme->p, 
-                          "need to accept terms-of-service <%s> for account %s", 
-                          tos, acme->acct->id);
-            rv = APR_EACCES;
+            *prequired = apr_pstrdup(p, tos);
+            rv = APR_INCOMPLETE;
         }
     }
     return rv;
diff --git a/modules/md/md_acme_acct.h b/modules/md/md_acme_acct.h
index 5abd9b5031..06ac9532b8 100644
--- a/modules/md/md_acme_acct.h
+++ b/modules/md/md_acme_acct.h
@@ -41,4 +41,8 @@ struct md_acme_acct_t {
 #define MD_FN_ACCOUNT           "account.json"
 #define MD_FN_ACCT_KEY          "account.pem"
 
+/* ACME account private keys are always RSA and have that many bits. Since accounts
+ * are expected to live long, better err on the safe side. */
+#define MD_ACME_ACCT_PKEY_BITS  3072
+
 #endif /* md_acme_acct_h */
diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c
index 83c6ca9609..e07a3c06a3 100644
--- a/modules/md/md_acme_authz.c
+++ b/modules/md/md_acme_authz.c
@@ -91,10 +91,10 @@ apr_status_t md_acme_authz_set_remove(md_acme_authz_set_t *set, const char *doma
     for (i = 0; i < set->authzs->nelts; ++i) {
         authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *);
         if (!apr_strnatcasecmp(domain, authz->domain)) {
-            int n = i +1;
+            int n = i + 1;
             if (n < set->authzs->nelts) {
                 void **elems = (void **)set->authzs->elts;
-                memmove(elems + i, elems + n, set->authzs->nelts - n); 
+                memmove(elems + i, elems + n, (size_t)(set->authzs->nelts - n)); 
             }
             --set->authzs->nelts;
             return APR_SUCCESS;
@@ -292,7 +292,8 @@ static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *a
 }
 
 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, apr_pool_t *p)
+                                      md_acme_t *acme, md_store_t *store, 
+                                      md_pkey_spec_t *key_spec, apr_pool_t *p)
 {
     const char *data;
     apr_status_t rv;
@@ -347,7 +348,8 @@ static apr_status_t setup_cha_dns(const char **pdns, md_acme_authz_cha_t *cha, a
 }
 
 static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, 
-                                         md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+                                         md_acme_t *acme, md_store_t *store, 
+                                         md_pkey_spec_t *key_spec, apr_pool_t *p)
 {
     md_cert_t *cha_cert;
     md_pkey_t *cha_key;
@@ -365,7 +367,7 @@ static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz
     if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, cha_dns)) 
         || APR_STATUS_IS_ENOENT(rv)) {
         
-        if (APR_SUCCESS != (rv = md_pkey_gen_rsa(&cha_key, p, acme->pkey_bits))) {
+        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-sni-01 challgenge key",
                           authz->domain);
             goto out;
@@ -405,7 +407,8 @@ out:
 }
 
 typedef apr_status_t cha_starter(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, 
-                                 md_acme_t *acme, md_store_t *store, apr_pool_t *p);
+                                 md_acme_t *acme, md_store_t *store, 
+                                 md_pkey_spec_t *key_spec, apr_pool_t *p);
                                  
 typedef struct {
     const char *name;
@@ -449,7 +452,8 @@ static apr_status_t find_type(void *baton, size_t index, md_json_t *json)
 }
 
 apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, 
-                                   apr_array_header_t *challenges, apr_pool_t *p)
+                                   apr_array_header_t *challenges, 
+                                   md_pkey_spec_t *key_spec, apr_pool_t *p)
 {
     apr_status_t rv;
     int i;
@@ -485,7 +489,7 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s
     
     for (i = 0; i < CHA_TYPES_LEN; ++i) {
         if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) {
-            return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, p);
+            return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, key_spec, p);
         }
     }
     
@@ -560,7 +564,7 @@ md_acme_authz_t *md_acme_authz_from_json(struct md_json_t *json, apr_pool_t *p)
         authz->domain = md_json_dups(p, json, MD_KEY_DOMAIN, NULL);            
         authz->location = md_json_dups(p, json, MD_KEY_LOCATION, NULL);            
         authz->dir = md_json_dups(p, json, MD_KEY_DIR, NULL);            
-        authz->state = (int)md_json_getl(json, MD_KEY_STATE, NULL);            
+        authz->state = (md_acme_authz_state_t)md_json_getl(json, MD_KEY_STATE, NULL);            
         return authz;
     }
     return NULL;
@@ -631,7 +635,7 @@ static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_lis
     const char *md_name;
     int create;
     
-    group = va_arg(ap, int);
+    group = (md_store_group_t)va_arg(ap, int);
     md_name = va_arg(ap, const char *);
     set = va_arg(ap, md_acme_authz_set_t *);
     create = va_arg(ap, int);
@@ -657,7 +661,7 @@ static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_li
     const char *md_name;
     int i;
 
-    group = va_arg(ap, int);
+    group = (md_store_group_t)va_arg(ap, int);
     md_name = va_arg(ap, const char *);
 
     if (APR_SUCCESS == md_acme_authz_set_load(store, group, md_name, &authz_set, p)) {
diff --git a/modules/md/md_acme_authz.h b/modules/md/md_acme_authz.h
index 0996deb56b..817d6fe830 100644
--- a/modules/md/md_acme_authz.h
+++ b/modules/md/md_acme_authz.h
@@ -21,6 +21,7 @@ struct md_acme_t;
 struct md_acme_acct_t;
 struct md_json_t;
 struct md_store_t;
+struct md_pkey_spec_t;
 
 typedef struct md_acme_challenge_t md_acme_challenge_t;
 
@@ -67,8 +68,8 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, struct md_acme_t *acme
                                   struct md_store_t *store, 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, apr_pool_t *p);
+                                   struct md_store_t *store, apr_array_header_t *challenges, 
+                                   struct md_pkey_spec_t *key_spec, apr_pool_t *p);
 apr_status_t md_acme_authz_del(md_acme_authz_t *authz, struct md_acme_t *acme, 
                                struct md_store_t *store, apr_pool_t *p);
 
diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c
index fe8359be3d..068c0d3349 100644
--- a/modules/md/md_acme_drive.c
+++ b/modules/md/md_acme_drive.c
@@ -42,9 +42,11 @@ typedef struct {
     const char *phase;
     int complete;
 
-    md_pkey_t *pkey;
-    md_cert_t *cert;
-    apr_array_header_t *chain;
+    md_pkey_t *privkey;              /* the new private key */
+    apr_array_header_t *pubcert;     /* the new certificate + chain certs */
+    
+    md_cert_t *cert;                 /* the new certificate */
+    apr_array_header_t *chain;       /* the chain certificates */
 
     md_acme_t *acme;
     md_t *md;
@@ -122,8 +124,8 @@ static apr_status_t ad_set_acct(md_proto_driver_t *d)
             goto out;
         }
     
-        if (APR_SUCCESS == (rv = md_acme_create_acct(ad->acme, d->p, 
-                                                     md->contacts, md->ca_agreement))
+        if (APR_SUCCESS == (rv = md_acme_create_acct(ad->acme, d->p, md->contacts, 
+                                                     md->ca_agreement))
             && APR_SUCCESS == (rv = md_acme_acct_save_staged(ad->acme, d->store, md, d->p))) {
             md->ca_account = MD_ACME_ACCT_STAGED;
             update = 1;
@@ -267,11 +269,13 @@ static apr_status_t ad_start_challenges(md_proto_driver_t *d)
         switch (authz->state) {
             case MD_ACME_AUTHZ_S_VALID:
                 break;
-            case MD_ACME_AUTHZ_S_PENDING:
                 
-                rv = md_acme_authz_respond(authz, ad->acme, d->store, ad->ca_challenges, d->p);
+            case MD_ACME_AUTHZ_S_PENDING:
+                rv = md_acme_authz_respond(authz, ad->acme, d->store, ad->ca_challenges, 
+                                           d->md->pkey_spec, d->p);
                 changed = 1;
                 break;
+                
             default:
                 rv = APR_EINVAL;
                 md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, 
@@ -464,22 +468,22 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void
 static apr_status_t ad_setup_certificate(md_proto_driver_t *d)
 {
     md_acme_driver_t *ad = d->baton;
-    md_pkey_t *pkey;
+    md_pkey_t *privkey;
     apr_status_t rv;
 
-    ad->phase = "setup cert pkey";
+    ad->phase = "setup cert privkey";
     
-    rv = md_pkey_load(d->store, MD_SG_STAGING, ad->md->name, &pkey, d->p);
+    rv = md_pkey_load(d->store, MD_SG_STAGING, ad->md->name, &privkey, d->p);
     if (APR_STATUS_IS_ENOENT(rv)) {
-        if (APR_SUCCESS == (rv = md_pkey_gen_rsa(&pkey, d->p, ad->acme->pkey_bits))) {
-            rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, ad->md->name, pkey, 1);
+        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, ad->md->name, privkey, 1);
         }
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate pkey", ad->md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate privkey", ad->md->name);
     }
 
     if (APR_SUCCESS == rv) {
         ad->phase = "setup csr";
-        rv = md_cert_req_create(&ad->csr_der_64, ad->md, pkey, d->p);
+        rv = md_cert_req_create(&ad->csr_der_64, ad->md, privkey, d->p);
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create CSR", ad->md->name);
     }
 
@@ -674,6 +678,12 @@ static apr_status_t acme_stage(md_proto_driver_t *d)
         ad->md = NULL;
     }
     
+    if (ad->md && ad->md->state == MD_S_MISSING) {
+        /* There is config information missing. It makes no sense to drive this MD further */
+        rv = APR_INCOMPLETE;
+        goto out;
+    }
+    
     if (ad->md) {
         /* staging in progress. look for new ACME account information collected there */
         rv = md_reg_creds_get(&ad->ncreds, d->reg, MD_SG_STAGING, d->md, d->p);
@@ -684,7 +694,7 @@ static apr_status_t acme_stage(md_proto_driver_t *d)
     }
     
     /* Find out where we're at with this managed domain */
-    if (ad->ncreds && ad->ncreds->pkey && ad->ncreds->cert && ad->ncreds->chain) {
+    if (ad->ncreds && ad->ncreds->privkey && ad->ncreds->pubcert) {
         /* There is a full set staged, to be loaded */
         md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: all data staged", d->md->name);
         renew = 0;
@@ -724,11 +734,32 @@ static apr_status_t acme_stage(md_proto_driver_t *d)
              * requests for new authorizations are denied. ToS may change during the
              * lifetime of an account */
             if (APR_SUCCESS == rv) {
+                const char *required;
+                
                 ad->phase = "check agreement";
                 md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
                               "%s: check Terms-of-Service agreement", d->md->name);
                 
-                rv = md_acme_check_agreement(ad->acme, d->p, ad->md->ca_agreement);
+                rv = md_acme_check_agreement(ad->acme, d->p, ad->md->ca_agreement, &required);
+                
+                if (APR_STATUS_IS_INCOMPLETE(rv) && required) {
+                    /* The CA wants the user to agree to Terms-of-Services. Until the user
+                     * has reconfigured and restarted the server, this MD cannot be
+                     * driven further */
+                    ad->md->state = MD_S_MISSING;
+                    md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
+
+                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, 
+                                  "%s: the CA requires you to accept the terms-of-service "
+                                  "as specified in <%s>. "
+                                  "Please read the document that you find at that URL and, "
+                                  "if you agree to the conditions, configure "
+                                  "\"MDCertificateAgreement url\" "
+                                  "with exactly that URL in your Apache. "
+                                  "Then (graceful) restart the server to activate.", 
+                                  ad->md->name, required);
+                    goto out;
+                }
             }
             
             /* If we know a cert's location, try to get it. Previous download might
@@ -779,6 +810,10 @@ static apr_status_t acme_stage(md_proto_driver_t *d)
             
         }
         
+        if (APR_SUCCESS == rv && !ad->chain) {
+            /* have we created this already? */
+            md_chain_load(d->store, MD_SG_STAGING, ad->md->name, &ad->chain, d->p);
+        }
         if (APR_SUCCESS == rv && !ad->chain) {
             ad->phase = "install chain";
             md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
@@ -786,6 +821,18 @@ static apr_status_t acme_stage(md_proto_driver_t *d)
             rv = ad_chain_install(d);
         }
 
+        if (APR_SUCCESS == rv && !ad->pubcert) {
+            /* have we created this already? */
+            md_pubcert_load(d->store, MD_SG_STAGING, ad->md->name, &ad->pubcert, d->p);
+        }
+        if (APR_SUCCESS == rv && !ad->pubcert) {
+            /* combine cert + chain into the pubcert */
+            ad->pubcert = apr_array_make(d->p, ad->chain->nelts + 1, sizeof(md_cert_t*));
+            APR_ARRAY_PUSH(ad->pubcert, md_cert_t *) = ad->cert;
+            apr_array_cat(ad->pubcert, ad->chain);
+            rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, ad->md->name, ad->pubcert, 0);
+        }
+
         if (APR_SUCCESS == rv && ad->cert) {
             apr_time_t now = apr_time_now();
             apr_interval_time_t max_delay, delay_activation; 
@@ -832,10 +879,9 @@ static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group,
                                  const char *name, apr_pool_t *p) 
 {
     apr_status_t rv;
-    md_pkey_t *pkey, *acct_key;
+    md_pkey_t *privkey, *acct_key;
     md_t *md;
-    md_cert_t *cert;
-    apr_array_header_t *chain;
+    apr_array_header_t *pubcert;
     struct md_acme_acct_t *acct;
 
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: preload start", name);
@@ -852,16 +898,12 @@ static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group,
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading md json", name);
         return rv;
     }
-    if (APR_SUCCESS != (rv = md_cert_load(store, MD_SG_STAGING, name, &cert, p))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading certificate", name);
-        return rv;
-    }
-    if (APR_SUCCESS != (rv = md_chain_load(store, MD_SG_STAGING, name, &chain, p))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading cert chain", name);
+    if (APR_SUCCESS != (rv = md_pkey_load(store, MD_SG_STAGING, name, &privkey, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading staging private key", name);
         return rv;
     }
-    if (APR_SUCCESS != (rv = md_pkey_load(store, MD_SG_STAGING, name, &pkey, p))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading staging private key", name);
+    if (APR_SUCCESS != (rv = md_pubcert_load(store, MD_SG_STAGING, name, &pubcert, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading pubcert", name);
         return rv;
     }
 
@@ -904,16 +946,12 @@ static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group,
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving md json", name);
         return rv;
     }
-    if (APR_SUCCESS != (rv = md_cert_save(store, p, load_group, name, cert, 1))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving certificate", name);
-        return rv;
-    }
-    if (APR_SUCCESS != (rv = md_chain_save(store, p, load_group, name, chain, 1))) {
+    if (APR_SUCCESS != (rv = md_pubcert_save(store, p, load_group, name, pubcert, 1))) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving cert chain", name);
         return rv;
     }
-    if (APR_SUCCESS != (rv = md_pkey_save(store, p, load_group, name, pkey, 1))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving domain private key", name);
+    if (APR_SUCCESS != (rv = md_pkey_save(store, p, load_group, name, privkey, 1))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving private key", name);
         return rv;
     }
     
diff --git a/modules/md/md_cmd_reg.c b/modules/md/md_cmd_reg.c
index 8dac2a89b5..1b1278ac46 100644
--- a/modules/md/md_cmd_reg.c
+++ b/modules/md/md_cmd_reg.c
@@ -103,7 +103,7 @@ static apr_status_t cmd_reg_list(md_cmd_ctx *ctx, const md_cmd_t *cmd)
     else {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ctx->p, "list do");
         md_reg_do(list_add_md, mdlist, ctx->reg, ctx->p);
-        qsort(mdlist->elts, mdlist->nelts, sizeof(md_t *), md_name_cmp);
+        qsort(mdlist->elts, (size_t)mdlist->nelts, sizeof(md_t *), md_name_cmp);
     
         for (i = 0; i < mdlist->nelts; ++i) {
             md = APR_ARRAY_IDX(mdlist, i, const md_t*);
@@ -274,7 +274,7 @@ static apr_status_t assess_and_drive(md_cmd_ctx *ctx, md_t *md)
         md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: %s", md->name, msg);
         
         if (APR_SUCCESS == (rv = md_reg_stage(ctx->reg, md, challenge, reset, NULL, ctx->p))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: loading", md->name);
+            md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: loading", md->name);
             
             rv = md_reg_load(ctx->reg, md->name, ctx->p);
             
@@ -317,7 +317,7 @@ static apr_status_t cmd_reg_drive(md_cmd_ctx *ctx, const md_cmd_t *cmd)
     }
     else {
         md_reg_do(list_add_md, mdlist, ctx->reg, ctx->p);
-        qsort(mdlist->elts, mdlist->nelts, sizeof(md_t *), md_name_cmp);
+        qsort(mdlist->elts, (size_t)mdlist->nelts, sizeof(md_t *), md_name_cmp);
     }   
     
     rv = APR_SUCCESS;
diff --git a/modules/md/md_core.c b/modules/md/md_core.c
index f9bbd78033..8294ba505c 100644
--- a/modules/md/md_core.c
+++ b/modules/md/md_core.c
@@ -24,6 +24,7 @@
 
 #include "md_json.h"
 #include "md.h"
+#include "md_crypt.h"
 #include "md_log.h"
 #include "md_store.h"
 #include "md_util.h"
@@ -229,6 +230,8 @@ md_t *md_clone(apr_pool_t *p, const md_t *src)
         md->name = apr_pstrdup(p, src->name);
         md->drive_mode = src->drive_mode;
         md->domains = md_array_str_compact(p, src->domains, 0);
+        md->pkey_spec = src->pkey_spec;
+        md->renew_norm = src->renew_norm;
         md->renew_window = src->renew_window;
         md->contacts = md_array_str_clone(p, src->contacts);
         if (src->ca_url) md->ca_url = apr_pstrdup(p, src->ca_url);
@@ -253,8 +256,10 @@ md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base)
     n->ca_proto = add->ca_proto? add->ca_proto : base->ca_proto;
     n->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement;
     n->drive_mode = (add->drive_mode != MD_DRIVE_DEFAULT)? add->drive_mode : base->drive_mode;
-    n->renew_window = (add->renew_window <= 0)? add->renew_window : base->renew_window;
-    n->transitive = (add->transitive < 0)? add->transitive : base->transitive;
+    n->pkey_spec = add->pkey_spec? add->pkey_spec : base->pkey_spec;
+    n->renew_norm = (add->renew_norm > 0)? add->renew_norm : base->renew_norm;
+    n->renew_window = (add->renew_window > 0)? add->renew_window : base->renew_window;
+    n->transitive = (add->transitive >= 0)? add->transitive : base->transitive;
     if (add->ca_challenges) {
         n->ca_challenges = apr_array_copy(p, add->ca_challenges);
     }
@@ -264,7 +269,6 @@ md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base)
     return n;
 }
 
-
 /**************************************************************************************************/
 /* format conversion */
 
@@ -284,6 +288,9 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
         if (md->cert_url) {
             md_json_sets(md->cert_url, json, MD_KEY_CERT, MD_KEY_URL, NULL);
         }
+        if (md->pkey_spec) {
+            md_json_setj(md_pkey_spec_to_json(md->pkey_spec, p), json, MD_KEY_PKEY, NULL);
+        }
         md_json_setl(md->state, json, MD_KEY_STATE, NULL);
         md_json_setl(md->drive_mode, json, MD_KEY_DRIVE_MODE, NULL);
         if (md->expires > 0) {
@@ -296,7 +303,13 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
             apr_rfc822_date(ts, md->valid_from);
             md_json_sets(ts, json, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL);
         }
-        md_json_setl(apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL);
+        if (md->renew_norm > 0) {
+            md_json_setl((long)apr_time_sec(md->renew_norm), json, MD_KEY_RENEW_NORM, NULL);
+            md_json_setl((long)apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL);
+        }
+        else {
+            md_json_setl((long)apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL);
+        }
         if (md->ca_challenges && md->ca_challenges->nelts > 0) {
             apr_array_header_t *na;
             na = md_array_str_compact(p, md->ca_challenges, 0);
@@ -320,7 +333,10 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
         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);
         md->cert_url = md_json_dups(p, json, MD_KEY_CERT, MD_KEY_URL, NULL);
-        md->state = (int)md_json_getl(json, MD_KEY_STATE, 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);
+        }
+        md->state = (md_state_t)md_json_getl(json, MD_KEY_STATE, NULL);
         md->drive_mode = (int)md_json_getl(json, MD_KEY_DRIVE_MODE, NULL);
         md->domains = md_array_str_compact(p, md->domains, 0);
         md->transitive = (int)md_json_getl(json, MD_KEY_TRANSITIVE, NULL);
@@ -332,6 +348,7 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
         if (s && *s) {
             md->valid_from = apr_date_parse_rfc(s);
         }
+        md->renew_norm = apr_time_from_sec(md_json_getl(json, MD_KEY_RENEW_NORM, NULL));
         md->renew_window = apr_time_from_sec(md_json_getl(json, MD_KEY_RENEW_WINDOW, NULL));
         if (md_json_has_key(json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL)) {
             md->ca_challenges = apr_array_make(p, 5, sizeof(const char*));
diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c
index 4e0b4d521f..10ddf51ed9 100644
--- a/modules/md/md_crypt.c
+++ b/modules/md/md_crypt.c
@@ -31,6 +31,7 @@
 
 #include "md.h"
 #include "md_crypt.h"
+#include "md_json.h"
 #include "md_log.h"
 #include "md_http.h"
 #include "md_util.h"
@@ -83,7 +84,7 @@ static void seed_RAND(int pid)
 {   
     unsigned char stackdata[256];
     /* stolen from mod_ssl/ssl_engine_rand.c */
-    apr_size_t n, l;
+    int n;
     struct {
         time_t t;
         pid_t pid;
@@ -99,8 +100,7 @@ static void seed_RAND(int pid)
      */
     my_seed.pid = pid;
     
-    l = sizeof(my_seed);
-    RAND_seed((unsigned char *)&my_seed, l);
+    RAND_seed((unsigned char *)&my_seed, sizeof(my_seed));
     
     /*
      * seed in some current state of the run-time stack (128 bytes)
@@ -173,7 +173,7 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton)
         if (ctx->pass_len < size) {
             size = (int)ctx->pass_len;
         }
-        memcpy(buf, ctx->pass_phrase, size);
+        memcpy(buf, ctx->pass_phrase, (size_t)size);
     }
     return ctx->pass_len;
 }
@@ -181,6 +181,50 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton)
 /**************************************************************************************************/
 /* private keys */
 
+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);
+    if (json) {
+        switch (spec->type) {
+            case MD_PKEY_TYPE_DEFAULT:
+                md_json_sets("Default", json, MD_KEY_TYPE, NULL);
+                break;
+            case MD_PKEY_TYPE_RSA:
+                md_json_sets("RSA", json, MD_KEY_TYPE, NULL);
+                if (spec->params.rsa.bits > 2048) {
+                    md_json_setl(spec->params.rsa.bits, json, MD_KEY_BITS, NULL);
+                }
+                break;
+            default:
+                md_json_sets("Unsupported", json, MD_KEY_TYPE, NULL);
+                break;
+        }
+    }
+    return json;    
+}
+
+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));
+    const char *s;
+    long l;
+    
+    if (spec) {
+        s = md_json_gets(json, MD_KEY_TYPE, NULL);
+        if (!s || !apr_strnatcasecmp("Default", s)) {
+            spec->type = MD_PKEY_TYPE_DEFAULT;
+        }
+        else if (!apr_strnatcasecmp("RSA", s)) {
+            spec->type = MD_PKEY_TYPE_RSA;
+            l = md_json_getl(json, MD_KEY_BITS, NULL);
+            if (l > 2048) {
+                spec->params.rsa.bits = (unsigned int)l;
+            }
+        }
+    }
+    return spec;
+}
+
 static md_pkey_t *make_pkey(apr_pool_t *p) 
 {
     md_pkey_t *pkey = apr_pcalloc(p, sizeof(*pkey));
@@ -231,7 +275,7 @@ apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p,
             apr_pool_cleanup_register(p, pkey, pkey_cleanup, apr_pool_cleanup_null);
         }
         else {
-            long err = ERR_get_error();
+            unsigned long err = ERR_get_error();
             rv = APR_EINVAL;
             md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, 
                           "error loading pkey %s: %s (pass phrase was %snull)", fname,
@@ -251,6 +295,7 @@ static apr_status_t pkey_to_buffer(buffer *buffer, md_pkey_t *pkey, apr_pool_t *
     void *cb_baton = NULL;
     passwd_ctx ctx;
     unsigned long err;
+    int i;
     
     if (!bio) {
         return APR_ENOMEM;
@@ -278,11 +323,12 @@ static apr_status_t pkey_to_buffer(buffer *buffer, md_pkey_t *pkey, apr_pool_t *
         return APR_EINVAL;
     }
 
-    buffer->len = BIO_pending(bio);
-    if (buffer->len > 0) {
-        buffer->data = apr_palloc(p, buffer->len+1);
-        buffer->len = BIO_read(bio, buffer->data, (int)buffer->len);
-        buffer->data[buffer->len] = '\0';
+    i = BIO_pending(bio);
+    if (i > 0) {
+        buffer->data = apr_palloc(p, (apr_size_t)i + 1);
+        i = BIO_read(bio, buffer->data, i);
+        buffer->data[i] = '\0';
+        buffer->len = (apr_size_t)i;
     }
     BIO_free(bio);
     return APR_SUCCESS;
@@ -303,7 +349,7 @@ apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
     return rv;
 }
 
-apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits)
+static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits)
 {
     EVP_PKEY_CTX *ctx = NULL;
     apr_status_t rv;
@@ -312,7 +358,7 @@ apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits)
     ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
     if (ctx 
         && EVP_PKEY_keygen_init(ctx) >= 0
-        && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) >= 0
+        && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int)bits) >= 0
         && EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) >= 0) {
         rv = APR_SUCCESS;
     }
@@ -328,6 +374,19 @@ apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits)
     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;
+    switch (ptype) {
+        case MD_PKEY_TYPE_DEFAULT:
+            return gen_rsa(ppkey, p, MD_PKEY_RSA_BITS_DEF);
+        case MD_PKEY_TYPE_RSA:
+            return gen_rsa(ppkey, p, spec->params.rsa.bits);
+        default:
+            return APR_ENOTIMPL;
+    }
+}
+
 #if OPENSSL_VERSION_NUMBER < 0x10100000L
 
 #ifndef NID_tlsfeature
@@ -350,7 +409,7 @@ static void RSA_get0_key(const RSA *r,
 static const char *bn64(const BIGNUM *b, apr_pool_t *p) 
 {
     if (b) {
-         int len = BN_num_bytes(b);
+         apr_size_t len = (apr_size_t)BN_num_bytes(b);
          char *buffer = apr_pcalloc(p, len);
          if (buffer) {
             BN_bn2bin(b, (unsigned char *)buffer);
@@ -393,7 +452,7 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *
     const char *sign64 = NULL;
     apr_status_t rv = APR_ENOMEM;
     
-    buffer = apr_pcalloc(p, EVP_PKEY_size(pkey->pkey));
+    buffer = apr_pcalloc(p, (apr_size_t)EVP_PKEY_size(pkey->pkey));
     if (buffer) {
         ctx = EVP_MD_CTX_create();
         if (ctx) {
@@ -728,6 +787,7 @@ apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname)
 static apr_status_t cert_to_buffer(buffer *buffer, md_cert_t *cert, apr_pool_t *p)
 {
     BIO *bio = BIO_new(BIO_s_mem());
+    int i;
     
     if (!bio) {
         return APR_ENOMEM;
@@ -740,11 +800,12 @@ static apr_status_t cert_to_buffer(buffer *buffer, md_cert_t *cert, apr_pool_t *
         return APR_EINVAL;
     }
 
-    buffer->len = BIO_pending(bio);
-    if (buffer->len > 0) {
-        buffer->data = apr_palloc(p, buffer->len+1);
-        buffer->len = BIO_read(bio, buffer->data, (int)buffer->len);
-        buffer->data[buffer->len] = '\0';
+    i = BIO_pending(bio);
+    if (i > 0) {
+        buffer->data = apr_palloc(p, (apr_size_t)i + 1);
+        i = BIO_read(bio, buffer->data, i);
+        buffer->data[i] = '\0';
+        buffer->len = (apr_size_t)i;
     }
     BIO_free(bio);
     return APR_SUCCESS;
@@ -797,7 +858,7 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p,
             const unsigned char *bf = (const unsigned char*)der;
             X509 *x509;
             
-            if (NULL == (x509 = d2i_X509(NULL, &bf, der_len))) {
+            if (NULL == (x509 = d2i_X509(NULL, &bf, (long)der_len))) {
                 rv = APR_EINVAL;
             }
             else {
@@ -818,19 +879,16 @@ md_cert_state_t md_cert_state_get(md_cert_t *cert)
     return MD_CERT_UNKNOWN;
 }
 
-apr_status_t md_chain_fload(apr_array_header_t **pcerts, apr_pool_t *p, const char *fname)
+apr_status_t md_chain_fappend(struct apr_array_header_t *certs, apr_pool_t *p, const char *fname)
 {
     FILE *f;
     apr_status_t rv;
-    apr_array_header_t *certs = NULL;
     X509 *x509;
     md_cert_t *cert;
     unsigned long err;
     
     rv = md_util_fopen(&f, fname, "r");
     if (rv == APR_SUCCESS) {
-        certs = apr_array_make(p, 5, sizeof(md_cert_t *));
-        
         ERR_clear_error();
         while (NULL != (x509 = PEM_read_X509(f, NULL, NULL, NULL))) {
             cert = make_cert(p, x509);
@@ -862,6 +920,16 @@ apr_status_t md_chain_fload(apr_array_header_t **pcerts, apr_pool_t *p, const ch
 out:
     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "read chain file %s, found %d certs", 
                   fname, certs? certs->nelts : 0);
+    return rv;
+}
+
+apr_status_t md_chain_fload(apr_array_header_t **pcerts, apr_pool_t *p, const char *fname)
+{
+    apr_array_header_t *certs;
+    apr_status_t rv;
+
+    certs = apr_array_make(p, 5, sizeof(md_cert_t *));
+    rv = md_chain_fappend(certs, p, fname);
     *pcerts = (APR_SUCCESS == rv)? certs : NULL;
     return rv;
 }
@@ -1027,12 +1095,12 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const md_t *md,
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: der length", md->name);
         rv = APR_EGENERAL; goto out;
     }
-    s = csr_der = apr_pcalloc(p, csr_der_len + 1);
+    s = csr_der = apr_pcalloc(p, (apr_size_t)csr_der_len + 1);
     if (i2d_X509_REQ(csr, (unsigned char**)&s) != csr_der_len) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: csr der enc", md->name);
         rv = APR_EGENERAL; goto out;
     }
-    csr_der_64 = md_util_base64url_encode(csr_der, csr_der_len, p);
+    csr_der_64 = md_util_base64url_encode(csr_der, (apr_size_t)csr_der_len, p);
     rv = APR_SUCCESS;
     
 out:
@@ -1104,7 +1172,7 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
         rv = APR_EGENERAL; goto out;
     }
     
-    days = ((apr_time_sec(valid_for) + MD_SECS_PER_DAY - 1)/ MD_SECS_PER_DAY);
+    days = (int)((apr_time_sec(valid_for) + MD_SECS_PER_DAY - 1)/ MD_SECS_PER_DAY);
     if (!X509_set_notBefore(x, ASN1_TIME_set(NULL, time(NULL)))) {
         rv = APR_EGENERAL; goto out;
     }
diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h
index 0437f897ea..5a53655926 100644
--- a/modules/md/md_crypt.h
+++ b/modules/md/md_crypt.h
@@ -41,9 +41,25 @@ apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p,
 
 typedef struct md_pkey_t md_pkey_t;
 
+typedef enum {
+    MD_PKEY_TYPE_DEFAULT,
+    MD_PKEY_TYPE_RSA,
+} md_pkey_type_t;
+
+typedef struct md_pkey_rsa_spec_t {
+    apr_uint32_t bits;
+} md_pkey_rsa_spec_t;
+
+typedef struct md_pkey_spec_t {
+    md_pkey_type_t type;
+    union {
+        md_pkey_rsa_spec_t rsa;
+    } params;
+} md_pkey_spec_t;
+
 apr_status_t md_crypt_init(apr_pool_t *pool);
 
-apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits);
+apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec);
 void md_pkey_free(md_pkey_t *pkey);
 
 const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p);
@@ -62,6 +78,9 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *
 void *md_cert_get_X509(struct md_cert_t *cert);
 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);
+
 /**************************************************************************************************/
 /* X509 certificates */
 
@@ -100,6 +119,8 @@ apr_status_t md_chain_fload(struct apr_array_header_t **pcerts,
                             apr_pool_t *p, const char *fname);
 apr_status_t md_chain_fsave(struct apr_array_header_t *certs, 
                             apr_pool_t *p, const char *fname, apr_fileperms_t perms);
+apr_status_t md_chain_fappend(struct apr_array_header_t *certs, 
+                              apr_pool_t *p, const char *fname);
 
 apr_status_t md_cert_req_create(const char **pcsr_der_64, const struct md_t *md, 
                                 md_pkey_t *pkey, apr_pool_t *p);
diff --git a/modules/md/md_curl.c b/modules/md/md_curl.c
index 1eb5e554c0..f948efd8ec 100644
--- a/modules/md/md_curl.c
+++ b/modules/md/md_curl.c
@@ -99,7 +99,7 @@ static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton)
         if (res->req->resp_limit) {
             apr_off_t body_len = 0;
             apr_brigade_length(res->body, 0, &body_len);
-            if (body_len + len > res->req->resp_limit) {
+            if (body_len + (apr_off_t)len > res->req->resp_limit) {
                 return 0; /* signal curl failure */
             }
         }
@@ -117,7 +117,7 @@ static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton)
     md_http_response_t *res = baton;
     size_t len, clen = elen * nmemb;
     const char *name = NULL, *value = "", *b = buffer;
-    int i;
+    apr_size_t i;
     
     len = (clen && b[clen-1] == '\n')? clen-1 : clen;
     len = (len && b[len-1] == '\r')? len-1 : len;
@@ -183,7 +183,7 @@ static int curlify_headers(void *baton, const char *key, const char *value)
 static apr_status_t curl_perform(md_http_request_t *req)
 {
     apr_status_t rv = APR_SUCCESS;
-    int curle;
+    CURLcode curle;
     md_http_response_t *res;
     CURL *curl;
     struct curl_slist *req_hdrs = NULL;
@@ -253,7 +253,8 @@ static apr_status_t curl_perform(md_http_request_t *req)
     }
     else {
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, res->rv, req->pool, 
-                      "request %ld failed(%d): %s", req->id, curle, curl_easy_strerror(curle));
+                      "request %ld failed(%d): %s", req->id, curle, 
+                      curl_easy_strerror(curle));
     }
     
     if (req->cb) {
diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c
index 04921c887f..25a841850f 100644
--- a/modules/md/md_reg.c
+++ b/modules/md/md_reg.c
@@ -162,19 +162,14 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
 
     if (APR_SUCCESS == (rv = md_reg_creds_get(&creds, reg, MD_SG_DOMAINS, md, p))) {
         state = MD_S_INCOMPLETE;
-        if (!creds->pkey) {
+        if (!creds->privkey) {
             md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
                           "md{%s}: incomplete, without private key", md->name);
         }
-        else if (!creds->cert) {
+        else if (!creds->pubcert) {
             md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
                           "md{%s}: incomplete, has key but no certificate", md->name);
         }
-        else if (!creds->chain) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                          "md{%s}: incomplete, has key and certificate, but no chain file.", 
-                          md->name);
-        }
         else {
             valid_from = md_cert_get_not_before(creds->cert);
             expires = md_cert_get_not_after(creds->cert);
@@ -199,8 +194,8 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
                 goto out;
             }
 
-            for (i = 0; i < creds->chain->nelts; ++i) {
-                cert = APR_ARRAY_IDX(creds->chain, i, const md_cert_t *);
+            for (i = 1; i < creds->pubcert->nelts; ++i) {
+                cert = APR_ARRAY_IDX(creds->pubcert, i, const md_cert_t *);
                 if (!md_cert_is_valid_now(cert)) {
                     state = MD_S_ERROR;
                     md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, 
@@ -281,17 +276,34 @@ apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew,
                 md->state = MD_S_EXPIRED;
                 renew = 1;
             }
-            else if ((md->expires - now) <= md->renew_window) {
-                int days = (int)(apr_time_sec(md->expires - now) / MD_SECS_PER_DAY);
-                md_log_perror( MD_LOG_MARK, MD_LOG_DEBUG, 0, p,  
-                              "md(%s): %d days to expiry, attempt renewal", md->name, days);
-                renew = 1;
+            else {
+                apr_interval_time_t renew_win, left, life;
+
+                renew_win = md->renew_window;
+                if (md->renew_norm > 0 
+                    && md->renew_norm > renew_win
+                    && md->expires > md->valid_from) {
+                    /* Calc renewal days as fraction of cert lifetime - if known */
+                    life = md->expires - md->valid_from; 
+                    renew_win = life * md->renew_norm / renew_win;
+                }
+                
+                left = md->expires - now;
+                if (left <= renew_win) {
+                    int days_left = (int)(apr_time_sec(left) / MD_SECS_PER_DAY);
+                    md_log_perror( MD_LOG_MARK, MD_LOG_DEBUG, 0, p,  
+                                  "md(%s): %d days to expiry, attempt renewal", 
+                                  md->name, days_left);
+                    renew = 1;
+                }                
             }
             break;
         case MD_S_INCOMPLETE:
         case MD_S_EXPIRED:
             renew = 1;
             break;
+        case MD_S_MISSING:
+            break;
     }
     *prenew = renew;
     *perrored = errored;
@@ -417,16 +429,10 @@ apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p,
 {
     apr_status_t rv;
     
-    rv = md_store_get_fname(pkeyfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PKEY, p);
+    *pchainfile = NULL;
+    rv = md_store_get_fname(pkeyfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PRIVKEY, p);
     if (APR_SUCCESS == rv) {
-        rv = md_store_get_fname(pcertfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_CERT, p);
-    }
-    if (APR_SUCCESS == rv) {
-        rv = md_store_get_fname(pchainfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_CHAIN, p);
-        if (APR_STATUS_IS_ENOENT(rv)) {
-            *pchainfile = NULL;
-            rv = APR_SUCCESS;
-        }
+        rv = md_store_get_fname(pcertfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PUBCERT, p);
     }
     return rv;
 }
@@ -513,6 +519,7 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
     }
     if (MD_UPD_RENEW_WINDOW & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update renew-window: %s", name);
+        nmd->renew_norm = updates->renew_norm;
         nmd->renew_window = updates->renew_window;
     }
     if (MD_UPD_CA_CHALLENGES & fields) {
@@ -544,29 +551,28 @@ static int ok_or_noent(apr_status_t rv)
 static apr_status_t creds_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
     md_reg_t *reg = baton;
-    apr_status_t rv;
-    md_cert_t *cert;
-    md_pkey_t *pkey;
-    apr_array_header_t *chain;
+    md_pkey_t *privkey;
+    apr_array_header_t *pubcert;
     md_creds_t *creds, **pcreds;
     const md_t *md;
     md_cert_state_t cert_state;
     md_store_group_t group;
+    apr_status_t rv;
     
     pcreds = va_arg(ap, md_creds_t **);
-    group = va_arg(ap, int);
+    group = (md_store_group_t)va_arg(ap, int);
     md = va_arg(ap, const md_t *);
     
-    if (ok_or_noent(rv = md_cert_load(reg->store, group, md->name, &cert, p))
-        && ok_or_noent(rv = md_pkey_load(reg->store, group, md->name, &pkey, p))
-        && ok_or_noent(rv = md_chain_load(reg->store, group, md->name, &chain, p))) {
+    if (ok_or_noent(rv = md_pkey_load(reg->store, group, md->name, &privkey, p))
+        && ok_or_noent(rv = md_pubcert_load(reg->store, group, md->name, &pubcert, p))) {
         rv = APR_SUCCESS;
             
         creds = apr_pcalloc(p, sizeof(*creds));
-        creds->cert = cert;
-        creds->pkey = pkey;
-        creds->chain = chain;
-        
+        creds->privkey = privkey;
+        if (pubcert && pubcert->nelts > 0) {
+            creds->pubcert = pubcert;
+            creds->cert = APR_ARRAY_IDX(pubcert, 0, md_cert_t *);
+        }
         if (creds->cert) {
             switch ((cert_state = md_cert_state_get(creds->cert))) {
                 case MD_CERT_VALID:
@@ -767,10 +773,12 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
                     smd->contacts = md->contacts;
                     fields |= MD_UPD_CONTACTS;
                 }
-                if (MD_VAL_UPDATE(md, smd, renew_window)) {
+                if (MD_VAL_UPDATE(md, smd, renew_window) 
+                    || MD_VAL_UPDATE(md, smd, renew_norm)) {
                     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                                  "%s: update renew_window, old=%ld, new=%ld", 
-                                  smd->name, (long)smd->renew_window, md->renew_window);
+                                  "%s: update renew norm=%ld, window=%ld", 
+                                  smd->name, (long)md->renew_norm, (long)md->renew_window);
+                    smd->renew_norm = md->renew_norm;
                     smd->renew_window = md->renew_window;
                     fields |= MD_UPD_RENEW_WINDOW;
                 }
@@ -880,7 +888,7 @@ apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, const char *challenge,
         return APR_SUCCESS;
     }
     
-    proto = apr_hash_get(reg->protos, md->ca_proto, strlen(md->ca_proto));
+    proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
     if (!proto) {
         md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, 
                       "md %s has unknown CA protocol: %s", md->name, md->ca_proto);
@@ -918,7 +926,7 @@ static apr_status_t run_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
         return APR_EINVAL;
     }
     
-    proto = apr_hash_get(reg->protos, md->ca_proto, strlen(md->ca_proto));
+    proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
     if (!proto) {
         md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, 
                       "md %s has unknown CA protocol: %s", md->name, md->ca_proto);
diff --git a/modules/md/md_store.c b/modules/md/md_store.c
index f94e959c5b..04e265cbea 100644
--- a/modules/md/md_store.c
+++ b/modules/md/md_store.c
@@ -232,13 +232,13 @@ typedef struct {
 apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group, const char *name, 
                           md_pkey_t **ppkey, apr_pool_t *p)
 {
-    return md_store_load(store, group, name, MD_FN_PKEY, MD_SV_PKEY, (void**)ppkey, p);
+    return md_store_load(store, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, (void**)ppkey, p);
 }
 
 apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name, 
                           struct md_pkey_t *pkey, int create)
 {
-    return md_store_save(store, p, group, name, MD_FN_PKEY, MD_SV_PKEY, pkey, create);
+    return md_store_save(store, p, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, pkey, create);
 }
 
 apr_status_t md_cert_load(md_store_t *store, md_store_group_t group, const char *name, 
@@ -267,6 +267,19 @@ apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p,
     return md_store_save(store, p, group, name, MD_FN_CHAIN, MD_SV_CHAIN, chain, create);
 }
 
+apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, 
+                             struct apr_array_header_t **ppubcert, apr_pool_t *p)
+{
+    return md_store_load(store, group, name, MD_FN_PUBCERT, MD_SV_CHAIN, (void**)ppubcert, p);
+}
+
+apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p, 
+                             md_store_group_t group, const char *name, 
+                             struct apr_array_header_t *pubcert, int create)
+{
+    return md_store_save(store, p, group, name, MD_FN_PUBCERT, MD_SV_CHAIN, pubcert, create);
+}
+
 typedef struct {
     md_store_t *store;
     md_store_group_t group;
diff --git a/modules/md/md_store.h b/modules/md/md_store.h
index 86a8639603..8f968c76aa 100644
--- a/modules/md/md_store.h
+++ b/modules/md/md_store.h
@@ -146,5 +146,11 @@ apr_status_t md_chain_load(md_store_t *store, md_store_group_t group,
 apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                            const char *name, struct apr_array_header_t *chain, int create);
 
+apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, 
+                             struct apr_array_header_t **ppubcert, apr_pool_t *p);
+apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p, 
+                             md_store_group_t group, const char *name, 
+                             struct apr_array_header_t *pubcert, int create);
+
 
 #endif /* mod_md_md_store_h */
diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c
index 2626fde9f7..d37968a522 100644
--- a/modules/md/md_store_fs.c
+++ b/modules/md/md_store_fs.c
@@ -37,7 +37,7 @@
 /**************************************************************************************************/
 /* file system based implementation of md_store_t */
 
-#define MD_STORE_VERSION        1.0
+#define MD_STORE_VERSION        2
 
 typedef struct {
     apr_fileperms_t dir;
@@ -60,8 +60,6 @@ struct md_store_fs_t {
     
     int port_80;
     int port_443;
-
-    const unsigned char *dupkey;
 };
 
 #define FS_STORE(store)     (md_store_fs_t*)(((char*)store)-offsetof(md_store_fs_t, s))
@@ -98,32 +96,88 @@ static apr_status_t init_store_file(md_store_fs_t *s_fs, const char *fname,
 {
     md_json_t *json = md_json_create(p);
     const char *key64;
+    unsigned char *key;
     apr_status_t rv;
-    unsigned char key[FS_STORE_KLEN];
-    int i;
     
     md_json_sets(MOD_MD_VERSION, json, MD_KEY_VERSION, NULL);
     md_json_setn(MD_STORE_VERSION, json, MD_KEY_STORE, MD_KEY_VERSION, NULL);
 
-    /*if (APR_SUCCESS != (rv = md_rand_bytes(key, sizeof(key), p))) {
+    s_fs->key_len = FS_STORE_KLEN;
+    s_fs->key = key = apr_pcalloc(p, FS_STORE_KLEN);
+    if (APR_SUCCESS != (rv = md_rand_bytes(key, s_fs->key_len, p))) {
         return rv;
-    }*/
-    for (i = 0; i < FS_STORE_KLEN; ++i) {
-        key[i] = 'a' + (i % 26);
-    } 
-
-    s_fs->key_len = sizeof(key);
-    s_fs->key = apr_pcalloc(p, sizeof(key) + 1);
-    memcpy((void*)s_fs->key, key, sizeof(key));
-    s_fs->dupkey = apr_pmemdup(p, key, sizeof(key));
+    }
         
-    key64 = md_util_base64url_encode((char *)key, sizeof(key), ptemp);
+    key64 = md_util_base64url_encode((char *)key, s_fs->key_len, ptemp);
     md_json_sets(key64, json, MD_KEY_KEY, NULL);
-    
     rv = md_json_fcreatex(json, ptemp, MD_JSON_FMT_INDENT, fname, MD_FPROT_F_UONLY);
     memset((char*)key64, 0, strlen(key64));
 
-    assert(memcmp(s_fs->key, s_fs->dupkey, FS_STORE_KLEN) == 0);
+    return rv;
+}
+
+static apr_status_t rename_pkey(void *baton, apr_pool_t *p, apr_pool_t *ptemp, 
+                                const char *dir, const char *name, 
+                                apr_filetype_e ftype)
+{
+    const char *from, *to;
+    apr_status_t rv = APR_SUCCESS;
+
+    if (APR_SUCCESS == (rv = md_util_path_merge(&from, ptemp, dir, name, NULL))
+        && APR_SUCCESS == (rv = md_util_path_merge(&to, ptemp, dir, MD_FN_PRIVKEY, NULL))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "renaming %s/%s to %s", 
+                      dir, name, MD_FN_PRIVKEY);
+        return apr_file_rename(from, to, ptemp);
+    }
+    return rv;
+}
+
+static apr_status_t mk_pubcert(void *baton, apr_pool_t *p, apr_pool_t *ptemp, 
+                               const char *dir, const char *name, 
+                               apr_filetype_e ftype)
+{
+    md_cert_t *cert;
+    apr_array_header_t *chain, *pubcert;
+    const char *fname, *fpubcert;
+    apr_status_t rv = APR_SUCCESS;
+    
+    if (   APR_SUCCESS == (rv = md_util_path_merge(&fpubcert, ptemp, dir, MD_FN_PUBCERT, NULL))
+        && APR_STATUS_IS_ENOENT((rv = md_chain_fload(&pubcert, ptemp, fpubcert)))
+        && APR_SUCCESS == (rv = md_util_path_merge(&fname, ptemp, dir, name, NULL))
+        && APR_SUCCESS == (rv = md_cert_fload(&cert, ptemp, fname))
+        && APR_SUCCESS == (rv = md_util_path_merge(&fname, ptemp, dir, MD_FN_CHAIN, NULL))) {
+        
+        rv = md_chain_fload(&chain, ptemp, fname);
+        if (APR_STATUS_IS_ENOENT(rv)) {
+            chain = apr_array_make(ptemp, 1, sizeof(md_cert_t*));
+            rv = APR_SUCCESS;
+        }
+        if (APR_SUCCESS == rv) {
+            pubcert = apr_array_make(ptemp, chain->nelts + 1, sizeof(md_cert_t*));
+            APR_ARRAY_PUSH(pubcert, md_cert_t *) = cert;
+            apr_array_cat(pubcert, chain);
+            rv = md_chain_fsave(pubcert, ptemp, fpubcert, MD_FPROT_F_UONLY);
+        }
+    }
+    return rv;
+}
+
+static apr_status_t upgrade_from_1_0(md_store_fs_t *s_fs, apr_pool_t *p, apr_pool_t *ptemp)
+{
+    md_store_group_t g;
+    apr_status_t rv = APR_SUCCESS;
+    
+    /* Migrate pkey.pem -> privkey.pem */
+    for (g = MD_SG_NONE; g < MD_SG_COUNT && APR_SUCCESS == rv; ++g) {
+        rv = md_util_files_do(rename_pkey, s_fs, p, s_fs->base, 
+                              md_store_group_name(g), "*", "pkey.pem", NULL);
+    }
+    /* Generate fullcert.pem from cert.pem and chain.pem where missing */
+    rv = md_util_files_do(mk_pubcert, s_fs, p, s_fs->base, 
+                          md_store_group_name(MD_SG_DOMAINS), "*", MD_FN_CERT, NULL);
+    rv = md_util_files_do(mk_pubcert, s_fs, p, s_fs->base, 
+                          md_store_group_name(MD_SG_ARCHIVE), "*", MD_FN_CERT, NULL);
+    
     return rv;
 }
 
@@ -131,7 +185,7 @@ static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname,
                                     apr_pool_t *p, apr_pool_t *ptemp)
 {
     md_json_t *json;
-    const char *key64;
+    const char *key64, *key;
     apr_status_t rv;
     double store_version;
     
@@ -145,22 +199,34 @@ static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname,
             md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "version too new: %s", store_version);
             return APR_EINVAL;
         }
-        else if (store_version > MD_STORE_VERSION) {
-            /* migrate future store version changes */
-        } 
-        
+
         key64 = md_json_dups(p, json, MD_KEY_KEY, NULL);
         if (!key64) {
             md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "missing key: %s", MD_KEY_KEY);
             return APR_EINVAL;
         }
         
-        s_fs->key_len = md_util_base64url_decode((const char **)&s_fs->key, key64, p);
-        if (s_fs->key_len < FS_STORE_KLEN) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "key too short: %d", s_fs->key_len);
+        s_fs->key_len = md_util_base64url_decode(&key, key64, p);
+        s_fs->key = (const unsigned char*)key;
+        if (s_fs->key_len != FS_STORE_KLEN) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "key length unexpected: %d", 
+                          s_fs->key_len);
             return APR_EINVAL;
         }
-        s_fs->dupkey = apr_pmemdup(p, s_fs->key, FS_STORE_KLEN);
+
+        /* Need to migrate format? */
+        if (store_version < MD_STORE_VERSION) {
+            if (store_version <= 1.0) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "migrating store v1.0 -> v1.1");
+                rv = upgrade_from_1_0(s_fs, p, ptemp);
+            }
+            
+            if (APR_SUCCESS == rv) {
+                md_json_setn(MD_STORE_VERSION, json, MD_KEY_STORE, MD_KEY_VERSION, NULL);
+                rv = md_json_freplace(json, ptemp, MD_JSON_FMT_INDENT, fname, MD_FPROT_F_UONLY);
+           }
+           md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, "migrated store");
+        } 
     }
     return rv;
 }
@@ -320,7 +386,6 @@ static void get_pass(const char **ppass, apr_size_t *plen,
         *plen = 0;
     }
     else {
-        assert(memcmp(s_fs->key, s_fs->dupkey, FS_STORE_KLEN) == 0);
         *ppass = (const char *)s_fs->key;
         *plen = s_fs->key_len;
     }
@@ -374,10 +439,10 @@ static apr_status_t pfs_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
     void **pvalue;
     apr_status_t rv;
     
-    group = va_arg(ap, int);
+    group = (md_store_group_t)va_arg(ap, int);
     name = va_arg(ap, const char *);
     aspect = va_arg(ap, const char *);
-    vtype = va_arg(ap, int);
+    vtype = (md_store_vtype_t)va_arg(ap, int);
     pvalue= va_arg(ap, void **);
         
     rv = fs_get_fname(&fpath, &s_fs->s, group, name, aspect, ptemp);
@@ -438,8 +503,8 @@ static apr_status_t pfs_is_newer(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
     int *pnewer;
     apr_status_t rv;
     
-    group1 = va_arg(ap, int);
-    group2 = va_arg(ap, int);
+    group1 = (md_store_group_t)va_arg(ap, int);
+    group2 = (md_store_group_t)va_arg(ap, int);
     name = va_arg(ap, const char*);
     aspect = va_arg(ap, const char*);
     pnewer = va_arg(ap, int*);
@@ -483,10 +548,10 @@ static apr_status_t pfs_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
     const char *pass;
     apr_size_t pass_len;
     
-    group = va_arg(ap, int);
+    group = (md_store_group_t)va_arg(ap, int);
     name = va_arg(ap, const char*);
     aspect = va_arg(ap, const char*);
-    vtype = va_arg(ap, int);
+    vtype = (md_store_vtype_t)va_arg(ap, int);
     value = va_arg(ap, void *);
     create = va_arg(ap, int);
     
@@ -540,7 +605,7 @@ static apr_status_t pfs_remove(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va
     apr_finfo_t info;
     md_store_group_t group;
     
-    group = va_arg(ap, int);
+    group = (md_store_group_t)va_arg(ap, int);
     name = va_arg(ap, const char*);
     aspect = va_arg(ap, const char *);
     force = va_arg(ap, int);
@@ -599,7 +664,7 @@ static apr_status_t pfs_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_
     md_store_group_t group;
     apr_status_t rv;
     
-    group = va_arg(ap, int);
+    group = (md_store_group_t)va_arg(ap, int);
     name = va_arg(ap, const char*);
     
     groupname = md_store_group_name(group);
@@ -684,8 +749,8 @@ static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
     int archive;
     apr_status_t rv;
     
-    from = va_arg(ap, int);
-    to = va_arg(ap, int);
+    from = (md_store_group_t)va_arg(ap, int);
+    to = (md_store_group_t)va_arg(ap, int);
     name = va_arg(ap, const char*);
     archive = va_arg(ap, int);
     
diff --git a/modules/md/md_util.c b/modules/md/md_util.c
index 01a0dfe945..8958a5d637 100644
--- a/modules/md/md_util.c
+++ b/modules/md/md_util.c
@@ -73,7 +73,7 @@ char *md_util_str_tolower(char *s)
 {
     char *orig = s;
     while (*s) {
-        *s = apr_tolower(*s);
+        *s = (char)apr_tolower(*s);
         ++s;
     }
     return orig;
@@ -759,26 +759,28 @@ const char *md_print_duration(apr_pool_t *p, apr_interval_time_t duration)
 
 /* base64 url encoding ****************************************************************************/
 
-static const int BASE64URL_UINT6[] = {
+#define N6 (unsigned int)-1
+
+static const unsigned int BASE64URL_UINT6[] = {
 /*   0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f        */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  0 */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  1 */ 
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, /*  2 */
-    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /*  3 */ 
-    -1, 0,  1,  2,  3,  4,  5,  6,   7,  8,  9, 10, 11, 12, 13, 14, /*  4 */
-    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, /*  5 */
-    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /*  6 */
-    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /*  7 */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  8 */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  9 */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  a */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  b */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  c */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  d */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  e */
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1  /*  f */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  0 */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  1 */ 
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, 62, N6, N6, /*  2 */
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /*  3 */ 
+    N6, 0,  1,  2,  3,  4,  5,  6,   7,  8,  9, 10, 11, 12, 13, 14, /*  4 */
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, N6, N6, N6, N6, 63, /*  5 */
+    N6, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /*  6 */
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, N6, N6, N6, N6, N6, /*  7 */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  8 */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  9 */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  a */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  b */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  c */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  d */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /*  e */
+    N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6  /*  f */
 };
-static const char BASE64URL_CHARS[] = {
+static const unsigned char BASE64URL_CHARS[] = {
     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /*  0 -  9 */
     'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 10 - 19 */
     'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', /* 20 - 29 */
@@ -788,21 +790,23 @@ static const char BASE64URL_CHARS[] = {
     '8', '9', '-', '_', ' ', ' ', ' ', ' ', ' ', ' ', /* 60 - 69 */
 };
 
+#define BASE64URL_CHAR(x)    BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ]
+   
 apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded, 
                                     apr_pool_t *pool)
 {
     const unsigned char *e = (const unsigned char *)encoded;
     const unsigned char *p = e;
     unsigned char *d;
-    int n;
-    apr_size_t len, mlen, remain, i;
+    unsigned int n;
+    long len, mlen, remain, i;
     
-    while (*p && BASE64URL_UINT6[ *p ] != -1) {
+    while (*p && BASE64URL_UINT6[ *p ] != N6) {
         ++p;
     }
     len = p - e;
     mlen = (len/4)*4;
-    *decoded = apr_pcalloc(pool, len+1);
+    *decoded = apr_pcalloc(pool, (apr_size_t)len + 1);
     
     i = 0;
     d = (unsigned char*)*decoded;
@@ -811,60 +815,59 @@ apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded,
              (BASE64URL_UINT6[ e[i+1] ] << 12) +
              (BASE64URL_UINT6[ e[i+2] ] << 6) +
              (BASE64URL_UINT6[ e[i+3] ]));
-        *d++ = n >> 16;
-        *d++ = n >> 8 & 0xffu;
-        *d++ = n & 0xffu;
+        *d++ = (unsigned char)(n >> 16);
+        *d++ = (unsigned char)(n >> 8 & 0xffu);
+        *d++ = (unsigned char)(n & 0xffu);
     }
     remain = len - mlen;
     switch (remain) {
         case 2:
             n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
                  (BASE64URL_UINT6[ e[mlen+1] ] << 12));
-            *d++ = n >> 16;
+            *d++ = (unsigned char)(n >> 16);
             remain = 1;
             break;
         case 3:
             n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
                  (BASE64URL_UINT6[ e[mlen+1] ] << 12) +
                  (BASE64URL_UINT6[ e[mlen+2] ] << 6));
-            *d++ = n >> 16;
-            *d++ = n >> 8 & 0xffu;
+            *d++ = (unsigned char)(n >> 16);
+            *d++ = (unsigned char)(n >> 8 & 0xffu);
             remain = 2;
             break;
         default: /* do nothing */
             break;
     }
-    return mlen/4*3 + remain;
+    return (apr_size_t)(mlen/4*3 + remain);
 }
 
-const char *md_util_base64url_encode(const char *data, 
-                                     apr_size_t dlen, apr_pool_t *pool)
+const char *md_util_base64url_encode(const char *data, apr_size_t dlen, apr_pool_t *pool)
 {
-    long i, len = (int)dlen;
+    int i, len = (int)dlen;
     apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */
     const unsigned char *udata = (const unsigned char*)data;
-    char *enc, *p = apr_pcalloc(pool, slen);
+    unsigned char *enc, *p = apr_pcalloc(pool, slen);
     
     enc = p;
     for (i = 0; i < len-2; i+= 3) {
-        *p++ = BASE64URL_CHARS[ (udata[i] >> 2) & 0x3fu ];
-        *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + (udata[i+1] >> 4)) & 0x3fu ];
-        *p++ = BASE64URL_CHARS[ ((udata[i+1] << 2) + (udata[i+2] >> 6)) & 0x3fu ];
-        *p++ = BASE64URL_CHARS[ udata[i+2] & 0x3fu ];
+        *p++ = BASE64URL_CHAR( (udata[i]   >> 2) );
+        *p++ = BASE64URL_CHAR( (udata[i]   << 4) + (udata[i+1] >> 4) );
+        *p++ = BASE64URL_CHAR( (udata[i+1] << 2) + (udata[i+2] >> 6) );
+        *p++ = BASE64URL_CHAR( (udata[i+2]) );
     }
     
     if (i < len) {
-        *p++ = BASE64URL_CHARS[ (udata[i] >> 2) & 0x3fu ];
+        *p++ = BASE64URL_CHAR( (udata[i] >> 2) );
         if (i == (len - 1)) {
-            *p++ = BASE64URL_CHARS[ (udata[i] << 4) & 0x3fu ];
+            *p++ = BASE64URL_CHARS[ ((unsigned int)udata[i] << 4) & 0x3fu ];
         }
         else {
-            *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + (udata[i+1] >> 4)) & 0x3fu ];
-            *p++ = BASE64URL_CHARS[ (udata[i+1] << 2) & 0x3fu ];
+            *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) );
+            *p++ = BASE64URL_CHAR( (udata[i+1] << 2) );
         }
     }
     *p++ = '\0';
-    return enc;
+    return (char *)enc;
 }
 
 /*******************************************************************************
@@ -874,12 +877,12 @@ const char *md_util_base64url_encode(const char *data,
 typedef struct {
     const char *s;
     apr_size_t slen;
-    int i;
-    int link_start;
+    apr_size_t i;
+    apr_size_t link_start;
     apr_size_t link_len;
-    int pn_start;
+    apr_size_t pn_start;
     apr_size_t pn_len;
-    int pv_start;
+    apr_size_t pv_start;
     apr_size_t pv_len;
 } link_ctx;
 
@@ -960,9 +963,9 @@ static int skip_nonws(link_ctx *ctx)
     return (ctx->i < ctx->slen);
 }
 
-static int find_chr(link_ctx *ctx, char c, int *pidx)
+static unsigned int find_chr(link_ctx *ctx, char c, apr_size_t *pidx)
 {
-    int j;
+    apr_size_t j;
     for (j = ctx->i; j < ctx->slen; ++j) {
         if (ctx->s[j] == c) {
             *pidx = j;
@@ -984,7 +987,7 @@ static int read_chr(link_ctx *ctx, char c)
 static int skip_qstring(link_ctx *ctx)
 {
     if (skip_ws(ctx) && read_chr(ctx, '\"')) {
-        int end;
+        apr_size_t end;
         if (find_chr(ctx, '\"', &end)) {
             ctx->i = end + 1;
             return 1;
@@ -996,7 +999,7 @@ static int skip_qstring(link_ctx *ctx)
 static int skip_ptoken(link_ctx *ctx)
 {
     if (skip_ws(ctx)) {
-        int i;
+        apr_size_t i;
         for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
             /* nop */
         }
@@ -1013,7 +1016,7 @@ static int read_link(link_ctx *ctx)
 {
     ctx->link_start = ctx->link_len = 0;
     if (skip_ws(ctx) && read_chr(ctx, '<')) {
-        int end;
+        apr_size_t end;
         if (find_chr(ctx, '>', &end)) {
             ctx->link_start = ctx->i;
             ctx->link_len = end - ctx->link_start;
@@ -1027,7 +1030,7 @@ static int read_link(link_ctx *ctx)
 static int skip_pname(link_ctx *ctx)
 {
     if (skip_ws(ctx)) {
-        int i;
+        apr_size_t i;
         for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
             /* nop */
         }
@@ -1068,7 +1071,7 @@ static int skip_param(link_ctx *ctx)
 
 static int pv_contains(link_ctx *ctx, const char *s)
 {
-    int pvstart = ctx->pv_start;
+    apr_size_t pvstart = ctx->pv_start;
     apr_size_t pvlen = ctx->pv_len;
     
     if (ctx->s[pvstart] == '\"' && pvlen > 1) {
@@ -1078,7 +1081,7 @@ static int pv_contains(link_ctx *ctx, const char *s)
     if (pvlen > 0) {
         apr_size_t slen = strlen(s);
         link_ctx pvctx;
-        int i;
+        apr_size_t i;
         
         memset(&pvctx, 0, sizeof(pvctx));
         pvctx.s = ctx->s + pvstart;
@@ -1148,7 +1151,7 @@ static int find_url(void *baton, const char *key, const char *value)
         
         memset(&ctx, 0, sizeof(ctx));
         ctx.s = value;
-        ctx.slen = (int)strlen(value);
+        ctx.slen = strlen(value);
         
         while (read_link(&ctx)) {
             while (skip_param(&ctx)) {
diff --git a/modules/md/md_version.h b/modules/md/md_version.h
index 41474e853e..306f40fec7 100644
--- a/modules/md/md_version.h
+++ b/modules/md/md_version.h
@@ -26,7 +26,7 @@
  * @macro
  * Version number of the md module as c string
  */
-#define MOD_MD_VERSION "0.7.0-git"
+#define MOD_MD_VERSION "0.8.1"
 
 /**
  * @macro
@@ -34,7 +34,7 @@
  * release. This is a 24 bit number with 8 bits for major number, 8 bits
  * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
  */
-#define MOD_MD_VERSION_NUM 0x000700
+#define MOD_MD_VERSION_NUM 0x000801
 
 #define MD_EXPERIMENTAL 0
 #define MD_ACME_DEF_URL    "https://acme-v01.api.letsencrypt.org/directory"
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c
index 075dbb35c4..44025e4c8c 100644
--- a/modules/md/mod_md.c
+++ b/modules/md/mod_md.c
@@ -78,6 +78,9 @@ static void md_merge_srv(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
     if (md->drive_mode == MD_DRIVE_DEFAULT) {
         md->drive_mode = md_config_geti(md->sc, MD_CONFIG_DRIVE_MODE);
     }
+    if (md->renew_norm <= 0) {
+        md->renew_norm = md_config_get_interval(md->sc, MD_CONFIG_RENEW_NORM);
+    }
     if (md->renew_window <= 0) {
         md->renew_window = md_config_get_interval(md->sc, MD_CONFIG_RENEW_WINDOW);
     }
@@ -87,6 +90,10 @@ static void md_merge_srv(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
     if (!md->ca_challenges && md->sc->ca_challenges) {
         md->ca_challenges = apr_array_copy(p, md->sc->ca_challenges);
     }        
+    if (!md->pkey_spec) {
+        md->pkey_spec = md->sc->pkey_spec;
+        
+    }
 }
 
 static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, apr_pool_t *p)
@@ -460,6 +467,9 @@ static apr_status_t drive_md(md_watchdog *wd, md_t *md, apr_pool_t *ptemp)
     int errored, renew;
     char ts[APR_RFC822_DATE_LEN];
     
+    if (md->state == MD_S_MISSING) {
+        rv = APR_INCOMPLETE;
+    }
     if (md->state == MD_S_COMPLETE && !md->expires) {
         /* This is our indicator that we did already renewed this managed domain
          * successfully and only wait on the next restart for it to activate */
@@ -508,7 +518,7 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
     md_watchdog *wd = baton;
     apr_status_t rv = APR_SUCCESS;
     md_t *md;
-    apr_interval_time_t interval, now;
+    apr_time_t next_run, now;
     int i;
     
     switch (state) {
@@ -519,9 +529,6 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
         case AP_WATCHDOG_STATE_RUNNING:
             assert(wd->reg);
             
-            /* normally, we'd like to run at least twice a day */
-            interval = apr_time_from_sec(MD_SECS_PER_DAY / 2);
-
             wd->all_valid = 1;
             wd->valid_not_before = 0;
             wd->processed_count = 0;
@@ -536,7 +543,13 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
             for (i = 0; i < wd->mds->nelts; ++i) {
                 md = APR_ARRAY_IDX(wd->mds, i, md_t *);
                 
-                if (APR_SUCCESS != (rv = drive_md(wd, md, ptemp))) {
+                rv = drive_md(wd, md, ptemp);
+                
+                if (APR_STATUS_IS_INCOMPLETE(rv)) {
+                    /* configuration not complete, this MD cannot be driven further */
+                    wd->all_valid = 0;
+                }
+                else if (APR_SUCCESS != rv) {
                     wd->all_valid = 0;
                     ++wd->error_count;
                     ap_log_error( APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO(10056) 
@@ -546,50 +559,57 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
 
             /* Determine when we want to run next */
             wd->error_runs = wd->error_count? (wd->error_runs + 1) : 0;
+
             if (wd->all_valid) {
-                now = apr_time_now();
-                if (wd->next_valid > now && (wd->next_valid - now < interval)) {
-                    interval = wd->next_valid - now;
-                    ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, 
-                                 "Delaying activation of %d Managed Domain%s by %s", 
-                                 wd->processed_count, (wd->processed_count > 1)? "s have" : " has",
-                                 md_print_duration(ptemp, interval));
-                }
-                else {
-                    ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, 
-                                 "all managed domains are valid");
-                }
+                ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, "all managed domains are valid");
             }
-            else {
-                /* back off duration, depending on the errors we encounter in a row */
-                interval = apr_time_from_sec(5 << (wd->error_runs - 1));
-                if (interval > apr_time_from_sec(60*60)) {
-                    interval = apr_time_from_sec(60*60);
-                }
-                ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10057) 
-                             "encountered errors for the %d. time, next run in %d seconds",
-                             wd->error_runs, (int)apr_time_sec(interval));
+            else if (wd->error_count == 0) {
+                ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO() 
+                             "all managed domains driven as far as possible");
+            }
+            
+            now = apr_time_now();
+            /* normally, we'd like to run at least twice a day */
+            next_run = now + apr_time_from_sec(MD_SECS_PER_DAY / 2);
+            
+            /* Unless we know of an MD change before that */
+            if (wd->next_change > 0 && wd->next_change < next_run) {
+                next_run = wd->next_change;
             }
             
-            /* We follow the chosen min_interval for re-evaluation, unless we
-             * know of a change (renewal) that happens before that. */
-            if (wd->next_change) {
-                apr_interval_time_t until_next = wd->next_change - apr_time_now();
-                if (until_next < interval) {
-                    interval = until_next;
+            /* Or have to activate a new cert even before that */
+            if (wd->next_valid > now && wd->next_valid < next_run) {
+                next_run = wd->next_valid;
+                ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, 
+                             "Delaying activation of %d Managed Domain%s by %s", 
+                             wd->processed_count, (wd->processed_count > 1)? "s have" : " has",
+                             md_print_duration(ptemp, next_run - now));
+            }
+            
+            /* Or encountered errors and like to retry even before that */
+            if (wd->error_count > 0) {
+                apr_interval_time_t delay;
+                
+                /* back off duration, depending on the errors we encounter in a row */
+                delay = apr_time_from_sec(5 << (wd->error_runs - 1));
+                if (delay > apr_time_from_sec(60*60)) {
+                    delay = apr_time_from_sec(60*60);
+                }
+                if (now + delay < next_run) {
+                    ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10057) 
+                                 "encountered errors for the %d. time, next try by %s",
+                                 wd->error_runs, md_print_duration(ptemp, delay));
+                    next_run = now + delay;
                 }
             }
             
-            /* Set when we'd like to be run next time. 
-             * TODO: it seems that this is really only ticking down when the server
-             * runs. When you wake up a hibernated machine, the watchdog will not run right away 
-             */
             if (APLOGdebug(wd->s)) {
-                ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, "next run in %s",
-                             md_print_duration(ptemp, interval));
+                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO()
+                             "next run in %s", md_print_duration(ptemp, next_run - now));
             }
-            wd_set_interval(wd->watchdog, interval, wd, run_watchdog);
+            wd_set_interval(wd->watchdog, next_run - now, wd, run_watchdog);
             break;
+            
         case AP_WATCHDOG_STATE_STOPPING:
             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10058)
                          "md watchdog stopping");
@@ -684,7 +704,7 @@ static apr_status_t start_watchdog(apr_array_header_t *names, apr_pool_t *p,
     if (!wd->mds->nelts) {
         ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10065)
                      "no managed domain in state to drive, no watchdog needed, "
-                     "will check again on next server restart");
+                     "will check again on next server (graceful) restart");
         apr_pool_destroy(wd->p);
         return APR_SUCCESS;
     }
diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c
index 1b39d81d70..17fbbe3f2c 100644
--- a/modules/md/mod_md_config.c
+++ b/modules/md/mod_md_config.c
@@ -25,6 +25,7 @@
 #include <http_vhost.h>
 
 #include "md.h"
+#include "md_crypt.h"
 #include "md_util.h"
 #include "mod_md_private.h"
 #include "mod_md_config.h"
@@ -39,6 +40,7 @@
 #define MD_CMD_MEMBER         "MDMember"
 #define MD_CMD_MEMBERS        "MDMembers"
 #define MD_CMD_PORTMAP        "MDPortMap"
+#define MD_CMD_PKEYS          "MDPrivateKeys"
 #define MD_CMD_RENEWWINDOW    "MDRenewWindow"
 #define MD_CMD_STOREDIR       "MDStoreDir"
 
@@ -65,8 +67,9 @@ static md_srv_conf_t defconf = {
     1,
     MD_DRIVE_AUTO,
     0,
-    apr_time_from_sec(14 * MD_SECS_PER_DAY),
-
+    NULL, 
+    apr_time_from_sec(90 * MD_SECS_PER_DAY), /* If the cert lifetime were 90 days, renew */
+    apr_time_from_sec(30 * MD_SECS_PER_DAY), /* 30 days before. Adjust to actual lifetime */
     MD_ACME_DEF_URL,
     "ACME",
     NULL,
@@ -109,6 +112,8 @@ static void srv_conf_props_clear(md_srv_conf_t *sc)
     sc->transitive = DEF_VAL;
     sc->drive_mode = DEF_VAL;
     sc->must_staple = DEF_VAL;
+    sc->pkey_spec = NULL;
+    sc->renew_norm = DEF_VAL;
     sc->renew_window = DEF_VAL;
     sc->ca_url = NULL;
     sc->ca_proto = NULL;
@@ -121,6 +126,8 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
     to->transitive = from->transitive;
     to->drive_mode = from->drive_mode;
     to->must_staple = from->must_staple;
+    to->pkey_spec = from->pkey_spec;
+    to->renew_norm = from->renew_norm;
     to->renew_window = from->renew_window;
     to->ca_url = from->ca_url;
     to->ca_proto = from->ca_proto;
@@ -133,6 +140,8 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t
     if (from->transitive != DEF_VAL) md->transitive = from->transitive;
     if (from->drive_mode != DEF_VAL) md->drive_mode = from->drive_mode;
     if (from->must_staple != DEF_VAL) md->must_staple = from->must_staple;
+    if (from->pkey_spec) md->pkey_spec = from->pkey_spec;
+    if (from->renew_norm != DEF_VAL) md->renew_norm = from->renew_norm;
     if (from->renew_window != DEF_VAL) md->renew_window = from->renew_window;
 
     if (from->ca_url) md->ca_url = from->ca_url;
@@ -166,6 +175,9 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
 
     nsc->transitive = (add->transitive != DEF_VAL)? add->transitive : base->transitive;
     nsc->drive_mode = (add->drive_mode != DEF_VAL)? add->drive_mode : base->drive_mode;
+    nsc->must_staple = (add->must_staple != DEF_VAL)? add->must_staple : base->must_staple;
+    nsc->pkey_spec = add->pkey_spec? add->pkey_spec : base->pkey_spec;
+    nsc->renew_window = (add->renew_norm != DEF_VAL)? add->renew_norm : base->renew_norm;
     nsc->renew_window = (add->renew_window != DEF_VAL)? add->renew_window : base->renew_window;
 
     nsc->ca_url = add->ca_url? add->ca_url : base->ca_url;
@@ -242,7 +254,7 @@ static const char *md_config_sec_start(cmd_parms *cmd, void *mconfig, const char
         return  MD_CMD_MD_SECTION "> directive missing closing '>'";
     }
 
-    arg = apr_pstrndup(cmd->pool, arg, endp-arg);
+    arg = apr_pstrndup(cmd->pool, arg, (apr_size_t)(endp-arg));
     if (!arg || !*arg) {
         return MD_CMD_MD_SECTION " > section must specify a unique domain name";
     }
@@ -444,23 +456,54 @@ static apr_status_t duration_parse(const char *value, apr_interval_time_t *ptime
     return rv;
 }
 
+static apr_status_t percentage_parse(const char *value, int *ppercent)
+{
+    char *endp;
+    apr_int64_t n;
+    
+    n = apr_strtoi64(value, &endp, 10);
+    if (errno) {
+        return errno;
+    }
+    if (*endp == '%') {
+        if (n < 0 || n >= 100) {
+            return APR_BADARG;
+        }
+        *ppercent = (int)n;
+        return APR_SUCCESS;
+    }
+    return APR_EINVAL;
+}
+
 static const char *md_config_set_renew_window(cmd_parms *cmd, void *dc, const char *value)
 {
     md_srv_conf_t *config = md_config_get(cmd->server);
     const char *err;
     apr_interval_time_t timeout;
-
-    /* Inspired by http_core.c */
-    if (duration_parse(value, &timeout, "d") != APR_SUCCESS) {
-        return "MDRenewWindow has wrong format";
-    }
-        
+    int percent;
+    
     if (!inside_section(cmd, MD_CMD_MD_SECTION)
         && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
         return err;
     }
-    config->renew_window = timeout;
-    return NULL;
+
+    /* Inspired by http_core.c */
+    if (duration_parse(value, &timeout, "d") == APR_SUCCESS) {
+        config->renew_norm = 0;
+        config->renew_window = timeout;
+        return NULL;
+    }
+    else {
+        switch (percentage_parse(value, &percent)) {
+            case APR_SUCCESS:
+                config->renew_norm = 100;
+                config->renew_window = percent;
+                return NULL;
+            case APR_BADARG:
+                return "MDRenewWindow as percent must be less than 100";
+        }
+    }
+    return "MDRenewWindow has unrecognized format";
 }
 
 static const char *md_config_set_store_dir(cmd_parms *cmd, void *arg, const char *value)
@@ -555,6 +598,57 @@ static const char *md_config_set_cha_tyes(cmd_parms *cmd, void *dc,
     return NULL;
 }
 
+static const char *md_config_set_pkeys(cmd_parms *cmd, void *arg, 
+                                       int argc, char *const argv[])
+{
+    md_srv_conf_t *config = md_config_get(cmd->server);
+    const char *err, *ptype;
+    apr_int64_t bits;
+    
+    if (!inside_section(cmd, MD_CMD_MD_SECTION)
+        && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    if (argc <= 0) {
+        return "needs to specify the private key type";
+    }
+    
+    ptype = argv[0];
+    if (!apr_strnatcasecmp("Default", ptype)) {
+        if (argc > 1) {
+            return "type 'Default' takes no parameter";
+        }
+        if (!config->pkey_spec) {
+            config->pkey_spec = apr_pcalloc(cmd->pool, sizeof(*config->pkey_spec));
+        }
+        config->pkey_spec->type = MD_PKEY_TYPE_DEFAULT;
+        return NULL;
+    }
+    else if (!apr_strnatcasecmp("RSA", ptype)) {
+        if (argc == 1) {
+            bits = MD_PKEY_RSA_BITS_DEF;
+        }
+        else if (argc == 2) {
+            bits = (int)apr_atoi64(argv[1]);
+            if (bits < 2048 || bits >= INT_MAX) {
+                return "must be a 2048 or higher in order to be considered safe. "
+                "Too large a value will slow down everything. Larger then 4096 probably does "
+                "not make sense unless quantum cryptography really changes spin.";
+            }
+        }
+        else {
+            return "key type 'RSA' has only one optinal parameter, the number of bits";
+        }
+
+        if (!config->pkey_spec) {
+            config->pkey_spec = apr_pcalloc(cmd->pool, sizeof(*config->pkey_spec));
+        }
+        config->pkey_spec->type = MD_PKEY_TYPE_RSA;
+        config->pkey_spec->params.rsa.bits = (unsigned int)bits;
+        return NULL;
+    }
+    return apr_pstrcat(cmd->pool, "unsupported private key type \"", ptype, "\"", NULL);
+}
 
 const command_rec md_cmds[] = {
     AP_INIT_TAKE1(     MD_CMD_CA, md_config_set_ca, NULL, RSRC_CONF, 
@@ -582,6 +676,8 @@ const command_rec md_cmds[] = {
                   "to indicate that the server port 8000 is reachable as port 80 from the "
                   "internet. Use 80:- to indicate that port 80 is not reachable from "
                   "the outside."),
+    AP_INIT_TAKE_ARGV( MD_CMD_PKEYS, md_config_set_pkeys, NULL, RSRC_CONF, 
+                  "set the type and parameters for private key generation"),
     AP_INIT_TAKE1(     MD_CMD_STOREDIR, md_config_set_store_dir, NULL, RSRC_CONF, 
                   "the directory for file system storage of managed domain data."),
     AP_INIT_TAKE1(     MD_CMD_RENEWWINDOW, md_config_set_renew_window, NULL, RSRC_CONF, 
@@ -654,6 +750,8 @@ int md_config_geti(const md_srv_conf_t *sc, md_config_var_t var)
 apr_interval_time_t md_config_get_interval(const md_srv_conf_t *sc, md_config_var_t var)
 {
     switch (var) {
+        case MD_CONFIG_RENEW_NORM:
+            return (sc->renew_norm != DEF_VAL)? sc->renew_norm : defconf.renew_norm;
         case MD_CONFIG_RENEW_WINDOW:
             return (sc->renew_window != DEF_VAL)? sc->renew_window : defconf.renew_window;
         default:
diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h
index 4996b5295f..e5dc4aa6fc 100644
--- a/modules/md/mod_md_config.h
+++ b/modules/md/mod_md_config.h
@@ -17,6 +17,7 @@
 #define mod_md_md_config_h
 
 struct md_store_t;
+struct md_pkey_spec_t;
 
 typedef enum {
     MD_CONFIG_CA_URL,
@@ -26,6 +27,7 @@ typedef enum {
     MD_CONFIG_DRIVE_MODE,
     MD_CONFIG_LOCAL_80,
     MD_CONFIG_LOCAL_443,
+    MD_CONFIG_RENEW_NORM,
     MD_CONFIG_RENEW_WINDOW,
     MD_CONFIG_TRANSITIVE,
 } md_config_var_t;
@@ -51,6 +53,10 @@ typedef struct md_srv_conf_t {
     int transitive;                    /* != 0 iff VirtualHost names/aliases are auto-added */
     int drive_mode;                    /* mode of obtaining credentials */
     int must_staple;                   /* certificates should set the OCSP Must Staple extension */
+    struct md_pkey_spec_t *pkey_spec;  /* specification for generating private keys */
+    apr_interval_time_t renew_norm;    /* If > 0, use as normalizing value for cert lifetime
+                                        * Example: renew_norm=90d renew_win=30d, cert lives
+                                        * for 12 days => renewal 4 days before */
     apr_interval_time_t renew_window;  /* time before expiration that starts renewal */
     
     const char *ca_url;                /* url of CA certificate service */
diff --git a/modules/md/mod_md_os.c b/modules/md/mod_md_os.c
index 3c599e17da..4b7cae7bab 100644
--- a/modules/md/mod_md_os.c
+++ b/modules/md/mod_md_os.c
@@ -34,7 +34,7 @@
 #include "md_util.h"
 #include "mod_md_os.h"
 
-apr_status_t md_try_chown(const char *fname, int uid, int gid, apr_pool_t *p)
+apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p)
 {
 #if AP_NEED_SET_MUTEX_PERMS
     if (-1 == chown(fname, (uid_t)uid, (gid_t)gid)) {
diff --git a/modules/md/mod_md_os.h b/modules/md/mod_md_os.h
index 9f5c2b6e8f..b4b59d8c0f 100644
--- a/modules/md/mod_md_os.h
+++ b/modules/md/mod_md_os.h
@@ -20,7 +20,7 @@
  * Try chown'ing the file/directory. Give id -1 to not change uid/gid.
  * Will return APR_ENOTIMPL on platforms not supporting this operation.
  */
-apr_status_t md_try_chown(const char *fname, int uid, int gid, apr_pool_t *p);
+apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p);
 
 /**
  * Make a file or directory read/write(/searchable) by httpd workers.
diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c
index 79e9a58a30..9b9e451ef7 100644
--- a/modules/ssl/ssl_engine_init.c
+++ b/modules/ssl/ssl_engine_init.c
@@ -1723,8 +1723,9 @@ static apr_status_t ssl_init_server_ctx(server_rec *s,
             else if (APR_STATUS_IS_EAGAIN(rv)) {
                 /* Managed Domain not ready yet. This is not a reason to fail the config */
                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10085)
-                             "Init: (%s) disabling this host for now as certificate/key data "
-                             "for the Managed Domain is incomplete.", ssl_util_vhostid(p, s));
+                             "Init: %s will respond with '503 Service Unavailable' for now. This "
+                             "host is part of a Managed Domain, but no SSL certificate is "
+                             "available (yet).", ssl_util_vhostid(p, s));
                 pks->service_unavailable = 1;
                 return APR_SUCCESS;
             }
-- 
2.40.0