You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2019/06/24 16:04:33 UTC

svn commit: r1862013 [1/8] - in /httpd/httpd/trunk: ./ modules/md/

Author: icing
Date: Mon Jun 24 16:04:32 2019
New Revision: 1862013

URL: http://svn.apache.org/viewvc?rev=1862013&view=rev
Log:
  *) mod_md: bringing over v2.0.6 from github.
     - supports the ACMEv2 protocol
     - supports the new challenge method 'tls-alpn-01' 
     - supports command configuration to setup/teardown 'dns-01' challenges
     - supports wildcard certificates when dns challenges are configured
     - ACMEv2 is the new default and will be used on the next certificate renewal,
       unless another MDCertificateAuthority is configured
     - challenge type 'tls-sni-01' has been removed as CAs do not offer this any longer
     - a domain exposes its status at https://<domain>/.httpd/certificate-status
     - Managed Domains are now in Apache's 'server-status' page
     - A new handler 'md-status' exposes verbose status information in JSON format
     - new directives "MDCertificateFile" and "MDCertificateKeyFile" to configure a
       Managed Domain that uses static files. Auto-renewal is turned off for those.
     - new MDMessageCmd that is invoked on several events: 'renewed', 'expiring' and
       'errored'. New 'MDWarnWindow' directive to configure when expiration warnings
       shall be issued.
     - ACMEv2 endpoints use the GET via empty POST way of accessing resources, see
       announcement by Let's Encrypt: 
       https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380


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

Modified: httpd/httpd/trunk/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CHANGES?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/CHANGES [utf-8] (original)
+++ httpd/httpd/trunk/CHANGES [utf-8] Mon Jun 24 16:04:32 2019
@@ -1,6 +1,27 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.1
 
+  *) mod_md: bringing over v2.0.6 from github.
+     - supports the ACMEv2 protocol
+     - supports the new challenge method 'tls-alpn-01' 
+     - supports command configuration to setup/teardown 'dns-01' challenges
+     - supports wildcard certificates when dns challenges are configured
+     - ACMEv2 is the new default and will be used on the next certificate renewal,
+       unless another MDCertificateAuthority is configured
+     - challenge type 'tls-sni-01' has been removed as CAs do not offer this any longer
+     - a domain exposes its status at https://<domain>/.httpd/certificate-status
+     - Managed Domains are now in Apache's 'server-status' page
+     - A new handler 'md-status' exposes verbose status information in JSON format
+     - new directives "MDCertificateFile" and "MDCertificateKeyFile" to configure a
+       Managed Domain that uses static files. Auto-renewal is turned off for those.
+     - new MDMessageCmd that is invoked on several events: 'renewed', 'expiring' and
+       'errored'. New 'MDWarnWindow' directive to configure when expiration warnings
+       shall be issued.
+     - ACMEv2 endpoints use the GET via empty POST way of accessing resources, see
+       announcement by Let's Encrypt: 
+       https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380
+     [Stefan Eissing]
+
   *) mod_ssl: use OPENSSL_init_ssl() to initialise OpenSSL on versions 1.1+.
      [Graham Leggett]
 

Modified: httpd/httpd/trunk/modules/md/config2.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/config2.m4?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/config2.m4 (original)
+++ httpd/httpd/trunk/modules/md/config2.m4 Mon Jun 24 16:04:32 2019
@@ -140,6 +140,9 @@ md_acme.lo dnl
 md_acme_acct.lo dnl
 md_acme_authz.lo dnl
 md_acme_drive.lo dnl
+md_acmev1_drive.lo dnl
+md_acmev2_drive.lo dnl
+md_acme_order.lo dnl
 md_core.lo dnl
 md_curl.lo dnl
 md_crypt.lo dnl
@@ -147,13 +150,18 @@ md_http.lo dnl
 md_json.lo dnl
 md_jws.lo dnl
 md_log.lo dnl
+md_result.lo dnl
 md_reg.lo dnl
+md_status.lo dnl
 md_store.lo dnl
 md_store_fs.lo dnl
+md_time.lo dnl
 md_util.lo dnl
 mod_md.lo dnl
 mod_md_config.lo dnl
+mod_md_drive.lo dnl
 mod_md_os.lo dnl
+mod_md_status.lo dnl
 "
 
 # Ensure that other modules can pick up mod_md.h

Modified: httpd/httpd/trunk/modules/md/md.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md.h?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md.h (original)
+++ httpd/httpd/trunk/modules/md/md.h Mon Jun 24 16:04:32 2019
@@ -17,6 +17,7 @@
 #ifndef mod_md_md_h
 #define mod_md_md_h
 
+#include "md_time.h"
 #include "md_version.h"
 
 struct apr_array_header_t;
@@ -37,13 +38,19 @@ struct md_pkey_spec_t;
 #define MD_HSTS_HEADER             "Strict-Transport-Security"
 #define MD_HSTS_MAX_AGE_DEFAULT    15768000
 
+#define PROTO_ACME_TLS_1        "acme-tls/1"
+
+#define MD_TIME_LIFE_NORM           (apr_time_from_sec(100 * MD_SECS_PER_DAY))
+#define MD_TIME_RENEW_WINDOW_DEF    (apr_time_from_sec(33 * MD_SECS_PER_DAY))
+#define MD_TIME_WARN_WINDOW_DEF     (apr_time_from_sec(10 * MD_SECS_PER_DAY))
+
 typedef enum {
-    MD_S_UNKNOWN,                   /* MD has not been analysed yet */
-    MD_S_INCOMPLETE,                /* MD is missing necessary information, cannot go live */
-    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_S_UNKNOWN = 0,               /* MD has not been analysed yet */
+    MD_S_INCOMPLETE = 1,            /* MD is missing necessary information, cannot go live */
+    MD_S_COMPLETE = 2,              /* MD has all necessary information, can go live */
+    MD_S_EXPIRED_DEPRECATED = 3,    /* deprecated */
+    MD_S_ERROR = 4,                 /* MD data is flawed, unable to be processed as is */ 
+    MD_S_MISSING_INFORMATION = 5,     /* User has not agreed to ToS */
 } md_state_t;
 
 typedef enum {
@@ -73,11 +80,11 @@ typedef enum {
 } md_store_group_t;
 
 typedef enum {
-    MD_DRIVE_DEFAULT = -1,          /* default value */
-    MD_DRIVE_MANUAL,                /* manually triggered transmission of credentials */
-    MD_DRIVE_AUTO,                  /* automatic process performed by httpd */
-    MD_DRIVE_ALWAYS,                /* always driven by httpd, even if not used in any vhost */
-} md_drive_mode_t;
+    MD_RENEW_DEFAULT = -1,          /* default value */
+    MD_RENEW_MANUAL,                /* manually triggered renewal of certificate */
+    MD_RENEW_AUTO,                  /* automatic process performed by httpd */
+    MD_RENEW_ALWAYS,                /* always renewed by httpd, even if not necessary */
+} md_renew_mode_t;
 
 typedef struct md_t md_t;
 struct md_t {
@@ -88,35 +95,46 @@ struct md_t {
     int transitive;                 /* != 0 iff VirtualHost names/aliases are auto-added */
     md_require_t require_https;     /* Iff https: is required for this MD */
     
-    int drive_mode;                 /* mode of obtaining credentials */
+    int renew_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 md_timeslice_t *renew_window;  /* time before expiration that starts renewal */
+    const md_timeslice_t *warn_window;   /* time before expiration that warnings are sent out */
     
     const char *ca_url;             /* url of CA certificate service */
     const char *ca_proto;           /* protocol used vs CA (e.g. ACME) */
     const char *ca_account;         /* account used at CA */
     const char *ca_agreement;       /* accepted agreement uri between CA and user */ 
     struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */
-
+    const char *cert_file;          /* != NULL iff pubcert file explicitly configured */
+    const char *pkey_file;          /* != NULL iff privkey file explicitly configured */
+    
     md_state_t state;               /* state of this MD */
-    apr_time_t valid_from;          /* When the credentials start to be valid. 0 if unknown */
-    apr_time_t expires;             /* When the credentials expire. 0 if unknown */
-    const char *cert_url;           /* url where cert has been created, remember during drive */ 
+    
+    struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
     
     const struct md_srv_conf_t *sc; /* server config where it was defined or NULL */
     const char *defn_name;          /* config file this MD was defined */
     unsigned defn_line_number;      /* line number of definition */
+    
+    const char *configured_name;    /* name this MD was configured with, if different */
 };
 
 #define MD_KEY_ACCOUNT          "account"
+#define MD_KEY_ACME_TLS_1       "acme-tls/1"
+#define MD_KEY_ACTIVITY         "activity"
 #define MD_KEY_AGREEMENT        "agreement"
+#define MD_KEY_AUTHORIZATIONS   "authorizations"
 #define MD_KEY_BITS             "bits"
 #define MD_KEY_CA               "ca"
 #define MD_KEY_CA_URL           "ca-url"
 #define MD_KEY_CERT             "cert"
+#define MD_KEY_CERT_FILE        "cert-file"
+#define MD_KEY_CERTIFICATE      "certificate"
+#define MD_KEY_CHALLENGE        "challenge"
 #define MD_KEY_CHALLENGES       "challenges"
+#define MD_KEY_CMD_DNS01        "cmd-dns-01"
+#define MD_KEY_COMPLETE         "complete"
 #define MD_KEY_CONTACT          "contact"
 #define MD_KEY_CONTACTS         "contacts"
 #define MD_KEY_CSR              "csr"
@@ -125,46 +143,67 @@ struct md_t {
 #define MD_KEY_DIR              "dir"
 #define MD_KEY_DOMAIN           "domain"
 #define MD_KEY_DOMAINS          "domains"
-#define MD_KEY_DRIVE_MODE       "drive-mode"
+#define MD_KEY_ENTRIES          "entries"
+#define MD_KEY_ERRORED          "errored"
 #define MD_KEY_ERRORS           "errors"
 #define MD_KEY_EXPIRES          "expires"
+#define MD_KEY_FINALIZE         "finalize"
+#define MD_KEY_FINISHED         "finished"
 #define MD_KEY_HTTP             "http"
 #define MD_KEY_HTTPS            "https"
 #define MD_KEY_ID               "id"
 #define MD_KEY_IDENTIFIER       "identifier"
 #define MD_KEY_KEY              "key"
 #define MD_KEY_KEYAUTHZ         "keyAuthorization"
+#define MD_KEY_LAST             "last"
+#define MD_KEY_LAST_RUN         "last-run"
 #define MD_KEY_LOCATION         "location"
+#define MD_KEY_LOG              "log"
+#define MD_KEY_MDS              "managed-domains"
+#define MD_KEY_MESSAGE          "message"
 #define MD_KEY_MUST_STAPLE      "must-staple"
 #define MD_KEY_NAME             "name"
+#define MD_KEY_NEXT_RUN         "next-run"
+#define MD_KEY_NOTIFIED         "notified"
+#define MD_KEY_ORDERS           "orders"
 #define MD_KEY_PERMANENT        "permanent"
 #define MD_KEY_PKEY             "privkey"
-#define MD_KEY_PROCESSED        "processed"
+#define MD_KEY_PKEY_FILE        "pkey-file"
+#define MD_KEY_PROBLEM          "problem"
 #define MD_KEY_PROTO            "proto"
+#define MD_KEY_READY            "ready"
 #define MD_KEY_REGISTRATION     "registration"
 #define MD_KEY_RENEW            "renew"
+#define MD_KEY_RENEW_MODE       "renew-mode"
+#define MD_KEY_RENEWAL          "renewal"
+#define MD_KEY_RENEWING         "renewing"
 #define MD_KEY_RENEW_WINDOW     "renew-window"
 #define MD_KEY_REQUIRE_HTTPS    "require-https"
 #define MD_KEY_RESOURCE         "resource"
+#define MD_KEY_SERIAL           "serial"
+#define MD_KEY_SHA256_FINGERPRINT  "sha256-fingerprint"
 #define MD_KEY_STATE            "state"
 #define MD_KEY_STATUS           "status"
 #define MD_KEY_STORE            "store"
 #define MD_KEY_TEMPORARY        "temporary"
 #define MD_KEY_TOKEN            "token"
+#define MD_KEY_TOTAL            "total"
 #define MD_KEY_TRANSITIVE       "transitive"
 #define MD_KEY_TYPE             "type"
 #define MD_KEY_URL              "url"
 #define MD_KEY_URI              "uri"
-#define MD_KEY_VALID_FROM       "validFrom"
+#define MD_KEY_VALID_FROM       "valid-from"
+#define MD_KEY_VALID_UNTIL      "valid-until"
 #define MD_KEY_VALUE            "value"
 #define MD_KEY_VERSION          "version"
+#define MD_KEY_WHEN             "when"
+#define MD_KEY_WARN_WINDOW      "warn-window"
 
 #define MD_FN_MD                "md.json"
 #define MD_FN_JOB               "job.json"
 #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"
 
 #define MD_FN_FALLBACK_PKEY     "fallback-privkey.pem"
@@ -226,7 +265,7 @@ md_t *md_get_by_dns_overlap(struct apr_a
  * Find the managed domain in the list that, for the given md, 
  * has the same name, or the most number of overlaps in domains
  */
-md_t *md_find_closest_match(apr_array_header_t *mds, const md_t *md);
+md_t *md_find_closest_match(struct apr_array_header_t *mds, const md_t *md);
 
 /**
  * Create and empty md record, structures initialized.
@@ -248,11 +287,6 @@ md_t *md_clone(apr_pool_t *p, const md_t
  */
 md_t *md_copy(apr_pool_t *p, const md_t *src);
 
-/**
- * Create a merged md with the settings of add overlaying the ones from base.
- */
-md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base);
-
 /** 
  * Convert the managed domain into a JSON representation and vice versa. 
  *
@@ -261,30 +295,26 @@ md_t *md_merge(apr_pool_t *p, const md_t
 struct md_json_t *md_to_json (const md_t *md, apr_pool_t *p);
 md_t *md_from_json(struct md_json_t *json, apr_pool_t *p);
 
-/**
- * Determine if MD should renew its cert (if it has one)
- */
-int md_should_renew(const md_t *md);
+int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t* alt_names);
+
+#define LE_ACMEv1_PROD      "https://acme-v01.api.letsencrypt.org/directory"
+#define LE_ACMEv1_STAGING   "https://acme-staging.api.letsencrypt.org/directory"
+
+#define LE_ACMEv2_PROD      "https://acme-v02.api.letsencrypt.org/directory"  
+#define LE_ACMEv2_STAGING   "https://acme-staging-v02.api.letsencrypt.org/directory"
+
 
 /**************************************************************************************************/
 /* domain credentials */
 
-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;
-    int expired;
+typedef struct md_pubcert_t md_pubcert_t;
+struct md_pubcert_t {
+    struct apr_array_header_t *certs;     /* chain of const md_cert*, leaf cert first */
+    struct apr_array_header_t *alt_names; /* alt-names of leaf cert */
+    const char *cert_file;                /* file path of chain */
+    const char *key_file;                 /* file path of key for leaf cert */
 };
 
-/* TODO: not sure this is a good idea, testing some readability and debuggabiltiy of
- * cascaded apr_status_t checks. */
-#define MD_CHK_VARS                 const char *md_chk_
-#define MD_LAST_CHK                 md_chk_
-#define MD_CHK_STEP(c, status, s)   (md_chk_ = s, (void)md_chk_, status == (rv = (c)))
-#define MD_CHK(c, status)           MD_CHK_STEP(c, status, #c)
-#define MD_IS_ERR(c, err)           (md_chk_ = #c, APR_STATUS_IS_##err((rv = (c))))
-#define MD_CHK_SUCCESS(c)           MD_CHK(c, APR_SUCCESS)
-#define MD_OK(c)                    MD_CHK_SUCCESS(c)
+#define MD_OK(c)                    (APR_SUCCESS == (rv = c))
 
 #endif /* mod_md_md_h */

Modified: httpd/httpd/trunk/modules/md/md_acme.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme.c?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme.c (original)
+++ httpd/httpd/trunk/modules/md/md_acme.c Mon Jun 24 16:04:32 2019
@@ -30,6 +30,7 @@
 #include "md_http.h"
 #include "md_log.h"
 #include "md_store.h"
+#include "md_result.h"
 #include "md_util.h"
 #include "md_version.h"
 
@@ -37,7 +38,7 @@
 #include "md_acme_acct.h"
 
 
-static const char *base_product;
+static const char *base_product= "-";
 
 typedef struct acme_problem_status_t acme_problem_status_t;
 
@@ -85,91 +86,6 @@ static apr_status_t problem_status_get(c
     return APR_EGENERAL;
 }
 
-apr_status_t md_acme_init(apr_pool_t *p, const char *base)
-{
-    base_product = base;
-    return md_crypt_init(p);
-}
-
-apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
-                            const char *proxy_url)
-{
-    md_acme_t *acme;
-    const char *err = NULL;
-    apr_status_t rv;
-    apr_uri_t uri_parsed;
-    size_t len;
-    
-    if (!url) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, "create ACME without url");
-        return APR_EINVAL;
-    }
-    
-    if (APR_SUCCESS != (rv = md_util_abs_uri_check(p, url, &err))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "invalid ACME uri (%s): %s", err, url);
-        return rv;
-    }
-    
-    acme = apr_pcalloc(p, sizeof(*acme));
-    acme->url = url;
-    acme->p = p;
-    acme->user_agent = apr_psprintf(p, "%s mod_md/%s", 
-                                    base_product, MOD_MD_VERSION);
-    acme->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
-    acme->max_retries = 3;
-    
-    if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: %s", url);
-        return APR_EINVAL;
-    }
-    
-    len = strlen(uri_parsed.hostname);
-    acme->sname = (len <= 16)? uri_parsed.hostname : apr_pstrdup(p, uri_parsed.hostname + len - 16);
-    
-    *pacme = (APR_SUCCESS == rv)? acme : NULL;
-    return rv;
-}
-
-apr_status_t md_acme_setup(md_acme_t *acme)
-{
-    apr_status_t rv;
-    md_json_t *json;
-    
-    assert(acme->url);
-    if (!acme->http && APR_SUCCESS != (rv = md_http_create(&acme->http, acme->p,
-                                                           acme->user_agent, acme->proxy_url))) {
-        return rv;
-    }
-    md_http_set_response_limit(acme->http, 1024*1024);
-    
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url);
-    
-    rv = md_acme_get_json(&json, acme, acme->url, acme->p);
-    if (APR_SUCCESS == rv) {
-        acme->new_authz = md_json_gets(json, "new-authz", NULL);
-        acme->new_cert = md_json_gets(json, "new-cert", NULL);
-        acme->new_reg = md_json_gets(json, "new-reg", NULL);
-        acme->revoke_cert = md_json_gets(json, "revoke-cert", NULL);
-        if (acme->new_authz && acme->new_cert && acme->new_reg && acme->revoke_cert) {
-            return APR_SUCCESS;
-        }
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, acme->p,
-                      "Unable to understand ACME server response. Wrong ACME protocol version?");
-        rv = APR_EINVAL;
-    }
-    else {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, acme->p, "unsuccessful in contacting ACME "
-                      "server at %s. If this problem persists, please check your network "
-                      "connectivity from your Apache server to the ACME server. Also, older "
-                      "servers might have trouble verifying the certificates of the ACME "
-                      "server. You can check if you are able to contact it manually via the "
-                      "curl command. Sometimes, the ACME server might be down for maintenance, "
-                      "so failing to contact it is not an immediate problem. mod_md will "
-                      "continue retrying this.", acme->url);
-    }
-    return rv;
-}
-
 /**************************************************************************************************/
 /* acme requests */
 
@@ -195,16 +111,6 @@ static apr_status_t http_update_nonce(co
     return res->rv;
 }
 
-static apr_status_t md_acme_new_nonce(md_acme_t *acme)
-{
-    apr_status_t rv;
-    long id;
-    
-    rv = md_http_HEAD(acme->http, acme->new_reg, NULL, http_update_nonce, acme, &id);
-    md_http_await(acme->http, id);
-    return rv;
-}
-
 static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, const char *url)
 {
     apr_pool_t *pool;
@@ -232,31 +138,26 @@ static md_acme_req_t *md_acme_req_create
         return NULL;
     }
     req->max_retries = acme->max_retries;
-    
+    req->result = md_result_make(req->p, APR_SUCCESS);
     return req;
 }
  
-apr_status_t md_acme_req_body_init(md_acme_req_t *req, md_json_t *jpayload)
+static apr_status_t acmev1_new_nonce(md_acme_t *acme)
 {
-    const char *payload;
-    size_t payload_len;
-
-    if (!req->acme->acct) {
-        return APR_EINVAL;
-    }
+    return md_http_HEAD(acme->http, acme->api.v1.new_reg, NULL, http_update_nonce, acme);
+}
 
-    payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
-    if (!payload) {
-        return APR_EINVAL;
-    }
+static apr_status_t acmev2_new_nonce(md_acme_t *acme)
+{
+    return md_http_HEAD(acme->http, acme->api.v2.new_nonce, NULL, http_update_nonce, acme);
+}
 
-    payload_len = strlen(payload);
-    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p, 
-                  "acct payload(len=%" APR_SIZE_T_FMT "): %s", payload_len, payload);
-    return md_jws_sign(&req->req_json, req->p, payload, payload_len,
-                       req->prot_hdrs, req->acme->acct_key, NULL);
-} 
 
+apr_status_t md_acme_init(apr_pool_t *p, const char *base,  int init_ssl)
+{
+    base_product = base;
+    return init_ssl? md_crypt_init(p) : APR_SUCCESS;
+}
 
 static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t *res)
 {
@@ -274,6 +175,7 @@ static apr_status_t inspect_problem(md_a
             ptype = md_json_gets(problem, MD_KEY_TYPE, NULL); 
             pdetail = md_json_gets(problem, MD_KEY_DETAIL, NULL);
             req->rv = problem_status_get(ptype);
+            md_result_problem_set(req->result, req->rv, ptype, pdetail);
             
             if (APR_STATUS_IS_EAGAIN(req->rv)) {
                 md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, req->rv, req->p,
@@ -298,7 +200,9 @@ static apr_status_t inspect_problem(md_a
             default:
                 md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, req->p,
                               "acme problem unknown: http status %d", res->status);
-                return APR_EGENERAL;
+                md_result_printf(req->result, APR_EGENERAL, "unexpected http status: %d",
+                                 res->status);
+                return req->result->status;
         }
     }
     return res->rv;
@@ -307,9 +211,73 @@ static apr_status_t inspect_problem(md_a
 /**************************************************************************************************/
 /* ACME requests with nonce handling */
 
-static apr_status_t md_acme_req_done(md_acme_req_t *req)
+static apr_status_t acmev1_req_init(md_acme_req_t *req, md_json_t *jpayload)
+{
+    const char *payload;
+    size_t payload_len;
+    
+    if (!req->acme->acct) {
+        return APR_EINVAL;
+    }
+    if (jpayload) {
+        payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
+        if (!payload) {
+            return APR_EINVAL;
+        }
+    }
+    else {
+        payload = "";
+    }
+
+    payload_len = strlen(payload);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p, 
+                  "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload_len, payload);
+    return md_jws_sign(&req->req_json, req->p, payload, payload_len,
+                       req->prot_hdrs, req->acme->acct_key, NULL);
+}
+
+static apr_status_t acmev2_req_init(md_acme_req_t *req, md_json_t *jpayload)
+{
+    const char *payload;
+    size_t payload_len;
+    
+    if (!req->acme->acct) {
+        return APR_EINVAL;
+    }
+    if (jpayload) {
+        payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
+        if (!payload) {
+            return APR_EINVAL;
+        }
+    }
+    else {
+        payload = "";
+    }
+
+    payload_len = strlen(payload);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p, 
+                  "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload_len, payload);
+    return md_jws_sign(&req->req_json, req->p, payload, payload_len,
+                       req->prot_hdrs, req->acme->acct_key, req->acme->acct->url);
+}
+
+apr_status_t md_acme_req_body_init(md_acme_req_t *req, md_json_t *payload)
 {
-    apr_status_t rv = req->rv;
+    return req->acme->req_init_fn(req, payload);
+}
+
+static apr_status_t md_acme_req_done(md_acme_req_t *req, apr_status_t rv)
+{
+    if (req->result->status != APR_SUCCESS) {
+        if (req->on_err) {
+            req->on_err(req, req->result, req->baton);
+        }
+    }
+    /* An error in rv superceeds the result->status */
+    if (APR_SUCCESS != rv) req->result->status = rv;
+    rv = req->result->status;
+    /* transfer results into the acme's central result for longer life and later inspection */
+    md_result_dup(req->acme->last, req->result);
     if (req->p) {
         apr_pool_destroy(req->p);
     }
@@ -361,9 +329,10 @@ static apr_status_t on_response(const md
         
         if (!processed) {
             rv = APR_EINVAL;
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, req->p, 
-                          "response: %d, content-type=%s", res->status, 
-                          apr_table_get(res->headers, "Content-Type"));
+            md_result_printf(req->result, rv, "unable to process the response: "
+                             "http-status=%d, content-type=%s", 
+                             res->status, apr_table_get(res->headers, "Content-Type"));
+            md_result_log(req->result, MD_LOG_ERR);
         }
     }
     else if (APR_EAGAIN == (rv = inspect_problem(req, res))) {
@@ -372,84 +341,111 @@ static apr_status_t on_response(const md
     }
 
 out:
-    md_acme_req_done(req);
+    md_acme_req_done(req, rv);
     return rv;
 }
 
+static apr_status_t acmev2_GET_as_POST_init(md_acme_req_t *req, void *baton)
+{
+    (void)baton;
+    return md_acme_req_body_init(req, NULL);
+}
+
 static apr_status_t md_acme_req_send(md_acme_req_t *req)
 {
     apr_status_t rv;
     md_acme_t *acme = req->acme;
     const char *body = NULL;
+    md_result_t *result;
 
     assert(acme->url);
     
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p, 
+                  "sending req: %s %s", req->method, req->url);
+    md_result_reset(req->acme->last);
+    result = md_result_make(req->p, APR_SUCCESS);
+    
+    /* Whom are we talking to? */
+    if (acme->version == MD_ACME_VERSION_UNKNOWN) {
+        rv = md_acme_setup(acme, result);
+        if (APR_SUCCESS != rv) goto leave;
+    }
+    
+    if (!strcmp("GET", req->method) && !req->on_init && !req->req_json 
+        && MD_ACME_VERSION_MAJOR(acme->version) > 1) {
+        /* See <https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.6.3>
+         * and <https://mailarchive.ietf.org/arch/msg/acme/sotffSQ0OWV-qQJodLwWYWcEVKI>
+         * and <https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380>
+         * We implement this change in ACMEv2 and higher as keeping the md_acme_GET() methods,
+         * but switching them to POSTs with a empty, JWS signed, body when we call
+         * our HTTP client. */
+        req->method = "POST";
+        req->on_init = acmev2_GET_as_POST_init;
+    }
+    
+    /* Besides GET/HEAD, we always need a fresh nonce */
     if (strcmp("GET", req->method) && strcmp("HEAD", req->method)) {
-        if (!acme->new_authz) {
-            if (APR_SUCCESS != (rv = md_acme_setup(acme))) {
-                return rv;
-            }
-        }
-        if (!acme->nonce) {
-            if (APR_SUCCESS != (rv = md_acme_new_nonce(acme))) {
-                md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, req->p, 
-                              "error retrieving new nonce from ACME server");
-                return rv;
-            }
+        if (acme->version == MD_ACME_VERSION_UNKNOWN) {
+            rv = md_acme_setup(acme, result);
+            if (APR_SUCCESS != rv) goto leave;
+        }
+        if (!acme->nonce && (APR_SUCCESS != (rv = acme->new_nonce_fn(acme)))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, req->p, 
+                          "error retrieving new nonce from ACME server");
+            goto leave;
         }
         
         apr_table_set(req->prot_hdrs, "nonce", acme->nonce);
+        if (MD_ACME_VERSION_MAJOR(acme->version) > 1) {
+            apr_table_set(req->prot_hdrs, "url", req->url);
+        }
         acme->nonce = NULL;
     }
     
     rv = req->on_init? req->on_init(req, req->baton) : APR_SUCCESS;
+    if (APR_SUCCESS != rv) goto leave;
     
-    if ((rv == APR_SUCCESS) && req->req_json) {
+    if (req->req_json) {
         body = md_json_writep(req->req_json, req->p, MD_JSON_FMT_INDENT);
         if (!body) {
-            rv = APR_EINVAL;
+            rv = APR_EINVAL; goto leave;
         }
     }
 
-    if (rv == APR_SUCCESS) {
-        long id = 0;
-        
-        if (body && md_log_is_level(req->p, MD_LOG_TRACE2)) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, req->p, 
-                          "req: POST %s, body:\n%s", req->url, body);
-        }
-        else {
-            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p, 
-                          "req: POST %s", req->url);
-        }
-        if (!strcmp("GET", req->method)) {
-            rv = md_http_GET(req->acme->http, req->url, NULL, on_response, req, &id);
-        }
-        else if (!strcmp("POST", req->method)) {
-            rv = md_http_POSTd(req->acme->http, req->url, NULL, "application/json",  
-                               body, body? strlen(body) : 0, on_response, req, &id);
-        }
-        else if (!strcmp("HEAD", req->method)) {
-            rv = md_http_HEAD(req->acme->http, req->url, NULL, on_response, req, &id);
-        }
-        else {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, req->p, 
-                          "HTTP method %s against: %s", req->method, req->url);
-            rv = APR_ENOTIMPL;
-        }
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->p, "req sent");
-        md_http_await(acme->http, id);
-        
-        if (APR_EAGAIN == rv && req->max_retries > 0) {
-            --req->max_retries;
-            return md_acme_req_send(req);
-        }
-        req = NULL;
+    if (body && md_log_is_level(req->p, MD_LOG_TRACE2)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, req->p, 
+                      "req: %s %s, body:\n%s", req->method, req->url, body);
     }
-
-    if (req) {
-        md_acme_req_done(req);
+    else {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p, 
+                      "req: %s %s", req->method, req->url);
+    }
+    
+    if (!strcmp("GET", req->method)) {
+        rv = md_http_GET(req->acme->http, req->url, NULL, on_response, req);
+    }
+    else if (!strcmp("POST", req->method)) {
+        rv = md_http_POSTd(req->acme->http, req->url, NULL, "application/jose+json",  
+                           body, body? strlen(body) : 0, on_response, req);
     }
+    else if (!strcmp("HEAD", req->method)) {
+        rv = md_http_HEAD(req->acme->http, req->url, NULL, on_response, req);
+    }
+    else {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, req->p, 
+                      "HTTP method %s against: %s", req->method, req->url);
+        rv = APR_ENOTIMPL;
+    }
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->p, "req sent");
+    
+    if (APR_EAGAIN == rv && req->max_retries > 0) {
+        --req->max_retries;
+        rv = md_acme_req_send(req);
+    }
+    req = NULL;
+
+leave:
+    if (req) md_acme_req_done(req, rv);
     return rv;
 }
 
@@ -457,6 +453,7 @@ apr_status_t md_acme_POST(md_acme_t *acm
                           md_acme_req_init_cb *on_init,
                           md_acme_req_json_cb *on_json,
                           md_acme_req_res_cb *on_res,
+                          md_acme_req_err_cb *on_err,
                           void *baton)
 {
     md_acme_req_t *req;
@@ -469,6 +466,7 @@ apr_status_t md_acme_POST(md_acme_t *acm
     req->on_init = on_init;
     req->on_json = on_json;
     req->on_res = on_res;
+    req->on_err = on_err;
     req->baton = baton;
     
     return md_acme_req_send(req);
@@ -478,6 +476,7 @@ apr_status_t md_acme_GET(md_acme_t *acme
                          md_acme_req_init_cb *on_init,
                          md_acme_req_json_cb *on_json,
                          md_acme_req_res_cb *on_res,
+                          md_acme_req_err_cb *on_err,
                          void *baton)
 {
     md_acme_req_t *req;
@@ -490,11 +489,22 @@ apr_status_t md_acme_GET(md_acme_t *acme
     req->on_init = on_init;
     req->on_json = on_json;
     req->on_res = on_res;
+    req->on_err = on_err;
     req->baton = baton;
     
     return md_acme_req_send(req);
 }
 
+void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t *result)
+{
+    if (acme->last->status == APR_SUCCESS) {
+        md_result_set(result, rv, NULL);
+    }
+    else {
+        md_result_problem_set(result, acme->last->status, acme->last->problem, acme->last->detail);
+    }
+}
+
 /**************************************************************************************************/
 /* GET JSON */
 
@@ -524,8 +534,255 @@ apr_status_t md_acme_get_json(struct md_
     ctx.pool = p;
     ctx.json = NULL;
     
-    rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, &ctx);
+    rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, NULL, &ctx);
     *pjson = (APR_SUCCESS == rv)? ctx.json : NULL;
     return rv;
 }
 
+/**************************************************************************************************/
+/* Generic ACME operations */
+
+void md_acme_clear_acct(md_acme_t *acme)
+{
+    acme->acct_id = NULL;
+    acme->acct = NULL;
+    acme->acct_key = NULL;
+}
+
+const char *md_acme_acct_id_get(md_acme_t *acme)
+{
+    return acme->acct_id;
+}
+
+const char *md_acme_acct_url_get(md_acme_t *acme)
+{
+    return acme->acct? acme->acct->url : NULL;
+}
+
+apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store,
+                              apr_pool_t *p, const char *acct_id)
+{
+    md_acme_acct_t *acct;
+    md_pkey_t *pkey;
+    apr_status_t rv;
+    
+    if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey, 
+                                               store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
+        if (acct->ca_url && !strcmp(acct->ca_url, acme->url)) {
+            acme->acct_id = apr_pstrdup(p, acct_id);
+            acme->acct = acct;
+            acme->acct_key = pkey;
+            rv = md_acme_acct_validate(acme, store, p);
+        }
+        else {
+            /* account is from a nother server or, more likely, from another
+             * protocol endpoint on the same server */
+            rv = APR_ENOENT;
+        }
+    }
+    return rv;
+}
+
+apr_status_t md_acme_save_acct(md_acme_t *acme, apr_pool_t *p, md_store_t *store)
+{
+    return md_acme_acct_save(store, p, acme, &acme->acct_id, acme->acct, acme->acct_key);
+}
+
+static apr_status_t acmev1_POST_new_account(md_acme_t *acme, 
+                                            md_acme_req_init_cb *on_init,
+                                            md_acme_req_json_cb *on_json,
+                                            md_acme_req_res_cb *on_res,
+                                            md_acme_req_err_cb *on_err,
+                                            void *baton)
+{
+    return md_acme_POST(acme, acme->api.v1.new_reg, on_init, on_json, on_res, on_err, baton);
+}
+
+static apr_status_t acmev2_POST_new_account(md_acme_t *acme, 
+                                            md_acme_req_init_cb *on_init,
+                                            md_acme_req_json_cb *on_json,
+                                            md_acme_req_res_cb *on_res,
+                                            md_acme_req_err_cb *on_err,
+                                            void *baton)
+{
+    return md_acme_POST(acme, acme->api.v2.new_account, on_init, on_json, on_res, on_err, baton);
+}
+
+apr_status_t md_acme_POST_new_account(md_acme_t *acme, 
+                                      md_acme_req_init_cb *on_init,
+                                      md_acme_req_json_cb *on_json,
+                                      md_acme_req_res_cb *on_res,
+                                      md_acme_req_err_cb *on_err,
+                                      void *baton)
+{
+    return acme->post_new_account_fn(acme, on_init, on_json, on_res, on_err, baton);
+}
+
+/**************************************************************************************************/
+/* ACME setup */
+
+apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
+                            const char *proxy_url)
+{
+    md_acme_t *acme;
+    const char *err = NULL;
+    apr_status_t rv;
+    apr_uri_t uri_parsed;
+    size_t len;
+    
+    if (!url) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, "create ACME without url");
+        return APR_EINVAL;
+    }
+    
+    if (APR_SUCCESS != (rv = md_util_abs_uri_check(p, url, &err))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "invalid ACME uri (%s): %s", err, url);
+        return rv;
+    }
+    
+    acme = apr_pcalloc(p, sizeof(*acme));
+    acme->url = url;
+    acme->p = p;
+    acme->user_agent = apr_psprintf(p, "%s mod_md/%s", 
+                                    base_product, MOD_MD_VERSION);
+    acme->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
+    acme->max_retries = 3;
+    
+    if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: %s", url);
+        return APR_EINVAL;
+    }
+    
+    len = strlen(uri_parsed.hostname);
+    acme->sname = (len <= 16)? uri_parsed.hostname : apr_pstrdup(p, uri_parsed.hostname + len - 16);
+    acme->version = MD_ACME_VERSION_UNKNOWN;
+    acme->last = md_result_make(acme->p, APR_SUCCESS);
+    
+    *pacme = (APR_SUCCESS == rv)? acme : NULL;
+    return rv;
+}
+
+typedef struct {
+    md_acme_t *acme;
+    md_result_t *result;
+} update_dir_ctx;
+
+static apr_status_t update_directory(const md_http_response_t *res)
+{
+    md_http_request_t *req = res->req;
+    md_acme_t *acme = ((update_dir_ctx *)req->baton)->acme;
+    md_result_t *result = ((update_dir_ctx *)req->baton)->result;
+    apr_status_t rv = res->rv;
+    md_json_t *json;
+    const char *s;
+    
+    if (APR_SUCCESS != rv) goto leave;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, req->pool, "directory lookup response: %d", res->status);
+    if (res->status == 503) {
+        md_result_printf(result, APR_EAGAIN,
+            "The ACME server at <%s> reports that Service is Unavailable (503). This "
+            "may happen during maintenance for short periods of time.", acme->url); 
+        md_result_log(result, MD_LOG_INFO);
+        rv = result->status;
+        goto leave;
+    }
+    else if (res->status < 200 || res->status >= 300) {
+        md_result_printf(result, APR_EAGAIN,
+            "The ACME server at <%s> responded with HTTP status %d. This "
+            "is unusual. Please verify that the URL is correct and that you can indeed "
+            "make request from the server to it by other means, e.g. invoking curl/wget.", 
+            acme->url, res->status); 
+        goto leave;
+    }
+    
+    rv = md_json_read_http(&json, req->pool, res);
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, req->pool, "reading JSON body");
+        goto leave;
+    }
+    
+    if (md_log_is_level(acme->p, MD_LOG_TRACE2)) {
+        s = md_json_writep(json, req->pool, MD_JSON_FMT_INDENT);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, req->pool,
+                      "response: %s", s ? s : "<failed to serialize!>");
+    }
+    
+    /* What have we got? */
+    if ((s = md_json_dups(acme->p, json, "new-authz", NULL))) {
+        acme->api.v1.new_authz = s;
+        acme->api.v1.new_cert = md_json_dups(acme->p, json, "new-cert", NULL);
+        acme->api.v1.new_reg = md_json_dups(acme->p, json, "new-reg", NULL);
+        acme->api.v1.revoke_cert = md_json_dups(acme->p, json, "revoke-cert", NULL);
+        if (acme->api.v1.new_authz && acme->api.v1.new_cert 
+            && acme->api.v1.new_reg && acme->api.v1.revoke_cert) {
+            acme->version = MD_ACME_VERSION_1;
+        }
+        acme->ca_agreement = md_json_dups(acme->p, json, "meta", "terms-of-service", NULL);
+        acme->new_nonce_fn = acmev1_new_nonce;
+        acme->req_init_fn = acmev1_req_init;
+        acme->post_new_account_fn = acmev1_POST_new_account;
+    }
+    else if ((s = md_json_dups(acme->p, json, "newAccount", NULL))) {
+        acme->api.v2.new_account = s;
+        acme->api.v2.new_order = md_json_dups(acme->p, json, "newOrder", NULL);
+        acme->api.v2.revoke_cert = md_json_dups(acme->p, json, "revokeCert", NULL);
+        acme->api.v2.key_change = md_json_dups(acme->p, json, "keyChange", NULL);
+        acme->api.v2.new_nonce = md_json_dups(acme->p, json, "newNonce", NULL);
+        if (acme->api.v2.new_account && acme->api.v2.new_order 
+            && acme->api.v2.revoke_cert && acme->api.v2.key_change
+            && acme->api.v2.new_nonce) {
+            acme->version = MD_ACME_VERSION_2;
+        }
+        acme->ca_agreement = md_json_dups(acme->p, json, "meta", "termsOfService", NULL);
+        acme->new_nonce_fn = acmev2_new_nonce;
+        acme->req_init_fn = acmev2_req_init;
+        acme->post_new_account_fn = acmev2_POST_new_account;
+    }
+    
+    if (MD_ACME_VERSION_UNKNOWN == acme->version) {
+        md_result_printf(result, APR_EINVAL,
+            "Unable to understand ACME server response from <%s>. "
+            "Wrong ACME protocol version or link?", acme->url); 
+        md_result_log(result, MD_LOG_WARNING);
+        rv = result->status;
+    }
+leave:
+    return rv;
+}
+
+apr_status_t md_acme_setup(md_acme_t *acme, md_result_t *result)
+{
+    apr_status_t rv;
+    update_dir_ctx ctx;
+   
+    assert(acme->url);
+    acme->version = MD_ACME_VERSION_UNKNOWN;
+    
+    if (!acme->http && APR_SUCCESS != (rv = md_http_create(&acme->http, acme->p,
+                                                           acme->user_agent, acme->proxy_url))) {
+        return rv;
+    }
+    md_http_set_response_limit(acme->http, 1024*1024);
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url);
+    
+    ctx.acme = acme;
+    ctx.result = result;
+    rv = md_http_GET(acme->http, acme->url, NULL, update_directory, &ctx);
+    
+    if (APR_SUCCESS != rv && APR_SUCCESS == result->status) {
+        /* If the result reports no error, we never got a response from the server */
+        md_result_printf(result, rv, 
+            "Unsuccessful in contacting ACME server at <%s>. If this problem persists, "
+            "please check your network connectivity from your Apache server to the "
+            "ACME server. Also, older servers might have trouble verifying the certificates "
+            "of the ACME server. You can check if you are able to contact it manually via the "
+            "curl command. Sometimes, the ACME server might be down for maintenance, "
+            "so failing to contact it is not an immediate problem. Apache will "
+            "continue retrying this.", acme->url);
+        md_result_log(result, MD_LOG_WARNING);
+    }
+    return rv;
+}
+
+

Modified: httpd/httpd/trunk/modules/md/md_acme.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_acme.h?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_acme.h (original)
+++ httpd/httpd/trunk/modules/md/md_acme.h Mon Jun 24 16:04:32 2019
@@ -26,14 +26,21 @@ struct md_json_t;
 struct md_pkey_t;
 struct md_t;
 struct md_acme_acct_t;
-struct md_proto_t;
+struct md_acmev2_acct_t;
 struct md_store_t;
+struct md_result_t;
 
 #define MD_PROTO_ACME               "ACME"
 
 #define MD_AUTHZ_CHA_HTTP_01        "http-01"
 #define MD_AUTHZ_CHA_SNI_01         "tls-sni-01"
 
+#define MD_ACME_VERSION_UNKNOWN    0x0
+#define MD_ACME_VERSION_1          0x010000
+#define MD_ACME_VERSION_2          0x020000
+
+#define MD_ACME_VERSION_MAJOR(i)    (((i)&0xFF0000) >> 16)
+
 typedef enum {
     MD_ACME_S_UNKNOWN,              /* MD has not been analysed yet */
     MD_ACME_S_REGISTERED,           /* MD is registered at CA, but not more */
@@ -46,30 +53,90 @@ typedef enum {
 
 typedef struct md_acme_t md_acme_t;
 
+typedef struct md_acme_req_t md_acme_req_t;
+/**
+ * Request callback on a successful HTTP response (status 2xx).
+ */
+typedef apr_status_t md_acme_req_res_cb(md_acme_t *acme, 
+                                        const struct md_http_response_t *res, void *baton);
+
+/**
+ * Request callback to initialize before sending. May be invoked more than once in
+ * case of retries.
+ */
+typedef apr_status_t md_acme_req_init_cb(md_acme_req_t *req, void *baton);
+
+/**
+ * Request callback on a successful response (HTTP response code 2xx) and content
+ * type matching application/.*json.
+ */
+typedef apr_status_t md_acme_req_json_cb(md_acme_t *acme, apr_pool_t *p, 
+                                         const apr_table_t *headers, 
+                                         struct md_json_t *jbody, void *baton);
+
+/**
+ * Request callback on detected errors.
+ */
+typedef apr_status_t md_acme_req_err_cb(md_acme_req_t *req, 
+                                        const struct md_result_t *result, void *baton);
+
+
+typedef apr_status_t md_acme_new_nonce_fn(md_acme_t *acme);
+typedef apr_status_t md_acme_req_init_fn(md_acme_req_t *req, struct md_json_t *jpayload);
+
+typedef apr_status_t md_acme_post_fn(md_acme_t *acme, 
+                                     md_acme_req_init_cb *on_init,
+                                     md_acme_req_json_cb *on_json,
+                                     md_acme_req_res_cb *on_res,
+                                     md_acme_req_err_cb *on_err,
+                                     void *baton);
+
 struct md_acme_t {
     const char *url;                /* directory url of the ACME service */
     const char *sname;              /* short name for the service, not necessarily unique */
     apr_pool_t *p;
     const char *user_agent;
     const char *proxy_url;
-    struct md_acme_acct_t *acct;
-    struct md_pkey_t *acct_key;
     
-    const char *new_authz;
-    const char *new_cert;
-    const char *new_reg;
-    const char *revoke_cert;
+    const char *acct_id;            /* local storage id account was loaded from or NULL */
+    struct md_acme_acct_t *acct;    /* account at ACME server to use for requests */
+    struct md_pkey_t *acct_key;     /* private RSA key belonging to account */
+    
+    int version;                    /* as detected from the server */
+    union {
+        struct {
+            const char *new_authz;
+            const char *new_cert;
+            const char *new_reg;
+            const char *revoke_cert;
+            
+        } v1;
+        struct {
+            const char *new_account;
+            const char *new_order;
+            const char *key_change;
+            const char *revoke_cert;
+            const char *new_nonce;
+        } v2;
+    } api;
+    const char *ca_agreement;
+    const char *acct_name;
+    
+    md_acme_new_nonce_fn *new_nonce_fn;
+    md_acme_req_init_fn *req_init_fn;
+    md_acme_post_fn *post_new_account_fn;
     
     struct md_http_t *http;
     
     const char *nonce;
     int max_retries;
+    struct md_result_t *last;      /* result of last request */
 };
 
 /**
  * Global init, call once at start up.
  */
-apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version);
+apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version, int init_ssl);
 
 /**
  * Create a new ACME server instance. If path is not NULL, will use that directory
@@ -89,16 +156,31 @@ apr_status_t md_acme_create(md_acme_t **
  * 
  * @param acme    the ACME server to contact
  */
-apr_status_t md_acme_setup(md_acme_t *acme);
+apr_status_t md_acme_setup(md_acme_t *acme, struct md_result_t *result);
+
+void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t *result);
 
 /**************************************************************************************************/
 /* account handling */
 
-#define MD_ACME_ACCT_STAGED     "staged"
+/**
+ * Clear any existing account data from acme instance.
+ */
+void md_acme_clear_acct(md_acme_t *acme);
+
+apr_status_t md_acme_POST_new_account(md_acme_t *acme, 
+                                      md_acme_req_init_cb *on_init,
+                                      md_acme_req_json_cb *on_json,
+                                      md_acme_req_res_cb *on_res,
+                                      md_acme_req_err_cb *on_err,
+                                      void *baton);
 
-apr_status_t md_acme_acct_load(struct md_acme_acct_t **pacct, struct md_pkey_t **ppkey,
-                               struct md_store_t *store, md_store_group_t group, 
-                               const char *name, apr_pool_t *p);
+/**
+ * Get the local name of the account currently used by the acme instance.
+ * Will be NULL if no account has been setup successfully.
+ */
+const char *md_acme_acct_id_get(md_acme_t *acme);
+const char *md_acme_acct_url_get(md_acme_t *acme);
 
 /** 
  * Specify the account to use by name in local store. On success, the account
@@ -107,14 +189,11 @@ apr_status_t md_acme_acct_load(struct md
 apr_status_t md_acme_use_acct(md_acme_t *acme, struct md_store_t *store, 
                               apr_pool_t *p, const char *acct_id);
 
-apr_status_t md_acme_use_acct_staged(md_acme_t *acme, struct md_store_t *store, 
-                                     md_t *md, apr_pool_t *p);
-
 /**
  * Get the local name of the account currently used by the acme instance.
  * Will be NULL if no account has been setup successfully.
  */
-const char *md_acme_get_acct_id(md_acme_t *acme);
+const char *md_acme_acct_id_get(md_acme_t *acme);
 
 /**
  * Agree to the given Terms-of-Service url for the current account.
@@ -136,71 +215,16 @@ apr_status_t md_acme_agree(md_acme_t *ac
 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.
- */
-const char *md_acme_get_agreement(md_acme_t *acme);
-
-
-/** 
- * Find an existing account in the local store. On APR_SUCCESS, the acme
- * instance will have a current, validated account to use.
- */ 
-apr_status_t md_acme_find_acct(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
-
-/**
- * Create a new account at the ACME server. The
- * new account is the one used by the acme instance afterwards, on success.
- */
-apr_status_t md_acme_create_acct(md_acme_t *acme, apr_pool_t *p, apr_array_header_t *contacts, 
-                                 const char *agreement);
-
-apr_status_t md_acme_acct_save(struct md_store_t *store, apr_pool_t *p, md_acme_t *acme,  
-                               struct md_acme_acct_t *acct, struct md_pkey_t *acct_key);
+apr_status_t md_acme_save_acct(md_acme_t *acme, apr_pool_t *p, struct md_store_t *store);
                                
-apr_status_t md_acme_save(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
-
-apr_status_t md_acme_acct_save_staged(md_acme_t *acme, struct md_store_t *store, 
-                                      md_t *md, apr_pool_t *p);
-
-/**
- * Delete the current account at the ACME server and remove it from store. 
- */
-apr_status_t md_acme_delete_acct(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
-
 /**
- * Delete the account from the local store without contacting the ACME server.
+ * Deactivate the current account at the ACME server.. 
  */
-apr_status_t md_acme_unstore_acct(struct md_store_t *store, apr_pool_t *p, const char *acct_id);
+apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p);
 
 /**************************************************************************************************/
 /* request handling */
 
-/**
- * Request callback on a successful HTTP response (status 2xx).
- */
-typedef apr_status_t md_acme_req_res_cb(md_acme_t *acme, 
-                                        const struct md_http_response_t *res, void *baton);
-
-/**
- * A request against an ACME server
- */
-typedef struct md_acme_req_t md_acme_req_t;
-
-/**
- * Request callback to initialize before sending. May be invoked more than once in
- * case of retries.
- */
-typedef apr_status_t md_acme_req_init_cb(md_acme_req_t *req, void *baton);
-
-/**
- * Request callback on a successful response (HTTP response code 2xx) and content
- * type matching application/.*json.
- */
-typedef apr_status_t md_acme_req_json_cb(md_acme_t *acme, apr_pool_t *p, 
-                                         const apr_table_t *headers, 
-                                         struct md_json_t *jbody, void *baton);
-
 struct md_acme_req_t {
     md_acme_t *acme;               /* the ACME server to talk to */
     apr_pool_t *p;                 /* pool for the request duration */
@@ -218,14 +242,19 @@ struct md_acme_req_t {
     md_acme_req_init_cb *on_init;  /* callback to initialize the request before submit */
     md_acme_req_json_cb *on_json;  /* callback on successful JSON response */
     md_acme_req_res_cb *on_res;    /* callback on generic HTTP response */
+    md_acme_req_err_cb *on_err;    /* callback on encountered error */
     int max_retries;               /* how often this might be retried */
     void *baton;                   /* userdata for callbacks */
+    struct md_result_t *result;    /* result of this request */
 };
 
+apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *payload);
+
 apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
                          md_acme_req_init_cb *on_init,
                          md_acme_req_json_cb *on_json,
                          md_acme_req_res_cb *on_res,
+                         md_acme_req_err_cb *on_err,
                          void *baton);
 /**
  * Perform a POST against the ACME url. If a on_json callback is given and
@@ -245,14 +274,9 @@ apr_status_t md_acme_POST(md_acme_t *acm
                           md_acme_req_init_cb *on_init,
                           md_acme_req_json_cb *on_json,
                           md_acme_req_res_cb *on_res,
+                          md_acme_req_err_cb *on_err,
                           void *baton);
 
-apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
-                         md_acme_req_init_cb *on_init,
-                         md_acme_req_json_cb *on_json,
-                         md_acme_req_res_cb *on_res,
-                         void *baton);
-
 /**
  * Retrieve a JSON resource from the ACME server 
  */