summaryrefslogtreecommitdiff
path: root/modules/md
diff options
context:
space:
mode:
authorStefan Eissing <icing@apache.org>2021-10-29 09:04:38 +0000
committerStefan Eissing <icing@apache.org>2021-10-29 09:04:38 +0000
commit51a214821cdceb0ae4153accf0d5f8c5207f6549 (patch)
tree1cf12feca8d899c0c59d356dfbc040a44406e549 /modules/md
parent7b83bd03a93b28ec21be973936ad06c606fbf54f (diff)
downloadhttpd-51a214821cdceb0ae4153accf0d5f8c5207f6549.tar.gz
*) mod_md: adding v2.4.8 with the following changes
- Added support for ACME External Account Binding (EAB). Use the new directive `MDExternalAccountBinding` to provide the server with the value for key identifier and hmac as provided by your CA. While working on some servers, EAB handling is not uniform across CAs. First tests with a Sectigo Certificate Manager in demo mode are successful. But ZeroSSL, for example, seems to regard EAB values as a one-time-use-only thing, which makes them fail if you create a seconde account or retry the creation of the first account with the same EAB. - The directive 'MDCertificateAuthority' now checks if its parameter is a http/https url or one of a set of known names. Those are 'LetsEncrypt', 'LetsEncrypt-Test', 'Buypass' and 'Buypass-Test' for now and they are not case-sensitive. The default of LetsEncrypt is unchanged. - `MDContactEmail` can now be specified inside a `<MDomain dnsname>` section. - Treating 401 HTTP status codes for orders like 403, since some ACME servers seem to prefer that for accessing oders from other accounts. - When retrieving certificate chains, try to read the repsonse even if the HTTP Content-Type is unrecognized. - Fixed a bug that reset the error counter of a certificate renewal and prevented the increasing delays in further attempts. - Fixed the renewal process giving up every time on an already existing order with some invalid domains. Now, if such are seen in a previous order, a new order is created for a clean start over again. See <https://github.com/icing/mod_md/issues/268> - Fixed a mixup in md-status handler when static certificate files and renewal was configured at the same time. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1894610 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules/md')
-rw-r--r--modules/md/md.h28
-rw-r--r--modules/md/md_acme.c59
-rw-r--r--modules/md/md_acme.h22
-rw-r--r--modules/md/md_acme_acct.c306
-rw-r--r--modules/md/md_acme_acct.h26
-rw-r--r--modules/md/md_acme_authz.c16
-rw-r--r--modules/md/md_acme_drive.c43
-rw-r--r--modules/md/md_acme_order.c15
-rw-r--r--modules/md/md_acme_order.h3
-rw-r--r--modules/md/md_acmev2_drive.c28
-rw-r--r--modules/md/md_core.c84
-rw-r--r--modules/md/md_crypt.c75
-rw-r--r--modules/md/md_crypt.h3
-rw-r--r--modules/md/md_jws.c112
-rw-r--r--modules/md/md_jws.h25
-rw-r--r--modules/md/md_reg.c56
-rw-r--r--modules/md/md_status.c102
-rw-r--r--modules/md/md_store_fs.c17
-rw-r--r--modules/md/md_version.h4
-rw-r--r--modules/md/mod_md.c29
-rw-r--r--modules/md/mod_md_config.c47
-rw-r--r--modules/md/mod_md_config.h4
-rw-r--r--modules/md/mod_md_status.c53
23 files changed, 814 insertions, 343 deletions
diff --git a/modules/md/md.h b/modules/md/md.h
index 78b7ef863b..c7ee08f92d 100644
--- a/modules/md/md.h
+++ b/modules/md/md.h
@@ -90,12 +90,15 @@ struct md_t {
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 */
+ const char *ca_agreement; /* accepted agreement uri between CA and user */
struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */
struct apr_array_header_t *cert_files; /* != NULL iff pubcerts explicitly configured */
struct apr_array_header_t *pkey_files; /* != NULL iff privkeys explicitly configured */
-
+ const char *ca_eab_kid; /* optional KEYID for external account binding */
+ const char *ca_eab_hmac; /* optional HMAC for external accont binding */
+
md_state_t state; /* state of this MD */
+ const char *state_descr; /* description of state of NULL */
struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
int stapling; /* if OCSP stapling is enabled */
@@ -133,6 +136,8 @@ struct md_t {
#define MD_KEY_DIR "dir"
#define MD_KEY_DOMAIN "domain"
#define MD_KEY_DOMAINS "domains"
+#define MD_KEY_EAB "eab"
+#define MD_KEY_EAB_REQUIRED "externalAccountRequired"
#define MD_KEY_ENTRIES "entries"
#define MD_KEY_ERRORED "errored"
#define MD_KEY_ERROR "error"
@@ -142,11 +147,13 @@ struct md_t {
#define MD_KEY_FINISHED "finished"
#define MD_KEY_FROM "from"
#define MD_KEY_GOOD "good"
+#define MD_KEY_HMAC "hmac"
#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_KID "kid"
#define MD_KEY_KEYAUTHZ "keyAuthorization"
#define MD_KEY_LAST "last"
#define MD_KEY_LAST_RUN "last-run"
@@ -183,10 +190,12 @@ struct md_t {
#define MD_KEY_SHA256_FINGERPRINT "sha256-fingerprint"
#define MD_KEY_STAPLING "stapling"
#define MD_KEY_STATE "state"
+#define MD_KEY_STATE_DESCR "state-descr"
#define MD_KEY_STATUS "status"
#define MD_KEY_STORE "store"
#define MD_KEY_SUBPROBLEMS "subproblems"
#define MD_KEY_TEMPORARY "temporary"
+#define MD_KEY_TOS "termsOfService"
#define MD_KEY_TOKEN "token"
#define MD_KEY_TOTAL "total"
#define MD_KEY_TRANSITIVE "transitive"
@@ -280,20 +289,21 @@ md_t *md_copy(apr_pool_t *p, const md_t *src);
*
* This reads and writes the following information: name, domains, ca_url, ca_proto and state.
*/
-struct md_json_t *md_to_json (const md_t *md, apr_pool_t *p);
+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);
+/**
+ * Same as md_to_json(), but with sensitive fields stripped.
+ */
+struct md_json_t *md_to_public_json(const md_t *md, apr_pool_t *p);
+
int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t* alt_names);
/* how many certificates this domain has/will eventually have. */
int md_cert_count(const md_t *md);
-#define LE_ACMEv1_PROD "https://acme-v01.api.letsencrypt.org/directory"
-#define LE_ACMEv1_STAGING "https://acme-staging.api.letsencrypt.org/directory"
-
-#define LE_ACMEv2_PROD "https://acme-v02.api.letsencrypt.org/directory"
-#define LE_ACMEv2_STAGING "https://acme-staging-v02.api.letsencrypt.org/directory"
-
+const char *md_get_ca_name_from_url(apr_pool_t *p, const char *url);
+apr_status_t md_get_ca_url_from_name(const char **purl, apr_pool_t *p, const char *name);
/**************************************************************************************************/
/* notifications */
diff --git a/modules/md/md_acme.c b/modules/md/md_acme.c
index 80a76fb63d..d22aa83ff9 100644
--- a/modules/md/md_acme.c
+++ b/modules/md/md_acme.c
@@ -52,6 +52,7 @@ static acme_problem_status_t Problems[] = {
{ "acme:error:badCSR", APR_EINVAL, 1 },
{ "acme:error:badNonce", APR_EAGAIN, 0 },
{ "acme:error:badSignatureAlgorithm", APR_EINVAL, 1 },
+ { "acme:error:externalAccountRequired", APR_EINVAL, 1 },
{ "acme:error:invalidContact", APR_BADARG, 1 },
{ "acme:error:unsupportedContact", APR_EGENERAL, 1 },
{ "acme:error:malformed", APR_EINVAL, 1 },
@@ -147,11 +148,7 @@ static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, co
req->p = pool;
req->method = method;
req->url = url;
- req->prot_hdrs = apr_table_make(pool, 5);
- if (!req->prot_hdrs) {
- apr_pool_destroy(pool);
- return NULL;
- }
+ req->prot_fields = md_json_create(pool);
req->max_retries = acme->max_retries;
req->result = md_result_make(req->p, APR_SUCCESS);
return req;
@@ -207,6 +204,7 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
switch (res->status) {
case 400:
return APR_EINVAL;
+ case 401: /* sectigo returns this instead of 403 */
case 403:
return APR_EACCES;
case 404:
@@ -246,7 +244,7 @@ static apr_status_t acmev2_req_init(md_acme_req_t *req, md_json_t *jpayload)
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p,
"acme payload(len=%" APR_SIZE_T_FMT "): %s", payload.len, payload.data);
return md_jws_sign(&req->req_json, req->p, &payload,
- req->prot_hdrs, req->acme->acct_key, req->acme->acct->url);
+ req->prot_fields, 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)
@@ -377,9 +375,9 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
"error retrieving new nonce from ACME server");
goto leave;
}
-
- apr_table_set(req->prot_hdrs, "nonce", acme->nonce);
- apr_table_set(req->prot_hdrs, "url", req->url);
+
+ md_json_sets(acme->nonce, req->prot_fields, "nonce", NULL);
+ md_json_sets(req->url, req->prot_fields, "url", NULL);
acme->nonce = NULL;
}
@@ -388,15 +386,14 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
if (req->req_json) {
body = apr_pcalloc(req->p, sizeof(*body));
- if (!body) {
- rv = APR_EINVAL; goto leave;
- }
body->data = md_json_writep(req->req_json, req->p, MD_JSON_FMT_INDENT);
body->len = strlen(body->data);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->p,
+ "sending JSON body: %s", body->data);
}
- if (body && md_log_is_level(req->p, MD_LOG_TRACE2)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, req->p,
+ if (body && md_log_is_level(req->p, MD_LOG_TRACE4)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->p,
"req: %s %s, body:\n%s", req->method, req->url, body->data);
}
else {
@@ -549,10 +546,35 @@ apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store,
md_acme_acct_t *acct;
md_pkey_t *pkey;
apr_status_t rv;
-
- if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
+
+ if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
+ store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
+ if (md_acme_acct_matches_url(acct, 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 another server or, more likely, from another
+ * protocol endpoint on the same server */
+ rv = APR_ENOENT;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_acme_use_acct_for_md(md_acme_t *acme, struct md_store_t *store,
+ apr_pool_t *p, const char *acct_id,
+ const md_t *md)
+{
+ 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)) {
+ if (md_acme_acct_matches_md(acct, md)) {
acme->acct_id = apr_pstrdup(p, acct_id);
acme->acct = acct;
acme->acct_key = pkey;
@@ -701,7 +723,8 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data)
&& acme->api.v2.new_nonce) {
acme->version = MD_ACME_VERSION_2;
}
- acme->ca_agreement = md_json_dups(acme->p, json, "meta", "termsOfService", NULL);
+ acme->ca_agreement = md_json_dups(acme->p, json, "meta", MD_KEY_TOS, NULL);
+ acme->eab_required = md_json_getb(json, "meta", MD_KEY_EAB_REQUIRED, NULL);
acme->new_nonce_fn = acmev2_new_nonce;
acme->req_init_fn = acmev2_req_init;
acme->post_new_account_fn = acmev2_POST_new_account;
diff --git a/modules/md/md_acme.h b/modules/md/md_acme.h
index 004e7867ea..f28f2b6c61 100644
--- a/modules/md/md_acme.h
+++ b/modules/md/md_acme.h
@@ -122,6 +122,7 @@ struct md_acme_t {
} api;
const char *ca_agreement;
const char *acct_name;
+ int eab_required;
md_acme_new_nonce_fn *new_nonce_fn;
md_acme_req_init_fn *req_init_fn;
@@ -185,12 +186,29 @@ 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
- * the "current" one used by the acme instance.
+ * is the "current" one used by the acme instance.
+ * @param acme the acme instance to set the account for
+ * @param store the store to load accounts from
+ * @param p pool for allocations
+ * @param acct_id name of the account to load
*/
apr_status_t md_acme_use_acct(md_acme_t *acme, struct md_store_t *store,
apr_pool_t *p, const char *acct_id);
/**
+ * Specify the account to use for a specific MD by name in local store.
+ * On success, the account is the "current" one used by the acme instance.
+ * @param acme the acme instance to set the account for
+ * @param store the store to load accounts from
+ * @param p pool for allocations
+ * @param acct_id name of the account to load
+ * @param md the MD the account shall be used for
+ */
+apr_status_t md_acme_use_acct_for_md(md_acme_t *acme, struct md_store_t *store,
+ apr_pool_t *p, const char *acct_id,
+ const md_t *md);
+
+/**
* Get the local name of the account currently used by the acme instance.
* Will be NULL if no account has been setup successfully.
*/
@@ -232,7 +250,7 @@ struct md_acme_req_t {
const char *url; /* url to POST the request to */
const char *method; /* HTTP method to use */
- apr_table_t *prot_hdrs; /* JWS headers needing protection (nonce) */
+ struct md_json_t *prot_fields; /* JWS protected fields */
struct md_json_t *req_json; /* JSON to be POSTed in request body */
apr_table_t *resp_hdrs; /* HTTP response headers */
diff --git a/modules/md/md_acme_acct.c b/modules/md/md_acme_acct.c
index 3c0f453605..94dd83190d 100644
--- a/modules/md/md_acme_acct.c
+++ b/modules/md/md_acme_acct.c
@@ -30,6 +30,7 @@
#include "md_json.h"
#include "md_jws.h"
#include "md_log.h"
+#include "md_result.h"
#include "md_store.h"
#include "md_util.h"
#include "md_version.h"
@@ -43,7 +44,6 @@ static apr_status_t acct_make(md_acme_acct_t **pacct, apr_pool_t *p,
md_acme_acct_t *acct;
acct = apr_pcalloc(p, sizeof(*acct));
-
acct->ca_url = ca_url;
if (!contacts || apr_is_empty_array(contacts)) {
acct->contacts = apr_array_make(p, 5, sizeof(const char *));
@@ -114,7 +114,9 @@ md_json_t *md_acme_acct_to_json(md_acme_acct_t *acct, apr_pool_t *p)
if (acct->registration) md_json_setj(acct->registration, jacct, MD_KEY_REGISTRATION, NULL);
if (acct->agreement) md_json_sets(acct->agreement, jacct, MD_KEY_AGREEMENT, NULL);
if (acct->orders) md_json_sets(acct->orders, jacct, MD_KEY_ORDERS, NULL);
-
+ if (acct->eab_kid) md_json_sets(acct->eab_kid, jacct, MD_KEY_EAB, MD_KEY_KID, NULL);
+ if (acct->eab_hmac) md_json_sets(acct->eab_hmac, jacct, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+
return jacct;
}
@@ -129,22 +131,17 @@ apr_status_t md_acme_acct_from_json(md_acme_acct_t **pacct, md_json_t *json, apr
if (md_json_has_key(json, MD_KEY_STATUS, NULL)) {
status = acct_st_from_str(md_json_gets(json, MD_KEY_STATUS, NULL));
}
- else {
- /* old accounts only had disabled boolean field */
- status = md_json_getb(json, MD_KEY_DISABLED, NULL)?
- MD_ACME_ACCT_ST_DEACTIVATED : MD_ACME_ACCT_ST_VALID;
- }
-
+
url = md_json_gets(json, MD_KEY_URL, NULL);
if (!url) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no url");
- goto out;
+ goto leave;
}
ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
if (!ca_url) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no CA url: %s", url);
- goto out;
+ goto leave;
}
contacts = apr_array_make(p, 5, sizeof(const char *));
@@ -155,14 +152,23 @@ apr_status_t md_acme_acct_from_json(md_acme_acct_t **pacct, md_json_t *json, apr
md_json_getsa(contacts, json, MD_KEY_REGISTRATION, MD_KEY_CONTACT, NULL);
}
rv = acct_make(&acct, p, ca_url, contacts);
- if (APR_SUCCESS == rv) {
- acct->status = status;
- acct->url = url;
+ if (APR_SUCCESS != rv) goto leave;
+
+ acct->status = status;
+ acct->url = url;
+ acct->agreement = md_json_gets(json, MD_KEY_AGREEMENT, NULL);
+ if (!acct->agreement) {
+ /* backward compatible check */
acct->agreement = md_json_gets(json, "terms-of-service", NULL);
- acct->orders = md_json_gets(json, MD_KEY_ORDERS, NULL);
+ }
+ acct->orders = md_json_gets(json, MD_KEY_ORDERS, NULL);
+ if (md_json_has_key(json, MD_KEY_EAB, MD_KEY_KID, NULL)
+ && md_json_has_key(json, MD_KEY_EAB, MD_KEY_HMAC, NULL)) {
+ acct->eab_kid = md_json_gets(json, MD_KEY_EAB, MD_KEY_KID, NULL);
+ acct->eab_hmac = md_json_gets(json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
}
-out:
+leave:
*pacct = (APR_SUCCESS == rv)? acct : NULL;
return rv;
}
@@ -228,10 +234,36 @@ out:
/**************************************************************************************************/
/* Lookup */
+int md_acme_acct_matches_url(md_acme_acct_t *acct, const char *url)
+{
+ /* The ACME url must match exactly */
+ if (!url || !acct->ca_url || strcmp(acct->ca_url, url)) return 0;
+ return 1;
+}
+
+int md_acme_acct_matches_md(md_acme_acct_t *acct, const md_t *md)
+{
+ if (!md_acme_acct_matches_url(acct, md->ca_url)) return 0;
+ /* if eab values are not mentioned, we match an account regardless
+ * if it was registered with eab or not */
+ if (!md->ca_eab_kid || !md->ca_eab_hmac) {
+ /* No eab only acceptable when no eab is asked for.
+ * This prevents someone that has no external account binding
+ * to re-use an account from another MDomain that was created
+ * with a binding. */
+ return !acct->eab_kid || !acct->eab_hmac;
+ }
+ /* But of eab is asked for, we need an acct that matches exactly.
+ * When someone configures a new EAB and we need
+ * to created a new account for it. */
+ if (!acct->eab_kid || !acct->eab_hmac) return 0;
+ return !strcmp(acct->eab_kid, md->ca_eab_kid)
+ && !strcmp(acct->eab_hmac, md->ca_eab_hmac);
+}
+
typedef struct {
apr_pool_t *p;
- md_acme_t *acme;
- int url_match;
+ const md_t *md;
const char *id;
} find_ctx;
@@ -239,50 +271,46 @@ static int find_acct(void *baton, const char *name, const char *aspect,
md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
{
find_ctx *ctx = baton;
- int disabled;
- const char *ca_url, *status;
-
+ md_acme_acct_t *acct;
+ apr_status_t rv;
+
(void)aspect;
(void)ptemp;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p, "account candidate %s/%s", name, aspect);
if (MD_SV_JSON == vtype) {
- md_json_t *json = value;
-
- status = md_json_gets(json, MD_KEY_STATUS, NULL);
- disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
- ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
-
- if ((!status || !strcmp("valid", status)) && !disabled
- && (!ctx->url_match || (ca_url && !strcmp(ctx->acme->url, ca_url)))) {
+ rv = md_acme_acct_from_json(&acct, (md_json_t*)value, ptemp);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (MD_ACME_ACCT_ST_VALID == acct->status
+ && (!ctx->md || md_acme_acct_matches_md(acct, ctx->md))) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p,
- "found account %s for %s: %s, status=%s, disabled=%d, ca-url=%s",
- name, ctx->acme->url, aspect, status, disabled, ca_url);
+ "found account %s for %s: %s, status=%d",
+ acct->id, ctx->md->ca_url, aspect, acct->status);
ctx->id = apr_pstrdup(ctx->p, name);
return 0;
}
}
+cleanup:
return 1;
}
static apr_status_t acct_find(const char **pid, md_acme_acct_t **pacct, md_pkey_t **ppkey,
md_store_t *store, md_store_group_t group,
- const char *name_pattern, int url_match,
- md_acme_t *acme, apr_pool_t *p)
+ const char *name_pattern,
+ const md_t *md, apr_pool_t *p)
{
apr_status_t rv;
find_ctx ctx;
-
+
+ memset(&ctx, 0, sizeof(ctx));
ctx.p = p;
- ctx.acme = acme;
- ctx.id = NULL;
- ctx.url_match = url_match;
- *pid = NULL;
-
+ ctx.md = md;
+
rv = md_store_iter(find_acct, &ctx, store, p, group, name_pattern, MD_FN_ACCOUNT, MD_SV_JSON);
if (ctx.id) {
*pid = ctx.id;
rv = md_acme_acct_load(pacct, ppkey, store, group, ctx.id, p);
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "loading account %s", ctx.id);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_find: got account %s", ctx.id);
}
else {
*pacct = NULL;
@@ -293,19 +321,26 @@ static apr_status_t acct_find(const char **pid, md_acme_acct_t **pacct, md_pkey_
}
static apr_status_t acct_find_and_verify(md_store_t *store, md_store_group_t group,
- const char *name_pattern, md_acme_t *acme, apr_pool_t *p)
+ const char *name_pattern,
+ md_acme_t *acme, const md_t *md,
+ apr_pool_t *p)
{
md_acme_acct_t *acct;
md_pkey_t *pkey;
const char *id;
apr_status_t rv;
- if (APR_SUCCESS == (rv = acct_find(&id, &acct, &pkey, store, group, name_pattern, 1, acme, p))) {
+ rv = acct_find(&id, &acct, &pkey, store, group, name_pattern, md, p);
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, "acct_find_and_verify: found %s",
+ id);
acme->acct_id = (MD_SG_STAGING == group)? NULL : id;
acme->acct = acct;
acme->acct_key = pkey;
- rv = md_acme_acct_validate(acme, NULL, p);
-
+ rv = md_acme_acct_validate(acme, (MD_SG_STAGING == group)? NULL : store, p);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p, "acct_find_and_verify: verified %s",
+ id);
+
if (APR_SUCCESS != rv) {
acme->acct_id = NULL;
acme->acct = NULL;
@@ -320,13 +355,13 @@ static apr_status_t acct_find_and_verify(md_store_t *store, md_store_group_t gro
return rv;
}
-apr_status_t md_acme_find_acct(md_acme_t *acme, md_store_t *store)
+apr_status_t md_acme_find_acct_for_md(md_acme_t *acme, md_store_t *store, const md_t *md)
{
apr_status_t rv;
while (APR_EAGAIN == (rv = acct_find_and_verify(store, MD_SG_ACCOUNTS,
mk_acct_pattern(acme->p, acme),
- acme, acme->p))) {
+ acme, md, acme->p))) {
/* nop */
}
@@ -335,61 +370,31 @@ apr_status_t md_acme_find_acct(md_acme_t *acme, md_store_t *store)
* can already be found in MD_SG_STAGING? */
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p,
"no account found, looking in STAGING");
- while (APR_EAGAIN == (rv = acct_find_and_verify(store, MD_SG_STAGING, "*",
- acme, acme->p))) {
- /* nop */
+ rv = acct_find_and_verify(store, MD_SG_STAGING, "*", acme, md, acme->p);
+ if (APR_EAGAIN == rv) {
+ rv = APR_ENOENT;
}
}
return rv;
}
-typedef struct {
- apr_pool_t *p;
- const char *url;
- const char *id;
-} load_ctx;
-
-static int id_by_url(void *baton, const char *name, const char *aspect,
- md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
-{
- load_ctx *ctx = baton;
- int disabled;
- const char *acct_url, *status;
-
- (void)aspect;
- (void)ptemp;
- if (MD_SV_JSON == vtype) {
- md_json_t *json = value;
-
- status = md_json_gets(json, MD_KEY_STATUS, NULL);
- disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
- acct_url = md_json_gets(json, MD_KEY_URL, NULL);
-
- if ((!status || !strcmp("valid", status)) && !disabled
- && acct_url && !strcmp(ctx->url, acct_url)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p,
- "found account %s for url %s: %s, status=%s, disabled=%d",
- name, ctx->url, aspect, status, disabled);
- ctx->id = apr_pstrdup(ctx->p, name);
- return 0;
- }
- }
- return 1;
-}
-
-apr_status_t md_acme_acct_id_for_url(const char **pid, md_store_t *store,
- md_store_group_t group, const char *url, apr_pool_t *p)
+apr_status_t md_acme_acct_id_for_md(const char **pid, md_store_t *store,
+ md_store_group_t group, const md_t *md,
+ apr_pool_t *p)
{
apr_status_t rv;
- load_ctx ctx;
-
+ find_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
ctx.p = p;
- ctx.url = url;
- ctx.id = NULL;
-
- rv = md_store_iter(id_by_url, &ctx, store, p, group, "*", MD_FN_ACCOUNT, MD_SV_JSON);
- *pid = (APR_SUCCESS == rv)? ctx.id : NULL;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_id_by_url %s -> %s", url, *pid);
+ ctx.md = md;
+
+ rv = md_store_iter(find_acct, &ctx, store, p, group, "*", MD_FN_ACCOUNT, MD_SV_JSON);
+ if (ctx.id) {
+ *pid = ctx.id;
+ rv = APR_SUCCESS;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_id_for_md %s -> %s", md->name, *pid);
return rv;
}
@@ -399,6 +404,8 @@ typedef struct {
md_acme_t *acme;
apr_pool_t *p;
const char *agreement;
+ const char *eab_kid;
+ const char *eab_hmac;
} acct_ctx_t;
/**************************************************************************************************/
@@ -416,7 +423,12 @@ static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p,
acct_ctx_t *ctx = baton;
apr_status_t rv = APR_SUCCESS;
md_acme_acct_t *acct = acme->acct;
-
+
+ if (md_log_is_level(p, MD_LOG_TRACE2)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, acme->p, "acct update response: %s",
+ md_json_writep(body, p, MD_JSON_FMT_COMPACT));
+ }
+
if (!acct->url) {
const char *location = apr_table_get(hdrs, "location");
if (!location) {
@@ -437,6 +449,10 @@ static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p,
if (md_json_has_key(body, MD_KEY_ORDERS, NULL)) {
acct->orders = md_json_dups(acme->p, body, MD_KEY_ORDERS, NULL);
}
+ if (ctx->eab_kid && ctx->eab_hmac) {
+ acct->eab_kid = ctx->eab_kid;
+ acct->eab_hmac = ctx->eab_hmac;
+ }
acct->registration = md_json_clone(ctx->p, body);
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "updated acct %s", acct->url);
@@ -451,6 +467,7 @@ apr_status_t md_acme_acct_update(md_acme_t *acme)
if (!acme->acct) {
return APR_EINVAL;
}
+ memset(&ctx, 0, sizeof(ctx));
ctx.acme = acme;
ctx.p = acme->p;
return md_acme_POST(acme, acme->acct->url, on_init_acct_upd, acct_upd, NULL, NULL, &ctx);
@@ -461,7 +478,16 @@ apr_status_t md_acme_acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_
apr_status_t rv;
if (APR_SUCCESS != (rv = md_acme_acct_update(acme))) {
- if (acme->acct && (APR_ENOENT == rv || APR_EACCES == rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, acme->p,
+ "acct update failed for %s", acme->acct->url);
+ if (APR_EINVAL == rv && (acme->acct->agreement || !acme->ca_agreement)) {
+ /* Sadly, some proprietary ACME servers choke on empty POSTs
+ * on accounts. Try a faked ToS agreement. */
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, acme->p,
+ "trying acct update via ToS agreement");
+ rv = md_acme_agree(acme, p, "accepted");
+ }
+ if (acme->acct && (APR_ENOENT == rv || APR_EACCES == rv || APR_EINVAL == rv)) {
if (MD_ACME_ACCT_ST_VALID == acme->acct->status) {
acme->acct->status = MD_ACME_ACCT_ST_UNKNOWN;
if (store) {
@@ -479,21 +505,73 @@ apr_status_t md_acme_acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_
/**************************************************************************************************/
/* Register a new account */
+static apr_status_t get_eab(md_json_t **peab, md_acme_req_t *req, const char *kid,
+ const char *hmac64, md_pkey_t *account_key,
+ const char *url)
+{
+ md_json_t *eab, *prot_fields, *jwk;
+ md_data_t payload, hmac_key;
+ apr_status_t rv;
+
+ prot_fields = md_json_create(req->p);
+ md_json_sets(url, prot_fields, "url", NULL);
+ md_json_sets(kid, prot_fields, "kid", NULL);
+
+ rv = md_jws_get_jwk(&jwk, req->p, account_key);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ md_data_null(&payload);
+ payload.data = md_json_writep(jwk, req->p, MD_JSON_FMT_COMPACT);
+ if (!payload.data) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ payload.len = strlen(payload.data);
+
+ md_util_base64url_decode(&hmac_key, hmac64, req->p);
+ if (!hmac_key.len) {
+ rv = APR_EINVAL;
+ md_result_problem_set(req->result, rv, "apache:eab-hmac-invalid",
+ "external account binding HMAC value is not valid base64", NULL);
+ goto cleanup;
+ }
+
+ rv = md_jws_hmac(&eab, req->p, &payload, prot_fields, &hmac_key);
+ if (APR_SUCCESS != rv) {
+ md_result_problem_set(req->result, rv, "apache:eab-hmac-fail",
+ "external account binding MAC could not be computed", NULL);
+ }
+
+cleanup:
+ *peab = (APR_SUCCESS == rv)? eab : NULL;
+ return rv;
+}
+
static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
{
acct_ctx_t *ctx = baton;
- md_json_t *jpayload;
+ md_json_t *jpayload, *jeab;
+ apr_status_t rv;
jpayload = md_json_create(req->p);
md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
if (ctx->agreement) {
md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
}
- return md_acme_req_body_init(req, jpayload);
+ if (ctx->eab_kid && ctx->eab_hmac) {
+ rv = get_eab(&jeab, req, ctx->eab_kid, ctx->eab_hmac,
+ req->acme->acct_key, req->url);
+ if (APR_SUCCESS != rv) goto cleanup;
+ md_json_setj(jeab, jpayload, "externalAccountBinding", NULL);
+ }
+ rv = md_acme_req_body_init(req, jpayload);
+
+cleanup:
+ return rv;
}
-apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store, apr_pool_t *p,
- apr_array_header_t *contacts, const char *agreement)
+apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store,
+ const md_t *md, apr_pool_t *p)
{
apr_status_t rv;
md_pkey_t *pkey;
@@ -503,15 +581,17 @@ apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store, apr_pool_
acct_ctx_t ctx;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account");
-
+
+ memset(&ctx, 0, sizeof(ctx));
ctx.acme = acme;
ctx.p = p;
/* The agreement URL is submitted when the ACME server announces Terms-of-Service
* in its directory meta data. The magic value "accepted" will always use the
* advertised URL. */
ctx.agreement = NULL;
- if (acme->ca_agreement && agreement) {
- ctx.agreement = !strcmp("accepted", agreement)? acme->ca_agreement : agreement;
+ if (acme->ca_agreement && md->ca_agreement) {
+ ctx.agreement = !strcmp("accepted", md->ca_agreement)?
+ acme->ca_agreement : md->ca_agreement;
}
if (ctx.agreement) {
@@ -521,9 +601,11 @@ apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store, apr_pool_
goto out;
}
}
+ ctx.eab_kid = md->ca_eab_kid;
+ ctx.eab_hmac = md->ca_eab_hmac;
- for (i = 0; i < contacts->nelts; ++i) {
- uri = APR_ARRAY_IDX(contacts, i, const char *);
+ for (i = 0; i < md->contacts->nelts; ++i) {
+ uri = APR_ARRAY_IDX(md->contacts, i, const char *);
if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, uri, &err))) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
"invalid contact uri (%s): %s", err, uri);
@@ -540,19 +622,19 @@ apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store, apr_pool_
*/
if (!acme->acct_key) {
find_ctx fctx;
-
+
+ memset(&fctx, 0, sizeof(fctx));
fctx.p = p;
- fctx.acme = acme;
- fctx.id = NULL;
- fctx.url_match = 0;
-
+ fctx.md = md;
+
md_store_iter(find_acct, &fctx, store, p, MD_SG_ACCOUNTS,
mk_acct_pattern(p, acme), MD_FN_ACCOUNT, MD_SV_JSON);
if (fctx.id) {
- rv = md_store_load(store, MD_SG_ACCOUNTS, fctx.id, MD_FN_ACCT_KEY, MD_SV_PKEY,
+ rv = md_store_load(store, MD_SG_ACCOUNTS, fctx.id, MD_FN_ACCT_KEY, MD_SV_PKEY,
(void**)&acme->acct_key, p);
if (APR_SUCCESS == rv) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "reusing key from account %s", fctx.id);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "reusing key from account %s", fctx.id);
}
else {
acme->acct_key = NULL;
@@ -570,7 +652,7 @@ apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store, apr_pool_
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "created new account key");
}
- if (APR_SUCCESS != (rv = acct_make(&acme->acct, p, acme->url, contacts))) goto out;
+ if (APR_SUCCESS != (rv = acct_make(&acme->acct, p, acme->url, md->contacts))) goto out;
rv = md_acme_POST_new_account(acme, on_init_acct_new, acct_upd, NULL, NULL, &ctx);
if (APR_SUCCESS == rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p,
@@ -608,6 +690,7 @@ apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p)
}
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "delete account %s from %s",
acct->url, acct->ca_url);
+ memset(&ctx, 0, sizeof(ctx));
ctx.acme = acme;
ctx.p = p;
return md_acme_POST(acme, acct->url, on_init_acct_del, acct_upd, NULL, NULL, &ctx);
@@ -637,6 +720,7 @@ apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *agreement
acme->acct->agreement = acme->ca_agreement;
}
+ memset(&ctx, 0, sizeof(ctx));
ctx.acme = acme;
ctx.p = p;
return md_acme_POST(acme, acme->acct->url, on_init_agree_tos, acct_upd, NULL, NULL, &ctx);
diff --git a/modules/md/md_acme_acct.h b/modules/md/md_acme_acct.h
index c2cf64c7e4..b5bba631f4 100644
--- a/modules/md/md_acme_acct.h
+++ b/modules/md/md_acme_acct.h
@@ -44,6 +44,8 @@ struct md_acme_acct_t {
const char *tos_required; /* terms of service asked for by CA */
const char *agreement; /* terms of service agreed to by user */
const char *orders; /* URL where certificate orders are found (ACMEv2) */
+ const char *eab_kid; /* external account binding keyid used or NULL */
+ const char *eab_hmac; /* external account binding hmac used or NULL */
struct md_json_t *registration; /* data from server registration */
};
@@ -104,21 +106,20 @@ 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, md_store_t *store);
+apr_status_t md_acme_find_acct_for_md(md_acme_t *acme, md_store_t *store, const md_t *md);
/**
- * Find the account id for a given account url.
+ * Find the account id for a given md.
*/
-apr_status_t md_acme_acct_id_for_url(const char **pid, md_store_t *store,
- md_store_group_t group, const char *url, apr_pool_t *p);
+apr_status_t md_acme_acct_id_for_md(const char **pid, md_store_t *store,
+ md_store_group_t group, const md_t *md, apr_pool_t *p);
/**
- * Create a new account at the ACME server. The
+ * Create a new account at the ACME server for an MD. The
* new account is the one used by the acme instance afterwards, on success.
*/
apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store,
- apr_pool_t *p, apr_array_header_t *contacts,
- const char *agreement);
+ const md_t *md, apr_pool_t *p);
apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme,
const char **pid, struct md_acme_acct_t *acct,
@@ -133,4 +134,15 @@ apr_status_t md_acme_acct_load(struct md_acme_acct_t **pacct, struct md_pkey_t *
md_store_t *store, md_store_group_t group,
const char *name, apr_pool_t *p);
+/*
+ * Return != 0 iff the account can be used for the ACME url.
+ */
+int md_acme_acct_matches_url(md_acme_acct_t *acct, const char *url);
+
+/*
+ * Return != 0 iff the account can be used for the MD, including
+ * its CA url and EAB settings.
+ */
+int md_acme_acct_matches_md(md_acme_acct_t *acct, const md_t *md);
+
#endif /* md_acme_acct_h */
diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c
index d5632fa6b6..01431328f1 100644
--- a/modules/md/md_acme_authz.c
+++ b/modules/md/md_acme_authz.c
@@ -393,7 +393,13 @@ static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_auth
/* Raise event that challenge data has been set up before we tell the
ACME server. Clusters might want to distribute it. */
event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_TLSALPN01, authz->domain);
- md_result_holler(result, event, p);
+ rv = md_result_raise(result, event, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: event '%s' failed. aborting challenge setup",
+ authz->domain, event);
+ goto out;
+ }
/* challenge is setup or was changed from previous data, tell ACME server
* so it may (re)try verification */
authz_req_ctx_init(&ctx, acme, NULL, authz, p);
@@ -463,7 +469,13 @@ static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *
/* Raise event that challenge data has been set up before we tell the
ACME server. Clusters might want to distribute it. */
event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_DNS01, authz->domain);
- md_result_holler(result, event, p);
+ rv = md_result_raise(result, event, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: event '%s' failed. aborting challenge setup",
+ authz->domain, event);
+ goto out;
+ }
/* challenge is setup, tell ACME server so it may (re)try verification */
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 setup succeeded for %s",
mdomain, authz->domain);
diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c
index 5e3b48cf91..6c461dc9af 100644
--- a/modules/md/md_acme_drive.c
+++ b/modules/md/md_acme_drive.c
@@ -46,14 +46,14 @@
/* account setup */
static apr_status_t use_staged_acct(md_acme_t *acme, struct md_store_t *store,
- const char *md_name, apr_pool_t *p)
+ const md_t *md, apr_pool_t *p)
{
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_STAGING, md_name, acme->p))) {
+ MD_SG_STAGING, md->name, acme->p))) {
acme->acct_id = NULL;
acme->acct = acct;
acme->acct_key = pkey;
@@ -89,7 +89,7 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
md_acme_clear_acct(ad->acme);
/* Do we have a staged (modified) account? */
- if (APR_SUCCESS == (rv = use_staged_acct(ad->acme, d->store, md->name, d->p))) {
+ if (APR_SUCCESS == (rv = use_staged_acct(ad->acme, d->store, md, d->p))) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-using staged account");
}
else if (!APR_STATUS_IS_ENOENT(rv)) {
@@ -99,7 +99,7 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
/* Get an account for the ACME server for this MD */
if (!ad->acme->acct && md->ca_account) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-use account '%s'", md->ca_account);
- rv = md_acme_use_acct(ad->acme, d->store, d->p, md->ca_account);
+ rv = md_acme_use_acct_for_md(ad->acme, d->store, d->p, md->ca_account, md);
if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_EINVAL(rv)) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "rejected %s", md->ca_account);
md->ca_account = NULL;
@@ -114,7 +114,7 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
/* Find a local account for server, store at MD */
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: looking at existing accounts",
d->proto->protocol);
- if (APR_SUCCESS == (rv = md_acme_find_acct(ad->acme, d->store))) {
+ if (APR_SUCCESS == (rv = md_acme_find_acct_for_md(ad->acme, d->store, md))) {
md->ca_account = md_acme_acct_id_get(ad->acme);
update_md = 1;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: using account %s (id=%s)",
@@ -153,7 +153,19 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
goto leave;
}
- rv = md_acme_acct_register(ad->acme, d->store, d->p, md->contacts, md->ca_agreement);
+ if (ad->acme->eab_required && (!md->ca_eab_kid || !strcmp("none", md->ca_eab_kid))) {
+ md_result_printf(result, APR_EINVAL,
+ "the CA requires 'External Account Binding' which is not "
+ "configured. This means you need to obtain a 'Key ID' and a "
+ "'HMAC' from the CA and configure that using the "
+ "MDExternalAccountBinding directive in your config. "
+ "The creation of a new ACME account will most likely fail, "
+ "but an attempt is made anyway.",
+ ad->acme->ca_agreement);
+ md_result_log(result, MD_LOG_INFO);
+ }
+
+ rv = md_acme_acct_register(ad->acme, d->store, md, d->p);
if (APR_SUCCESS != rv) {
if (APR_SUCCESS != ad->acme->last->status) {
md_result_dup(result, ad->acme->last);
@@ -169,11 +181,11 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
leave:
/* Persist MD changes in STAGING, so we pick them up on next run */
- if (APR_SUCCESS == rv&& update_md) {
+ if (APR_SUCCESS == rv && update_md) {
rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
}
/* Persist account changes in STAGING, so we pick them up on next run */
- if (APR_SUCCESS == rv&& update_acct) {
+ if (APR_SUCCESS == rv && update_acct) {
rv = save_acct_staged(ad->acme, d->store, md->name, d->p);
}
return rv;
@@ -894,6 +906,7 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro
md_credentials_t *creds;
apr_array_header_t *all_creds;
struct md_acme_acct_t *acct;
+ const char *id;
int i;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name);
@@ -953,7 +966,6 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro
if (acct) {
md_acme_t *acme;
- const char *id = md->ca_account;
/* We may have STAGED the same account several times. This happens when
* several MDs are renewed at once and need a new account. They will all store
@@ -961,8 +973,9 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro
* the same url, we save them all into a single one.
*/
md_result_activity_setn(result, "saving staged account");
- if (!id && acct->url) {
- rv = md_acme_acct_id_for_url(&id, d->store, MD_SG_ACCOUNTS, acct->url, d->p);
+ id = md->ca_account;
+ if (!id) {
+ rv = md_acme_acct_id_for_md(&id, d->store, MD_SG_ACCOUNTS, md, d->p);
if (APR_STATUS_IS_ENOENT(rv)) {
id = NULL;
}
@@ -983,6 +996,14 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro
}
md->ca_account = id;
}
+ else if (!md->ca_account) {
+ /* staging reused another account and did not create a new one. find
+ * the account, if it is already there */
+ rv = md_acme_acct_id_for_md(&id, d->store, MD_SG_ACCOUNTS, md, d->p);
+ if (APR_SUCCESS == rv) {
+ md->ca_account = id;
+ }
+ }
md_result_activity_setn(result, "saving staged md/privkey/pubcert");
if (APR_SUCCESS != (rv = md_save(d->store, d->p, load_group, md, 1))) {
diff --git a/modules/md/md_acme_order.c b/modules/md/md_acme_order.c
index ee1166be1d..8037c762f6 100644
--- a/modules/md/md_acme_order.c
+++ b/modules/md/md_acme_order.c
@@ -397,10 +397,15 @@ static apr_status_t await_valid(void *baton, int attempt)
ctx->result, ctx->p))) goto out;
switch (ctx->order->status) {
case MD_ACME_ORDER_ST_VALID:
+ md_result_set(ctx->result, APR_EINVAL, "ACME server order status is 'valid'.");
break;
case MD_ACME_ORDER_ST_PROCESSING:
rv = APR_EAGAIN;
break;
+ case MD_ACME_ORDER_ST_INVALID:
+ md_result_set(ctx->result, APR_EINVAL, "ACME server order status is 'invalid'.");
+ rv = APR_EINVAL;
+ break;
default:
rv = APR_EINVAL;
break;
@@ -515,12 +520,10 @@ static apr_status_t check_challenges(void *baton, int attempt)
goto leave;
case MD_ACME_AUTHZ_S_INVALID:
rv = APR_EINVAL;
- if (!authz->error_type) {
- md_result_printf(ctx->result, rv,
- "domain authorization for %s failed, CA considers "
- "answer to challenge invalid, no error given",
- authz->domain);
- }
+ md_result_printf(ctx->result, rv,
+ "domain authorization for %s failed, CA considers "
+ "answer to challenge invalid%s.",
+ authz->domain, authz->error_type? "" : ", no error given");
md_result_log(ctx->result, MD_LOG_ERR);
goto leave;
default:
diff --git a/modules/md/md_acme_order.h b/modules/md/md_acme_order.h
index ec945c7e5c..868fb56b13 100644
--- a/modules/md/md_acme_order.h
+++ b/modules/md/md_acme_order.h
@@ -63,8 +63,7 @@ apr_status_t md_acme_order_purge(struct md_store_t *store, apr_pool_t *p,
md_store_group_t group, const char *md_name,
apr_table_t *env);
-
-apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme,
+apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme,
apr_array_header_t *challenge_types,
md_store_t *store, const md_t *md,
apr_table_t *env, struct md_result_t *result,
diff --git a/modules/md/md_acmev2_drive.c b/modules/md/md_acmev2_drive.c
index 40add6f565..abb985c44d 100644
--- a/modules/md/md_acmev2_drive.c
+++ b/modules/md/md_acmev2_drive.c
@@ -51,7 +51,7 @@
* Either we have an order stored in the STAGING area, or we need to create a
* new one at the ACME server.
*/
-static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result)
+static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result, int *pis_new)
{
md_acme_driver_t *ad = d->baton;
apr_status_t rv;
@@ -65,6 +65,7 @@ static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result)
* if known AUTHZ resource is not valid, remove, goto 4.1.1
* if no AUTHZ available, create a new one for the domain, store it
*/
+ if (pis_new) *pis_new = 0;
rv = md_acme_order_load(d->store, MD_SG_STAGING, md->name, &ad->order, d->p);
if (APR_SUCCESS == rv) {
md_result_activity_setn(result, "Loaded order from staging");
@@ -82,7 +83,8 @@ static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result)
if (APR_SUCCESS != rv) {
md_result_set(result, rv, "saving order in staging");
}
-
+ if (pis_new) *pis_new = 1;
+
leave:
md_acme_report_result(ad->acme, rv, result);
return rv;
@@ -94,7 +96,8 @@ leave:
apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, md_result_t *result)
{
apr_status_t rv = APR_SUCCESS;
-
+ int is_new_order = 0;
+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: (ACMEv2) need certificate", d->md->name);
/* Chose (or create) and ACME account to use */
@@ -115,27 +118,36 @@ apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m
* * COMPLETE: all done, return success
* * INVALID and otherwise: fail renewal, delete local order
*/
- if (APR_SUCCESS != (rv = ad_setup_order(d, result))) {
+ if (APR_SUCCESS != (rv = ad_setup_order(d, result, &is_new_order))) {
goto leave;
}
rv = md_acme_order_update(ad->order, ad->acme, result, d->p);
- if (APR_STATUS_IS_ENOENT(rv)) {
- /* order is no longer known at the ACME server */
+ if (APR_STATUS_IS_ENOENT(rv)
+ || APR_STATUS_IS_EACCES(rv)
+ || MD_ACME_ORDER_ST_INVALID == ad->order->status) {
+ /* order is invalid or no longer known at the ACME server */
ad->order = NULL;
md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env);
}
else if (APR_SUCCESS != rv) {
goto leave;
}
-
+
+retry:
if (!ad->order) {
- rv = ad_setup_order(d, result);
+ rv = ad_setup_order(d, result, &is_new_order);
if (APR_SUCCESS != rv) goto leave;
}
rv = md_acme_order_start_challenges(ad->order, ad->acme, ad->ca_challenges,
d->store, d->md, d->env, result, d->p);
+ if (!is_new_order && APR_STATUS_IS_EINVAL(rv)) {
+ /* found 'invalid' domains in previous order, need to start over */
+ ad->order = NULL;
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env);
+ goto retry;
+ }
if (APR_SUCCESS != rv) goto leave;
rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md,
diff --git a/modules/md/md_core.c b/modules/md/md_core.c
index 9b696e2b99..620b809984 100644
--- a/modules/md/md_core.c
+++ b/modules/md/md_core.c
@@ -19,6 +19,7 @@
#include <apr_lib.h>
#include <apr_strings.h>
+#include <apr_uri.h>
#include <apr_tables.h>
#include <apr_time.h>
#include <apr_date.h>
@@ -277,6 +278,8 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
md_json_setj(md_pkeys_spec_to_json(md->pks, p), json, MD_KEY_PKEY, NULL);
}
md_json_setl(md->state, json, MD_KEY_STATE, NULL);
+ if (md->state_descr)
+ md_json_sets(md->state_descr, json, MD_KEY_STATE_DESCR, NULL);
md_json_setl(md->renew_mode, json, MD_KEY_RENEW_MODE, NULL);
if (md->renew_window)
md_json_sets(md_timeslice_format(md->renew_window, p), json, MD_KEY_RENEW_WINDOW, NULL);
@@ -302,6 +305,10 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
if (md->cert_files) md_json_setsa(md->cert_files, json, MD_KEY_CERT_FILES, NULL);
if (md->pkey_files) md_json_setsa(md->pkey_files, json, MD_KEY_PKEY_FILES, NULL);
md_json_setb(md->stapling > 0, json, MD_KEY_STAPLING, NULL);
+ if (md->ca_eab_kid && strcmp("none", md->ca_eab_kid)) {
+ md_json_sets(md->ca_eab_kid, json, MD_KEY_EAB, MD_KEY_KID, NULL);
+ if (md->ca_eab_hmac) md_json_sets(md->ca_eab_hmac, json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+ }
return json;
}
return NULL;
@@ -323,6 +330,7 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
md->pks = md_pkeys_spec_from_json(md_json_getj(json, MD_KEY_PKEY, NULL), p);
}
md->state = (md_state_t)md_json_getl(json, MD_KEY_STATE, NULL);
+ md->state_descr = md_json_dups(p, json, MD_KEY_STATE_DESCR, NULL);
if (MD_S_EXPIRED_DEPRECATED == md->state) md->state = MD_S_COMPLETE;
md->renew_mode = (int)md_json_getl(json, MD_KEY_RENEW_MODE, NULL);
md->domains = md_array_str_compact(p, md->domains, 0);
@@ -354,8 +362,84 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
}
md->stapling = (int)md_json_getb(json, MD_KEY_STAPLING, NULL);
+ if (md_json_has_key(json, MD_KEY_EAB, NULL)) {
+ md->ca_eab_kid = md_json_dups(p, json, MD_KEY_EAB, MD_KEY_KID, NULL);
+ md->ca_eab_hmac = md_json_dups(p, json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+ }
return md;
}
return NULL;
}
+md_json_t *md_to_public_json(const md_t *md, apr_pool_t *p)
+{
+ md_json_t *json = md_to_json(md, p);
+ if (md_json_has_key(json, MD_KEY_EAB, MD_KEY_HMAC, NULL)) {
+ md_json_sets("***", json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+ }
+ return json;
+}
+
+typedef struct {
+ const char *name;
+ const char *url;
+} md_ca_t;
+
+#define LE_ACMEv2_PROD "https://acme-v02.api.letsencrypt.org/directory"
+#define LE_ACMEv2_STAGING "https://acme-staging-v02.api.letsencrypt.org/directory"
+#define BUYPASS_ACME "https://api.buypass.com/acme/directory"
+#define BUYPASS_ACME_TEST "https://api.test4.buypass.no/acme/directory"
+
+static md_ca_t KNOWN_CAs[] = {
+ { "LetsEncrypt", LE_ACMEv2_PROD },
+ { "LetsEncrypt-Test", LE_ACMEv2_STAGING },
+ { "Buypass", BUYPASS_ACME },
+ { "Buypass-Test", BUYPASS_ACME_TEST },
+};
+
+const char *md_get_ca_name_from_url(apr_pool_t *p, const char *url)
+{
+ apr_uri_t uri_parsed;
+ unsigned int i;
+
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ if (!apr_strnatcasecmp(KNOWN_CAs[i].url, url)) {
+ return KNOWN_CAs[i].name;
+ }
+ }
+ if (APR_SUCCESS == apr_uri_parse(p, url, &uri_parsed)) {
+ return uri_parsed.hostname;
+ }
+ return apr_pstrdup(p, url);
+}
+
+apr_status_t md_get_ca_url_from_name(const char **purl, apr_pool_t *p, const char *name)
+{
+ const char *err;
+ unsigned int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ *purl = NULL;
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ if (!apr_strnatcasecmp(KNOWN_CAs[i].name, name)) {
+ *purl = KNOWN_CAs[i].url;
+ goto leave;
+ }
+ }
+ *purl = name;
+ rv = md_util_abs_http_uri_check(p, name, &err);
+ if (APR_SUCCESS != rv) {
+ apr_array_header_t *names;
+
+ names = apr_array_make(p, 10, sizeof(const char*));
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ APR_ARRAY_PUSH(names, const char *) = KNOWN_CAs[i].name;
+ }
+ *purl = apr_psprintf(p,
+ "The CA name '%s' is not known and it is not a URL either (%s). "
+ "Known CA names are: %s.",
+ name, err, apr_array_pstrcat(p, names, ' '));
+ }
+leave:
+ return rv;
+}
diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c
index 1cb5c0473d..81dc7aed32 100644
--- a/modules/md/md_crypt.c
+++ b/modules/md/md_crypt.c
@@ -27,6 +27,7 @@
#include <openssl/err.h>
#include <openssl/evp.h>
+#include <openssl/hmac.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
@@ -643,7 +644,6 @@ static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *
const EVP_CIPHER *cipher = NULL;
pem_password_cb *cb = NULL;
void *cb_baton = NULL;
- apr_status_t rv = APR_SUCCESS;
passwd_ctx ctx;
unsigned long err;
int i;
@@ -652,8 +652,7 @@ static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *
return APR_ENOMEM;
}
if (pass_len > INT_MAX) {
- rv = APR_EINVAL;
- goto cleanup;
+ return APR_EINVAL;
}
if (pass && pass_len > 0) {
ctx.pass_phrase = pass;
@@ -662,8 +661,7 @@ static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *
cb_baton = &ctx;
cipher = EVP_aes_256_cbc();
if (!cipher) {
- rv = APR_ENOTIMPL;
- goto cleanup;
+ return APR_ENOTIMPL;
}
}
@@ -673,11 +671,11 @@ static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *
#else
if (!PEM_write_bio_PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) {
#endif
+ BIO_free(bio);
err = ERR_get_error();
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "PEM_write key: %ld %s",
err, ERR_error_string(err, NULL));
- rv = APR_EINVAL;
- goto cleanup;
+ return APR_EINVAL;
}
md_data_null(buf);
@@ -687,10 +685,8 @@ static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *
i = BIO_read(bio, (char*)buf->data, i);
buf->len = (apr_size_t)i;
}
-
-cleanup:
BIO_free(bio);
- return rv;
+ return APR_SUCCESS;
}
apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
@@ -992,8 +988,6 @@ static apr_status_t sha256_digest(md_data_t **pdigest, apr_pool_t *p, const md_d
unsigned int dlen;
digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
- if (!digest) goto leave;
-
ctx = EVP_MD_CTX_create();
if (ctx) {
rv = APR_ENOTIMPL;
@@ -1007,7 +1001,6 @@ static apr_status_t sha256_digest(md_data_t **pdigest, apr_pool_t *p, const md_d
}
}
}
-leave:
if (ctx) {
EVP_MD_CTX_destroy(ctx);
}
@@ -1043,6 +1036,31 @@ apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p,
return rv;
}
+apr_status_t md_crypt_hmac64(const char **pmac64, const md_data_t *hmac_key,
+ apr_pool_t *p, const char *d, size_t dlen)
+{
+ const char *mac64 = NULL;
+ unsigned char *s;
+ unsigned int digest_len = 0;
+ md_data_t *digest;
+ apr_status_t rv = APR_SUCCESS;
+
+ digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
+ s = HMAC(EVP_sha256(), (const unsigned char*)hmac_key->data, (int)hmac_key->len,
+ (const unsigned char*)d, (size_t)dlen,
+ (unsigned char*)digest->data, &digest_len);
+ if (!s) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ digest->len = digest_len;
+ mac64 = md_util_base64url_encode(digest, p);
+
+cleanup:
+ *pmac64 = (APR_SUCCESS == rv)? mac64 : NULL;
+ return rv;
+}
+
/**************************************************************************************************/
/* certificates */
@@ -1331,17 +1349,13 @@ apr_status_t md_cert_to_sha256_digest(md_data_t **pdigest, const md_cert_t *cert
{
md_data_t *digest;
unsigned int dlen;
- apr_status_t rv = APR_ENOMEM;
digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
- if (!digest) goto leave;
-
X509_digest(cert->x509, EVP_sha256(), (unsigned char*)digest->data, &dlen);
digest->len = dlen;
- rv = APR_SUCCESS;
-leave:
- *pdigest = (APR_SUCCESS == rv)? digest : NULL;
- return rv;
+
+ *pdigest = digest;
+ return APR_SUCCESS;
}
apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p)
@@ -1463,17 +1477,30 @@ apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain,
ct = apr_table_get(res->headers, "Content-Type");
if (!res->body || !ct) goto cleanup;
ct = md_util_parse_ct(res->req->pool, ct);
- if (!strcmp("application/pem-certificate-chain", ct)
+ if (!strcmp("application/pkix-cert", ct)) {
+ rv = md_cert_read_http(&cert, p, res);
+ if (APR_SUCCESS != rv) goto cleanup;
+ APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+ }
+ else if (!strcmp("application/pem-certificate-chain", ct)
|| !strncmp("text/plain", ct, sizeof("text/plain")-1)) {
/* Some servers seem to think 'text/plain' is sufficient, see #232 */
rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool);
if (APR_SUCCESS != rv) goto cleanup;
rv = md_cert_read_chain(chain, res->req->pool, data, data_len);
}
- else if (!strcmp("application/pkix-cert", ct)) {
- rv = md_cert_read_http(&cert, p, res);
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "attempting to parse certificates from unrecognized content-type: %s", ct);
+ rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool);
if (APR_SUCCESS != rv) goto cleanup;
- APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+ rv = md_cert_read_chain(chain, res->req->pool, data, data_len);
+ if (APR_SUCCESS == rv && chain->nelts == 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "certificiate chain response did not contain any certificates "
+ "(suspicious content-type: %s)", ct);
+ rv = APR_ENOENT;
+ }
}
cleanup:
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h
index 85e45e4e44..203dc40f40 100644
--- a/modules/md/md_crypt.h
+++ b/modules/md/md_crypt.h
@@ -114,6 +114,9 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *
void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey);
+apr_status_t md_crypt_hmac64(const char **pmac64, const struct md_data_t *hmac_key,
+ apr_pool_t *p, const char *d, size_t dlen);
+
/**************************************************************************************************/
/* X509 certificates */
diff --git a/modules/md/md_jws.c b/modules/md/md_jws.c
index 4bdd99a125..c0e8c1b4fc 100644
--- a/modules/md/md_jws.c
+++ b/modules/md/md_jws.c
@@ -25,67 +25,67 @@
#include "md_log.h"
#include "md_util.h"
-static int header_set(void *data, const char *key, const char *val)
+apr_status_t md_jws_get_jwk(md_json_t **pjwk, apr_pool_t *p, struct md_pkey_t *pkey)
{
- md_json_sets(val, (md_json_t *)data, key, NULL);
- return 1;
+ md_json_t *jwk;
+
+ if (!pkey) return APR_EINVAL;
+
+ jwk = md_json_create(p);
+ md_json_sets(md_pkey_get_rsa_e64(pkey, p), jwk, "e", NULL);
+ md_json_sets("RSA", jwk, "kty", NULL);
+ md_json_sets(md_pkey_get_rsa_n64(pkey, p), jwk, "n", NULL);
+ *pjwk = jwk;
+ return APR_SUCCESS;
}
apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
- md_data_t *payload, struct apr_table_t *protected,
+ md_data_t *payload, md_json_t *prot_fields,
struct md_pkey_t *pkey, const char *key_id)
{
- md_json_t *msg, *jprotected;
+ md_json_t *msg, *jprotected, *jwk;
const char *prot64, *pay64, *sign64, *sign, *prot;
- apr_status_t rv = APR_SUCCESS;
+ md_data_t data;
+ apr_status_t rv;
- *pmsg = NULL;
-
msg = md_json_create(p);
-
- jprotected = md_json_create(p);
+ jprotected = md_json_clone(p, prot_fields);
md_json_sets("RS256", jprotected, "alg", NULL);
if (key_id) {
md_json_sets(key_id, jprotected, "kid", NULL);
}
else {
- md_json_sets(md_pkey_get_rsa_e64(pkey, p), jprotected, "jwk", "e", NULL);
- md_json_sets("RSA", jprotected, "jwk", "kty", NULL);
- md_json_sets(md_pkey_get_rsa_n64(pkey, p), jprotected, "jwk", "n", NULL);
+ rv = md_jws_get_jwk(&jwk, p, pkey);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "get jwk");
+ goto cleanup;
+ }
+ md_json_setj(jwk, jprotected, "jwk", NULL);
}
- apr_table_do(header_set, jprotected, protected, NULL);
- prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, p, "protected: %s",
- prot ? prot : "<failed to serialize!>");
+ prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
if (!prot) {
rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "serialize protected");
+ goto cleanup;
}
-
- if (rv == APR_SUCCESS) {
- md_data_t data;
- md_data_init(&data, prot, strlen(prot));
- prot64 = md_util_base64url_encode(&data, p);
- md_json_sets(prot64, msg, "protected", NULL);
- pay64 = md_util_base64url_encode(payload, p);
+ md_data_init(&data, prot, strlen(prot));
+ prot64 = md_util_base64url_encode(&data, p);
+ md_json_sets(prot64, msg, "protected", NULL);
- md_json_sets(pay64, msg, "payload", NULL);
- sign = apr_psprintf(p, "%s.%s", prot64, pay64);
+ pay64 = md_util_base64url_encode(payload, p);
+ md_json_sets(pay64, msg, "payload", NULL);
+ sign = apr_psprintf(p, "%s.%s", prot64, pay64);
- rv = md_crypt_sign64(&sign64, pkey, p, sign, strlen(sign));
+ rv = md_crypt_sign64(&sign64, pkey, p, sign, strlen(sign));
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "jwk signed message");
+ goto cleanup;
}
+ md_json_sets(sign64, msg, "signature", NULL);
- if (rv == APR_SUCCESS) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p,
- "jws pay64=%s\nprot64=%s\nsign64=%s", pay64, prot64, sign64);
-
- md_json_sets(sign64, msg, "signature", NULL);
- }
- else {
- md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "jwk signed message");
- }
-
+cleanup:
*pmsg = (APR_SUCCESS == rv)? msg : NULL;
return rv;
}
@@ -108,3 +108,41 @@ apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pke
rv = md_crypt_sha256_digest64(pthumb, p, &data);
return rv;
}
+
+apr_status_t md_jws_hmac(md_json_t **pmsg, apr_pool_t *p,
+ md_data_t *payload, md_json_t *prot_fields,
+ const md_data_t *hmac_key)
+{
+ md_json_t *msg, *jprotected;
+ const char *prot64, *pay64, *mac64, *sign, *prot;
+ md_data_t data;
+ apr_status_t rv;
+
+ msg = md_json_create(p);
+ jprotected = md_json_clone(p, prot_fields);
+ md_json_sets("HS256", jprotected, "alg", NULL);
+ prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
+ if (!prot) {
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "serialize protected");
+ goto cleanup;
+ }
+
+ md_data_init(&data, prot, strlen(prot));
+ prot64 = md_util_base64url_encode(&data, p);
+ md_json_sets(prot64, msg, "protected", NULL);
+
+ pay64 = md_util_base64url_encode(payload, p);
+ md_json_sets(pay64, msg, "payload", NULL);
+ sign = apr_psprintf(p, "%s.%s", prot64, pay64);
+
+ rv = md_crypt_hmac64(&mac64, hmac_key, p, sign, strlen(sign));
+ if (APR_SUCCESS != rv) {
+ goto cleanup;
+ }
+ md_json_sets(mac64, msg, "signature", NULL);
+
+cleanup:
+ *pmsg = (APR_SUCCESS == rv)? msg : NULL;
+ return rv;
+}
diff --git a/modules/md/md_jws.h b/modules/md/md_jws.h
index 7121308aae..466f2dfeab 100644
--- a/modules/md/md_jws.h
+++ b/modules/md/md_jws.h
@@ -22,10 +22,31 @@ struct md_json_t;
struct md_pkey_t;
struct md_data_t;
+/**
+ * Get the JSON value of the 'jwk' field for the given key.
+ */
+apr_status_t md_jws_get_jwk(md_json_t **pjwk, apr_pool_t *p, struct md_pkey_t *pkey);
+
+/**
+ * Get the JWS key signed JSON message with given payload and protected fields, signed
+ * using the given key and optional key_id.
+ */
apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
- struct md_data_t *payload, struct apr_table_t *protected,
+ struct md_data_t *payload, md_json_t *prot_fields,
struct md_pkey_t *pkey, const char *key_id);
+/**
+ * Get the 'Thumbprint' as defined in RFC8555 for the given key in
+ * base64 encoding.
+ */
+apr_status_t md_jws_pkey_thumb(const char **pthumb64, apr_pool_t *p, struct md_pkey_t *pkey);
+
+/**
+ * Get the JWS HS256 signed message for given payload and protected fields,
+ * using the base64 encoded MAC key.
+ */
+apr_status_t md_jws_hmac(md_json_t **pmsg, apr_pool_t *p,
+ struct md_data_t *payload, md_json_t *prot_fields,
+ const struct md_data_t *hmac_key);
-apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pkey_t *pkey);
#endif /* md_jws_h */
diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c
index b500ddc601..67746033ea 100644
--- a/modules/md/md_reg.c
+++ b/modules/md/md_reg.c
@@ -92,7 +92,8 @@ apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *st
reg->can_http = 1;
reg->can_https = 1;
reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
- reg->ca_file = ca_file? apr_pstrdup(p, ca_file) : NULL;
+ reg->ca_file = (ca_file && apr_strnatcasecmp("none", ca_file))?
+ apr_pstrdup(p, ca_file) : NULL;
md_timeslice_create(&reg->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF);
md_timeslice_create(&reg->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF);
@@ -197,9 +198,11 @@ static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, i
static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
{
- md_state_t state;
+ md_state_t state = MD_S_COMPLETE;
+ const char *state_descr = NULL;
const md_pubcert_t *pub;
const md_cert_t *cert;
+ const md_pkey_spec_t *spec;
apr_status_t rv = APR_SUCCESS;
int i;
@@ -207,45 +210,50 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
if (md->warn_window == NULL) md->warn_window = reg->warn_window;
for (i = 0; i < md_cert_count(md); ++i) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: check cert %d", md->name, i);
- if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, i, p))) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
+ "md{%s}: check cert %s", md->name, md_pkey_spec_name(spec));
+ rv = md_reg_get_pubcert(&pub, reg, md, i, p);
+ if (APR_SUCCESS == rv) {
cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
if (!md_is_covered_by_alt_names(md, pub->alt_names)) {
state = MD_S_INCOMPLETE;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: incomplete, certificate(%d) does not cover all domains.",
- md->name, i);
- goto out;
+ state_descr = apr_psprintf(p, "certificate(%s) does not cover all domains.",
+ md_pkey_spec_name(spec));
+ goto cleanup;
}
if (!md->must_staple != !md_cert_must_staple(cert)) {
state = MD_S_INCOMPLETE;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: incomplete, OCSP Stapling is%s requested, but "
- "certificate(%d) has it%s enabled.",
- md->name, md->must_staple? "" : " not", i,
+ state_descr = apr_psprintf(p, "'must-staple' is%s requested, but "
+ "certificate(%s) has it%s enabled.",
+ md->must_staple? "" : " not",
+ md_pkey_spec_name(spec),
!md->must_staple? "" : " not");
- goto out;
+ goto cleanup;
}
- state = MD_S_COMPLETE;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: certificate(%d) is ok",
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: certificate(%d) is ok",
md->name, i);
}
else if (APR_STATUS_IS_ENOENT(rv)) {
state = MD_S_INCOMPLETE;
+ state_descr = apr_psprintf(p, "certificate(%s) is missing",
+ md_pkey_spec_name(spec));
rv = APR_SUCCESS;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: incomplete, certificate(%d) is missing", md->name, i);
- goto out;
+ goto cleanup;
+ }
+ else {
+ state = MD_S_ERROR;
+ state_descr = "error intializing";
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name);
+ goto cleanup;
}
}
-out:
- if (APR_SUCCESS != rv) {
- state = MD_S_ERROR;
- md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name);
- }
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: state==%d", md->name, state);
+cleanup:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: state=%d, %s",
+ md->name, state, state_descr);
md->state = state;
+ md->state_descr = state_descr;
return rv;
}
diff --git a/modules/md/md_status.c b/modules/md/md_status.c
index ecc35c66d0..32efc19a67 100644
--- a/modules/md/md_status.c
+++ b/modules/md/md_status.c
@@ -88,55 +88,88 @@ leave:
return rv;
}
-/**************************************************************************************************/
-/* md status information */
-
-static apr_status_t job_loadj(md_json_t **pjson, md_store_group_t group, const char *name,
+static apr_status_t job_loadj(md_json_t **pjson, md_store_group_t group, const char *name,
struct md_reg_t *reg, int with_log, apr_pool_t *p)
{
apr_status_t rv;
-
+
md_store_t *store = md_reg_store_get(reg);
rv = md_store_load_json(store, group, name, MD_FN_JOB, pjson, p);
if (APR_SUCCESS == rv && !with_log) md_json_del(*pjson, MD_KEY_LOG, NULL);
return rv;
}
+static apr_status_t status_get_cert_json_ex(
+ md_json_t **pjson,
+ const md_cert_t *cert,
+ const md_t *md,
+ md_reg_t *reg,
+ md_ocsp_reg_t *ocsp,
+ int with_logs,
+ apr_pool_t *p)
+{
+ md_json_t *certj, *jobj;
+ md_timeperiod_t ocsp_valid;
+ md_ocsp_cert_stat_t cert_stat;
+ apr_status_t rv;
+
+ if (APR_SUCCESS != (rv = status_get_cert_json(&certj, cert, p))) goto leave;
+ if (md->stapling && ocsp) {
+ rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md);
+ if (APR_SUCCESS == rv) {
+ md_json_sets(md_ocsp_cert_stat_name(cert_stat), certj, MD_KEY_OCSP, MD_KEY_STATUS, NULL);
+ md_json_set_timeperiod(&ocsp_valid, certj, MD_KEY_OCSP, MD_KEY_VALID, NULL);
+ }
+ else if (!APR_STATUS_IS_ENOENT(rv)) goto leave;
+ rv = APR_SUCCESS;
+ if (APR_SUCCESS == job_loadj(&jobj, MD_SG_OCSP, md->name, reg, with_logs, p)) {
+ md_json_setj(jobj, certj, MD_KEY_OCSP, MD_KEY_RENEWAL, NULL);
+ }
+ }
+leave:
+ *pjson = (APR_SUCCESS == rv)? certj : NULL;
+ return rv;
+}
+
+static int get_cert_count(const md_t *md, int from_staging)
+{
+ if (!from_staging && md->cert_files && md->cert_files->nelts) {
+ return md->cert_files->nelts;
+ }
+ return md_pkeys_spec_count(md->pks);
+}
+
+static const char *get_cert_name(const md_t *md, int i, int from_staging, apr_pool_t *p)
+{
+ if (!from_staging && md->cert_files && md->cert_files->nelts) {
+ /* static files configured, not from staging, used index names */
+ return apr_psprintf(p, "%d", i);
+ }
+ return md_pkey_spec_name(md_pkeys_spec_get(md->pks, i));
+}
+
static apr_status_t status_get_certs_json(md_json_t **pjson, apr_array_header_t *certs,
+ int from_staging,
const md_t *md, md_reg_t *reg,
- md_ocsp_reg_t *ocsp, int with_logs,
+ md_ocsp_reg_t *ocsp, int with_logs,
apr_pool_t *p)
{
- md_json_t *json, *certj, *jobj;
- md_timeperiod_t certs_valid = {0, 0}, valid, ocsp_valid;
- md_pkey_spec_t *spec;
+ md_json_t *json, *certj;
+ md_timeperiod_t certs_valid = {0, 0}, valid;
md_cert_t *cert;
- md_ocsp_cert_stat_t cert_stat;
int i;
apr_status_t rv = APR_SUCCESS;
json = md_json_create(p);
- for (i = 0; i < md_cert_count(md); ++i) {
- spec = md_pkeys_spec_get(md->pks, i);
+ for (i = 0; i < get_cert_count(md, from_staging); ++i) {
cert = APR_ARRAY_IDX(certs, i, md_cert_t*);
if (!cert) continue;
-
- if (APR_SUCCESS != (rv = status_get_cert_json(&certj, cert, p))) goto leave;
- if (md->stapling && ocsp) {
- rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md);
- if (APR_SUCCESS == rv) {
- md_json_sets(md_ocsp_cert_stat_name(cert_stat), certj, MD_KEY_OCSP, MD_KEY_STATUS, NULL);
- md_json_set_timeperiod(&ocsp_valid, certj, MD_KEY_OCSP, MD_KEY_VALID, NULL);
- }
- else if (!APR_STATUS_IS_ENOENT(rv)) goto leave;
- rv = APR_SUCCESS;
- if (APR_SUCCESS == job_loadj(&jobj, MD_SG_OCSP, md->name, reg, with_logs, p)) {
- md_json_setj(jobj, certj, MD_KEY_OCSP, MD_KEY_RENEWAL, NULL);
- }
- }
+
+ rv = status_get_cert_json_ex(&certj, cert, md, reg, ocsp, with_logs, p);
+ if (APR_SUCCESS != rv) goto leave;
valid = md_cert_get_valid(cert);
certs_valid = i? md_timeperiod_common(&certs_valid, &valid) : valid;
- md_json_setj(certj, json, md_pkey_spec_name(spec), NULL);
+ md_json_setj(certj, json, get_cert_name(md, i, from_staging, p), NULL);
}
if (certs_valid.start) {
@@ -157,7 +190,7 @@ static apr_status_t get_staging_certs_json(md_json_t **pjson, const md_t *md,
apr_status_t rv;
certs = apr_array_make(p, 5, sizeof(md_cert_t*));
- for (i = 0; i < md_cert_count(md); ++i) {
+ for (i = 0; i < get_cert_count(md, 1); ++i) {
spec = md_pkeys_spec_get(md->pks, i);
cert = NULL;
rv = md_pubcert_load(md_reg_store_get(reg), MD_SG_STAGING, md->name, spec, &chain, p);
@@ -166,7 +199,7 @@ static apr_status_t get_staging_certs_json(md_json_t **pjson, const md_t *md,
}
APR_ARRAY_PUSH(certs, const md_cert_t*) = cert;
}
- return status_get_certs_json(pjson, certs, md, reg, NULL, 0, p);
+ return status_get_certs_json(pjson, certs, 1, md, reg, NULL, 0, p);
}
static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
@@ -182,9 +215,9 @@ static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
apr_time_t renew_at;
int i;
- mdj = md_to_json(md, p);
+ mdj = md_to_public_json(md, p);
certs = apr_array_make(p, 5, sizeof(md_cert_t*));
- for (i = 0; i < md_cert_count(md); ++i) {
+ for (i = 0; i < get_cert_count(md, 0); ++i) {
cert = NULL;
if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, i, p)) {
cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
@@ -192,7 +225,7 @@ static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
APR_ARRAY_PUSH(certs, const md_cert_t*) = cert;
}
- rv = status_get_certs_json(&certsj, certs, md, reg, ocsp, with_logs, p);
+ rv = status_get_certs_json(&certsj, certs, 0, md, reg, ocsp, with_logs, p);
if (APR_SUCCESS != rv) goto leave;
md_json_setj(certsj, mdj, MD_KEY_CERT, NULL);
@@ -605,8 +638,9 @@ apr_status_t md_job_notify(md_job_t *job, const char *reason, md_result_t *resul
job->dirty = 1;
if (APR_SUCCESS == rv && APR_SUCCESS == result->status) {
job->notified = 1;
- if (!strcmp("renewed", reason)) job->notified_renewed = 1;
- job->error_runs = 0;
+ if (!strcmp("renewed", reason)) {
+ job->notified_renewed = 1;
+ }
}
else {
++job->error_runs;
diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c
index 959bafee80..f70542cbb7 100644
--- a/modules/md/md_store_fs.c
+++ b/modules/md/md_store_fs.c
@@ -631,7 +631,7 @@ static apr_status_t pfs_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
&& MD_OK(mk_group_dir(&dir, s_fs, group, name, p))
&& MD_OK(md_util_path_merge(&fpath, ptemp, dir, aspect, NULL))) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "storing in %s", fpath);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, ptemp, "storing in %s", fpath);
switch (vtype) {
case MD_SV_TEXT:
rv = (create? md_text_fcreatex(fpath, perms->file, p, value)
@@ -745,7 +745,9 @@ static apr_status_t pfs_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_
/* Remove all files in dir, there should be no sub-dirs */
rv = md_util_rm_recursive(dir, ptemp, 1);
}
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, ptemp, "purge %s/%s (%s)", groupname, name, dir);
+ if (!APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, ptemp, "purge %s/%s (%s)", groupname, name, dir);
+ }
return APR_SUCCESS;
}
@@ -1018,12 +1020,12 @@ static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
}
if (!MD_OK(apr_file_rename(to_dir, narch_dir, ptemp))) {
- md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
to_dir, narch_dir);
goto out;
}
if (!MD_OK(apr_file_rename(from_dir, to_dir, ptemp))) {
- md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
from_dir, to_dir);
apr_file_rename(narch_dir, to_dir, ptemp);
goto out;
@@ -1034,7 +1036,7 @@ static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
}
else if (APR_STATUS_IS_ENOENT(rv)) {
if (APR_SUCCESS != (rv = apr_file_rename(from_dir, to_dir, ptemp))) {
- md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
from_dir, to_dir);
goto out;
}
@@ -1075,8 +1077,9 @@ static apr_status_t pfs_rename(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va
goto out;
}
- if (APR_SUCCESS != (rv = apr_file_rename(from_dir, to_dir, ptemp))) {
- md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
+ if (APR_SUCCESS != (rv = apr_file_rename(from_dir, to_dir, ptemp))
+ && !APR_STATUS_IS_ENOENT(rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
from_dir, to_dir);
goto out;
}
diff --git a/modules/md/md_version.h b/modules/md/md_version.h
index 65aa56a462..72c1498322 100644
--- a/modules/md/md_version.h
+++ b/modules/md/md_version.h
@@ -27,7 +27,7 @@
* @macro
* Version number of the md module as c string
*/
-#define MOD_MD_VERSION "2.4.7"
+#define MOD_MD_VERSION "2.4.8"
/**
* @macro
@@ -35,7 +35,7 @@
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_MD_VERSION_NUM 0x020407
+#define MOD_MD_VERSION_NUM 0x020408
#define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory"
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c
index 8b379eb175..6387e4dd20 100644
--- a/modules/md/mod_md.c
+++ b/modules/md/mod_md.c
@@ -54,7 +54,6 @@
#include "mod_md_ocsp.h"
#include "mod_md_os.h"
#include "mod_md_status.h"
-#include "mod_ssl_openssl.h"
static void md_hooks(apr_pool_t *pool);
@@ -324,11 +323,15 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
md->ca_agreement = md_config_gets(md->sc, MD_CONFIG_CA_AGREEMENT);
}
contact = md_config_gets(md->sc, MD_CONFIG_CA_CONTACT);
- if (contact && contact[0]) {
+ if (md->contacts && md->contacts->nelts > 0) {
+ /* set explicitly */
+ }
+ else if (contact && contact[0]) {
apr_array_clear(md->contacts);
APR_ARRAY_PUSH(md->contacts, const char *) =
md_util_schemify(p, contact, "mailto");
- } else if( md->sc->s->server_admin && strcmp(DEFAULT_ADMIN, md->sc->s->server_admin)) {
+ }
+ else if( md->sc->s->server_admin && strcmp(DEFAULT_ADMIN, md->sc->s->server_admin)) {
apr_array_clear(md->contacts);
APR_ARRAY_PUSH(md->contacts, const char *) =
md_util_schemify(p, md->sc->s->server_admin, "mailto");
@@ -350,6 +353,10 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
if (md->require_https < 0) {
md->require_https = md_config_geti(md->sc, MD_CONFIG_REQUIRE_HTTPS);
}
+ if (!md->ca_eab_kid) {
+ md->ca_eab_kid = md->sc->ca_eab_kid;
+ md->ca_eab_hmac = md->sc->ca_eab_hmac;
+ }
if (md->must_staple < 0) {
md->must_staple = md_config_geti(md->sc, MD_CONFIG_MUST_STAPLE);
}
@@ -592,14 +599,18 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *
s->server_hostname, s->port, md->name, sc->name,
domain, (int)sc->assigned->nelts);
- if (sc->ca_contact && sc->ca_contact[0]) {
+ if (md->contacts && md->contacts->nelts > 0) {
+ /* set explicitly */
+ }
+ else if (sc->ca_contact && sc->ca_contact[0]) {
uri = md_util_schemify(p, sc->ca_contact, "mailto");
if (md_array_str_index(md->contacts, uri, 0, 0) < 0) {
APR_ARRAY_PUSH(md->contacts, const char *) = uri;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10044)
"%s: added contact %s", md->name, uri);
}
- } else if (s->server_admin && strcmp(DEFAULT_ADMIN, s->server_admin)) {
+ }
+ else if (s->server_admin && strcmp(DEFAULT_ADMIN, s->server_admin)) {
uri = md_util_schemify(p, s->server_admin, "mailto");
if (md_array_str_index(md->contacts, uri, 0, 0) < 0) {
APR_ARRAY_PUSH(md->contacts, const char *) = uri;
@@ -675,18 +686,18 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
if (md->cert_files && md->cert_files->nelts) {
if (!md->pkey_files || (md->cert_files->nelts != md->pkey_files->nelts)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10170)
- "The Managed Domain '%s', defined in %s(line %d), "
+ "The Managed Domain '%s' "
"needs one MDCertificateKeyFile for each MDCertificateFile.",
- md->name, md->defn_name, md->defn_line_number);
+ md->name);
return APR_EINVAL;
}
}
else if (md->pkey_files && md->pkey_files->nelts
&& (!md->cert_files || !md->cert_files->nelts)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10171)
- "The Managed Domain '%s', defined in %s(line %d), "
+ "The Managed Domain '%s' "
"has MDCertificateKeyFile(s) but no MDCertificateFile.",
- md->name, md->defn_name, md->defn_line_number);
+ md->name);
return APR_EINVAL;
}
diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c
index bfc64adbbd..cda11a0026 100644
--- a/modules/md/mod_md_config.c
+++ b/modules/md/mod_md_config.c
@@ -110,6 +110,8 @@ static md_srv_conf_t defconf = {
"ACME", /* ca protocol */
NULL, /* ca agreemnent */
NULL, /* ca challenges array */
+ NULL, /* ca eab kid */
+ NULL, /* ca eab hmac */
0, /* stapling */
1, /* staple others */
NULL, /* currently defined md */
@@ -162,6 +164,8 @@ static void srv_conf_props_clear(md_srv_conf_t *sc)
sc->ca_proto = NULL;
sc->ca_agreement = NULL;
sc->ca_challenges = NULL;
+ sc->ca_eab_kid = NULL;
+ sc->ca_eab_hmac = NULL;
sc->stapling = DEF_VAL;
sc->staple_others = DEF_VAL;
}
@@ -180,6 +184,8 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
to->ca_proto = from->ca_proto;
to->ca_agreement = from->ca_agreement;
to->ca_challenges = from->ca_challenges;
+ to->ca_eab_kid = from->ca_eab_kid;
+ to->ca_eab_hmac = from->ca_eab_hmac;
to->stapling = from->stapling;
to->staple_others = from->staple_others;
}
@@ -196,7 +202,14 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t
if (from->ca_url) md->ca_url = from->ca_url;
if (from->ca_proto) md->ca_proto = from->ca_proto;
if (from->ca_agreement) md->ca_agreement = from->ca_agreement;
+ if (from->ca_contact) {
+ apr_array_clear(md->contacts);
+ APR_ARRAY_PUSH(md->contacts, const char *) =
+ md_util_schemify(p, from->ca_contact, "mailto");
+ }
if (from->ca_challenges) md->ca_challenges = apr_array_copy(p, from->ca_challenges);
+ if (from->ca_eab_kid) md->ca_eab_kid = from->ca_eab_kid;
+ if (from->ca_eab_hmac) md->ca_eab_hmac = from->ca_eab_hmac;
if (from->stapling != DEF_VAL) md->stapling = from->stapling;
}
@@ -238,6 +251,8 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
nsc->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement;
nsc->ca_challenges = (add->ca_challenges? apr_array_copy(pool, add->ca_challenges)
: (base->ca_challenges? apr_array_copy(pool, base->ca_challenges) : NULL));
+ nsc->ca_eab_kid = add->ca_eab_kid? add->ca_eab_kid : base->ca_eab_kid;
+ nsc->ca_eab_hmac = add->ca_eab_hmac? add->ca_eab_hmac : base->ca_eab_hmac;
nsc->stapling = (add->stapling != DEF_VAL)? add->stapling : base->stapling;
nsc->staple_others = (add->staple_others != DEF_VAL)? add->staple_others : base->staple_others;
nsc->current = NULL;
@@ -461,13 +476,16 @@ static const char *md_config_set_names(cmd_parms *cmd, void *dc,
static const char *md_config_set_ca(cmd_parms *cmd, void *dc, const char *value)
{
md_srv_conf_t *sc = md_config_get(cmd->server);
- const char *err;
+ const char *err, *url;
(void)dc;
if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
return err;
}
- sc->ca_url = value;
+ if (APR_SUCCESS != md_get_ca_url_from_name(&url, cmd->pool, value)) {
+ return url;
+ }
+ sc->ca_url = url;
return NULL;
}
@@ -1009,9 +1027,30 @@ static const char *md_config_set_ca_certs(cmd_parms *cmd, void *dc, const char *
return NULL;
}
+static const char *md_config_set_eab(cmd_parms *cmd, void *dc,
+ const char *keyid, const char *hmac)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
+ return err;
+ }
+ if (!hmac) {
+ if (apr_strnatcasecmp("None", keyid)) {
+ return "only 'None' or a KEYID and HMAC string are allowed.";
+ }
+ keyid = "none";
+ }
+ sc->ca_eab_kid = keyid;
+ sc->ca_eab_hmac = hmac;
+ return NULL;
+}
+
const command_rec md_cmds[] = {
AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF,
- "URL of CA issuing the certificates"),
+ "URL or known name of CA issuing the certificates"),
AP_INIT_TAKE1("MDCertificateAgreement", md_config_set_agreement, NULL, RSRC_CONF,
"either 'accepted' or the URL of CA Terms-of-Service agreement you accept"),
AP_INIT_TAKE_ARGV("MDCAChallenges", md_config_set_cha_tyes, NULL, RSRC_CONF,
@@ -1085,6 +1124,8 @@ const command_rec md_cmds[] = {
"How long to delay activation of new certificates"),
AP_INIT_TAKE1("MDCACertificateFile", md_config_set_ca_certs, NULL, RSRC_CONF,
"Set the CA file to use for connections"),
+ AP_INIT_TAKE12("MDExternalAccountBinding", md_config_set_eab, NULL, RSRC_CONF,
+ "Set the external account binding keyid and hmac values to use at CA"),
AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
};
diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h
index 76423bd602..35c3152981 100644
--- a/modules/md/mod_md_config.h
+++ b/modules/md/mod_md_config.h
@@ -91,7 +91,9 @@ typedef struct md_srv_conf_t {
const char *ca_proto; /* protocol used vs CA (e.g. ACME) */
const char *ca_agreement; /* accepted agreement uri between CA and user */
struct apr_array_header_t *ca_challenges; /* challenge types configured */
-
+ const char *ca_eab_kid; /* != NULL, external account binding keyid */
+ const char *ca_eab_hmac; /* != NULL, external account binding hmac */
+
int stapling; /* OCSP stapling enabled */
int staple_others; /* Provide OCSP stapling for non-MD certificates */
diff --git a/modules/md/mod_md_status.c b/modules/md/mod_md_status.c
index cb0e2677f0..390290b85c 100644
--- a/modules/md/mod_md_status.c
+++ b/modules/md/mod_md_status.c
@@ -172,7 +172,10 @@ static void si_val_status(status_ctx *ctx, md_json_t *mdj, const status_info *in
apr_time_t until;
(void)info;
switch (md_json_getl(mdj, info->key, NULL)) {
- case MD_S_INCOMPLETE: s = "incomplete"; break;
+ case MD_S_INCOMPLETE:
+ s = md_json_gets(mdj, MD_KEY_STATE_DESCR, NULL);
+ s = s? apr_psprintf(ctx->p, "incomplete: %s", s) : "incomplete";
+ break;
case MD_S_EXPIRED_DEPRECATED:
case MD_S_COMPLETE:
until = md_json_get_time(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL);
@@ -188,28 +191,11 @@ static void si_val_status(status_ctx *ctx, md_json_t *mdj, const status_info *in
static void si_val_url(status_ctx *ctx, md_json_t *mdj, const status_info *info)
{
const char *url, *s;
- apr_uri_t uri_parsed;
-
s = url = md_json_gets(mdj, info->key, NULL);
if (!url) return;
- if (!strcmp(LE_ACMEv2_PROD, url)) {
- s = "Let's Encrypt";
- }
- else if (!strcmp(LE_ACMEv2_STAGING, url)) {
- s = "Let's Encrypt (staging)";
- }
- else if (!strcmp(LE_ACMEv1_PROD, url)) {
- s = "Let's Encrypt (v1)";
- }
- else if (!strcmp(LE_ACMEv1_STAGING, url)) {
- s = "Let's Encrypt (v1,staging)";
- }
- else if (APR_SUCCESS == apr_uri_parse(ctx->p, url, &uri_parsed)) {
- s = uri_parsed.hostname;
-
- }
- apr_brigade_printf(ctx->bb, NULL, NULL, "<a href='%s'>%s</a>",
+ s = md_get_ca_name_from_url(ctx->p, url);
+ apr_brigade_printf(ctx->bb, NULL, NULL, "<a href='%s'>%s</a>",
ap_escape_html2(ctx->p, url, 1),
ap_escape_html2(ctx->p, s, 1));
}
@@ -326,13 +312,24 @@ static void si_val_ca_url(status_ctx *ctx, md_json_t *mdj, const status_info *in
jcert = md_json_getj(mdj, info->key, NULL);
if (jcert) si_val_url(ctx, jcert, &sub);
}
-
+
+static int count_certs(void *baton, const char *key, md_json_t *json)
+{
+ int *pcount = baton;
+
+ (void)json;
+ if (strcmp(key, MD_KEY_VALID)) {
+ *pcount += 1;
+ }
+ return 1;
+}
+
static void print_job_summary(apr_bucket_brigade *bb, md_json_t *mdj, const char *key,
const char *separator)
{
char buffer[HUGE_STRING_LEN];
apr_status_t rv;
- int finished, errors;
+ int finished, errors, cert_count;
apr_time_t t;
const char *s, *line;
@@ -353,8 +350,16 @@ static void print_job_summary(apr_bucket_brigade *bb, md_json_t *mdj, const char
}
if (finished) {
- line = apr_psprintf(bb->p, "%s finished successfully.", line);
- }
+ cert_count = 0;
+ md_json_iterkey(count_certs, &cert_count, mdj, key, MD_KEY_CERT, NULL);
+ if (cert_count > 0) {
+ line =apr_psprintf(bb->p, "%s finished, %d new certificate%s staged.",
+ line, cert_count, cert_count > 1? "s" : "");
+ }
+ else {
+ line = apr_psprintf(bb->p, "%s finished successfully.", line);
+ }
+ }
else {
s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_DETAIL, NULL);
if (s) line = apr_psprintf(bb->p, "%s %s", line, s);