summaryrefslogtreecommitdiffstats
path: root/modules/md/md_acme_acct.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/md/md_acme_acct.c')
-rw-r--r--modules/md/md_acme_acct.c785
1 files changed, 432 insertions, 353 deletions
diff --git a/modules/md/md_acme_acct.c b/modules/md/md_acme_acct.c
index c4a2b5f..f3e043e 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"
@@ -38,15 +39,12 @@
#include "md_acme_acct.h"
static apr_status_t acct_make(md_acme_acct_t **pacct, apr_pool_t *p,
- const char *ca_url, const char *id, apr_array_header_t *contacts)
+ const char *ca_url, apr_array_header_t *contacts)
{
md_acme_acct_t *acct;
acct = apr_pcalloc(p, sizeof(*acct));
-
- acct->id = id? apr_pstrdup(p, id) : NULL;
acct->ca_url = ca_url;
-
if (!contacts || apr_is_empty_array(contacts)) {
acct->contacts = apr_array_make(p, 5, sizeof(const char *));
}
@@ -72,87 +70,118 @@ static const char *mk_acct_pattern(apr_pool_t *p, md_acme_t *acme)
/**************************************************************************************************/
/* json load/save */
-static md_json_t *acct_to_json(md_acme_acct_t *acct, apr_pool_t *p)
+static md_acme_acct_st acct_st_from_str(const char *s)
+{
+ if (s) {
+ if (!strcmp("valid", s)) {
+ return MD_ACME_ACCT_ST_VALID;
+ }
+ else if (!strcmp("deactivated", s)) {
+ return MD_ACME_ACCT_ST_DEACTIVATED;
+ }
+ else if (!strcmp("revoked", s)) {
+ return MD_ACME_ACCT_ST_REVOKED;
+ }
+ }
+ return MD_ACME_ACCT_ST_UNKNOWN;
+}
+
+md_json_t *md_acme_acct_to_json(md_acme_acct_t *acct, apr_pool_t *p)
{
md_json_t *jacct;
+ const char *s;
assert(acct);
jacct = md_json_create(p);
- md_json_sets(acct->id, jacct, MD_KEY_ID, NULL);
- md_json_setb(acct->disabled, jacct, MD_KEY_DISABLED, NULL);
- md_json_sets(acct->url, jacct, MD_KEY_URL, NULL);
- md_json_sets(acct->ca_url, jacct, MD_KEY_CA_URL, NULL);
- md_json_setj(acct->registration, jacct, MD_KEY_REGISTRATION, NULL);
- if (acct->agreement) {
- md_json_sets(acct->agreement, jacct, MD_KEY_AGREEMENT, NULL);
- }
-
+ switch (acct->status) {
+ case MD_ACME_ACCT_ST_VALID:
+ s = "valid";
+ break;
+ case MD_ACME_ACCT_ST_DEACTIVATED:
+ s = "deactivated";
+ break;
+ case MD_ACME_ACCT_ST_REVOKED:
+ s = "revoked";
+ break;
+ default:
+ s = NULL;
+ break;
+ }
+ if (s) md_json_sets(s, jacct, MD_KEY_STATUS, NULL);
+ if (acct->url) md_json_sets(acct->url, jacct, MD_KEY_URL, NULL);
+ if (acct->ca_url) md_json_sets(acct->ca_url, jacct, MD_KEY_CA_URL, NULL);
+ if (acct->contacts) md_json_setsa(acct->contacts, jacct, MD_KEY_CONTACT, NULL);
+ 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;
}
-static apr_status_t acct_from_json(md_acme_acct_t **pacct, md_json_t *json, apr_pool_t *p)
+apr_status_t md_acme_acct_from_json(md_acme_acct_t **pacct, md_json_t *json, apr_pool_t *p)
{
apr_status_t rv = APR_EINVAL;
md_acme_acct_t *acct;
- int disabled;
- const char *ca_url, *url, *id;
+ md_acme_acct_st status = MD_ACME_ACCT_ST_UNKNOWN;
+ const char *ca_url, *url;
apr_array_header_t *contacts;
- id = md_json_gets(json, MD_KEY_ID, NULL);
- disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
- 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", id);
- goto out;
+ if (md_json_has_key(json, MD_KEY_STATUS, NULL)) {
+ status = acct_st_from_str(md_json_gets(json, MD_KEY_STATUS, NULL));
}
-
+
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: %s", id);
- goto out;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no url");
+ 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 leave;
+ }
+
contacts = apr_array_make(p, 5, sizeof(const char *));
- md_json_getsa(contacts, json, MD_KEY_REGISTRATION, MD_KEY_CONTACT, NULL);
- rv = acct_make(&acct, p, ca_url, id, contacts);
- if (APR_SUCCESS == rv) {
- acct->disabled = disabled;
- acct->url = url;
+ if (md_json_has_key(json, MD_KEY_CONTACT, NULL)) {
+ md_json_getsa(contacts, json, MD_KEY_CONTACT, NULL);
+ }
+ else {
+ md_json_getsa(contacts, json, MD_KEY_REGISTRATION, MD_KEY_CONTACT, NULL);
+ }
+ rv = acct_make(&acct, p, ca_url, contacts);
+ 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);
+ 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;
}
-apr_status_t md_acme_acct_save_staged(md_acme_t *acme, md_store_t *store, md_t *md, apr_pool_t *p)
-{
- md_acme_acct_t *acct = acme->acct;
- md_json_t *jacct;
- apr_status_t rv;
-
- jacct = acct_to_json(acct, p);
-
- rv = md_store_save(store, p, MD_SG_STAGING, md->name, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
- if (APR_SUCCESS == rv) {
- rv = md_store_save(store, p, MD_SG_STAGING, md->name, MD_FN_ACCT_KEY,
- MD_SV_PKEY, acme->acct_key, 0);
- }
- return rv;
-}
-
apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme,
- md_acme_acct_t *acct, md_pkey_t *acct_key)
+ const char **pid, md_acme_acct_t *acct, md_pkey_t *acct_key)
{
md_json_t *jacct;
apr_status_t rv;
int i;
- const char *id;
-
- jacct = acct_to_json(acct, p);
- id = acct->id;
+ const char *id = pid? *pid : NULL;
+ jacct = md_acme_acct_to_json(acct, p);
if (id) {
rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
}
@@ -160,23 +189,16 @@ apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme
rv = APR_EAGAIN;
for (i = 0; i < 1000 && APR_SUCCESS != rv; ++i) {
id = mk_acct_id(p, acme, i);
- md_json_sets(id, jacct, MD_KEY_ID, NULL);
rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 1);
}
-
}
if (APR_SUCCESS == rv) {
- acct->id = id;
+ if (pid) *pid = id;
rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCT_KEY, MD_SV_PKEY, acct_key, 0);
}
return rv;
}
-apr_status_t md_acme_save(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
-{
- return md_acme_acct_save(store, p, acme, acme->acct, acme->acct_key);
-}
-
apr_status_t md_acme_acct_load(md_acme_acct_t **pacct, md_pkey_t **ppkey,
md_store_t *store, md_store_group_t group,
const char *name, apr_pool_t *p)
@@ -193,11 +215,11 @@ apr_status_t md_acme_acct_load(md_acme_acct_t **pacct, md_pkey_t **ppkey,
goto out;
}
- rv = acct_from_json(pacct, json, p);
+ rv = md_acme_acct_from_json(pacct, json, p);
if (APR_SUCCESS == rv) {
rv = md_store_load(store, group, name, MD_FN_ACCT_KEY, MD_SV_PKEY, (void**)ppkey, p);
if (APR_SUCCESS != rv) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "loading key: %s", name);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "loading key: %s", name);
goto out;
}
}
@@ -212,9 +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_effective)) 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;
+ const md_t *md;
const char *id;
} find_ctx;
@@ -222,232 +271,227 @@ 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, *id;
-
+ 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;
-
- id = md_json_gets(json, MD_KEY_ID, NULL);
- disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
- ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
-
- if (!disabled && 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, disabled=%d, ca-url=%s",
- name, ctx->acme->url, id, disabled, ca_url);
- ctx->id = id;
+ "found account %s for %s: %s, status=%d",
+ acct->id, ctx->md->ca_effective, aspect, acct->status);
+ ctx->id = apr_pstrdup(ctx->p, name);
return 0;
}
}
+cleanup:
return 1;
}
-static apr_status_t acct_find(md_acme_acct_t **pacct, md_pkey_t **ppkey,
- md_store_t *store, md_acme_t *acme, apr_pool_t *p)
+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,
+ 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;
-
- rv = md_store_iter(find_acct, &ctx, store, p, MD_SG_ACCOUNTS, mk_acct_pattern(p, acme),
- MD_FN_ACCOUNT, MD_SV_JSON);
+ ctx.md = md;
+
+ rv = md_store_iter(find_acct, &ctx, store, p, group, name_pattern, MD_FN_ACCOUNT, MD_SV_JSON);
if (ctx.id) {
- rv = md_acme_acct_load(pacct, ppkey, store, MD_SG_ACCOUNTS, ctx.id, p);
+ *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, "acct_find: got account %s", ctx.id);
}
else {
*pacct = NULL;
rv = APR_ENOENT;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, "acct_find: none found");
}
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "acct_find %s", (*pacct)? (*pacct)->id : "NULL");
return rv;
}
-/**************************************************************************************************/
-/* Register a new account */
-
-typedef struct {
- md_acme_t *acme;
- apr_pool_t *p;
-} acct_ctx_t;
-
-static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
+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, const md_t *md,
+ apr_pool_t *p)
{
- acct_ctx_t *ctx = baton;
- md_json_t *jpayload;
+ md_acme_acct_t *acct;
+ md_pkey_t *pkey;
+ const char *id;
+ apr_status_t rv;
- jpayload = md_json_create(req->p);
- md_json_sets("new-reg", jpayload, MD_KEY_RESOURCE, NULL);
- md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
- if (ctx->acme->acct->agreement) {
- md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
- }
-
- return md_acme_req_body_init(req, jpayload);
-}
+ 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, (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);
-static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p,
- const apr_table_t *hdrs, md_json_t *body, void *baton)
-{
- acct_ctx_t *ctx = baton;
- apr_status_t rv = APR_SUCCESS;
- md_acme_acct_t *acct = acme->acct;
-
- if (!acct->url) {
- const char *location = apr_table_get(hdrs, "location");
- if (!location) {
- md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, APR_EINVAL, p, "new acct without location");
- return APR_EINVAL;
- }
- acct->url = apr_pstrdup(ctx->p, location);
- }
- if (!acct->tos_required) {
- acct->tos_required = md_link_find_relation(hdrs, ctx->p, "terms-of-service");
- if (acct->tos_required) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
- "server requires agreement to <%s>", acct->tos_required);
+ if (APR_SUCCESS != rv) {
+ acme->acct_id = NULL;
+ acme->acct = NULL;
+ acme->acct_key = NULL;
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ /* verification failed and account has been disabled.
+ Indicate to caller that he may try again. */
+ rv = APR_EAGAIN;
+ }
}
}
-
- apr_array_clear(acct->contacts);
- md_json_getsa(acct->contacts, body, MD_KEY_CONTACT, NULL);
- acct->registration = md_json_clone(ctx->p, body);
-
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "updated acct %s", acct->url);
return rv;
}
-static apr_status_t acct_register(md_acme_t *acme, apr_pool_t *p,
- apr_array_header_t *contacts, const char *agreement)
+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;
- md_pkey_t *pkey;
- const char *err = NULL, *uri;
- md_pkey_spec_t spec;
- int i;
-
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account");
- if (agreement) {
- if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, agreement, &err))) {
- md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
- "invalid agreement uri (%s): %s", err, agreement);
- goto out;
- }
+ while (APR_EAGAIN == (rv = acct_find_and_verify(store, MD_SG_ACCOUNTS,
+ mk_acct_pattern(acme->p, acme),
+ acme, md, acme->p))) {
+ /* nop */
}
- for (i = 0; i < contacts->nelts; ++i) {
- uri = APR_ARRAY_IDX(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);
- goto out;
+
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ /* No suitable account found in MD_SG_ACCOUNTS. Maybe a new account
+ * 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");
+ rv = acct_find_and_verify(store, MD_SG_STAGING, "*", acme, md, acme->p);
+ if (APR_EAGAIN == rv) {
+ rv = APR_ENOENT;
}
}
-
- spec.type = MD_PKEY_TYPE_RSA;
- spec.params.rsa.bits = MD_ACME_ACCT_PKEY_BITS;
-
- if (APR_SUCCESS == (rv = md_pkey_gen(&pkey, acme->p, &spec))
- && APR_SUCCESS == (rv = acct_make(&acme->acct, p, acme->url, NULL, contacts))) {
- acct_ctx_t ctx;
+ return rv;
+}
- acme->acct_key = pkey;
- if (agreement) {
- acme->acct->agreement = agreement;
- }
+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;
+ find_ctx ctx;
- ctx.acme = acme;
- ctx.p = p;
- rv = md_acme_POST(acme, acme->new_reg, on_init_acct_new, acct_upd, NULL, &ctx);
- if (APR_SUCCESS == rv) {
- md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p,
- "registered new account %s", acme->acct->url);
- }
- }
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.p = p;
+ ctx.md = md;
-out:
- if (APR_SUCCESS != rv && acme->acct) {
- acme->acct = NULL;
+ 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;
}
/**************************************************************************************************/
-/* acct validation */
+/* acct operation context */
+typedef struct {
+ md_acme_t *acme;
+ apr_pool_t *p;
+ const char *agreement;
+ const char *eab_kid;
+ const char *eab_hmac;
+} acct_ctx_t;
-static apr_status_t on_init_acct_valid(md_acme_req_t *req, void *baton)
-{
- md_json_t *jpayload;
+/**************************************************************************************************/
+/* acct update */
+static apr_status_t on_init_acct_upd(md_acme_req_t *req, void *baton)
+{
(void)baton;
- jpayload = md_json_create(req->p);
- md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
-
- return md_acme_req_body_init(req, jpayload);
+ return md_acme_req_body_init(req, NULL);
}
-static apr_status_t acct_valid(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
- md_json_t *body, void *baton)
+static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p,
+ const apr_table_t *hdrs, md_json_t *body, void *baton)
{
- md_acme_acct_t *acct = acme->acct;
+ acct_ctx_t *ctx = baton;
apr_status_t rv = APR_SUCCESS;
- const char *body_str;
- const char *tos_required;
+ 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) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, APR_EINVAL, p, "new acct without location");
+ return APR_EINVAL;
+ }
+ acct->url = apr_pstrdup(ctx->p, location);
+ }
- (void)p;
- (void)baton;
apr_array_clear(acct->contacts);
- md_json_getsa(acct->contacts, body, MD_KEY_CONTACT, NULL);
- acct->registration = md_json_clone(acme->p, body);
-
- body_str = md_json_writep(body, acme->p, MD_JSON_FMT_INDENT);
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p, "validate acct %s: %s",
- acct->url, body_str ? body_str : "<failed to serialize!>");
-
- acct->agreement = md_json_gets(acct->registration, MD_KEY_AGREEMENT, NULL);
- tos_required = md_link_find_relation(hdrs, acme->p, "terms-of-service");
-
- if (tos_required) {
- if (!acct->agreement || strcmp(tos_required, acct->agreement)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p,
- "needs to agree to terms-of-service '%s', "
- "has already agreed to '%s'",
- tos_required, acct->agreement);
- }
- acct->tos_required = tos_required;
+ md_json_dupsa(acct->contacts, acme->p, body, MD_KEY_CONTACT, NULL);
+ if (md_json_has_key(body, MD_KEY_STATUS, NULL)) {
+ acct->status = acct_st_from_str(md_json_gets(body, MD_KEY_STATUS, NULL));
+ }
+ if (md_json_has_key(body, MD_KEY_AGREEMENT, NULL)) {
+ acct->agreement = md_json_dups(acme->p, body, MD_KEY_AGREEMENT, NULL);
}
+ 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);
return rv;
}
-static apr_status_t md_acme_validate_acct(md_acme_t *acme)
+apr_status_t md_acme_acct_update(md_acme_t *acme)
{
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "acct validation");
+ acct_ctx_t ctx;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "acct update");
if (!acme->acct) {
return APR_EINVAL;
}
- return md_acme_POST(acme, acme->acct->url, on_init_acct_valid, acct_valid, NULL, NULL);
+ 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);
}
-/**************************************************************************************************/
-/* account setup */
-
-static apr_status_t acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+apr_status_t md_acme_acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
{
apr_status_t rv;
- if (APR_SUCCESS != (rv = md_acme_validate_acct(acme))) {
- if (acme->acct && (APR_ENOENT == rv || APR_EACCES == rv)) {
- if (!acme->acct->disabled) {
- acme->acct->disabled = 1;
+ if (APR_SUCCESS != (rv = md_acme_acct_update(acme))) {
+ 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) {
- md_acme_save(acme, store, p);
+ md_acme_acct_save(store, p, acme, &acme->acct_id, acme->acct, acme->acct_key);
}
}
acme->acct = NULL;
@@ -458,133 +502,187 @@ static apr_status_t acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t
return rv;
}
-apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store,
- apr_pool_t *p, const char *acct_id)
+/**************************************************************************************************/
+/* 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_acme_acct_t *acct;
- md_pkey_t *pkey;
+ md_json_t *eab, *prot_fields, *jwk;
+ md_data_t payload, hmac_key;
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 = acct;
- acme->acct_key = pkey;
- rv = 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;
- }
+
+ 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;
}
- return rv;
-}
+ payload.len = strlen(payload.data);
-apr_status_t md_acme_use_acct_staged(md_acme_t *acme, struct md_store_t *store,
- 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))) {
- acme->acct = acct;
- acme->acct_key = pkey;
- rv = acct_validate(acme, NULL, p);
+ 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;
}
-const char *md_acme_get_acct_id(md_acme_t *acme)
+static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
{
- return acme->acct? acme->acct->id : NULL;
-}
+ acct_ctx_t *ctx = baton;
+ md_json_t *jpayload, *jeab;
+ apr_status_t rv;
-const char *md_acme_get_agreement(md_acme_t *acme)
-{
- return acme->acct? acme->acct->agreement : NULL;
-}
+ 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);
+ }
+ 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_find_acct(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store,
+ const md_t *md, apr_pool_t *p)
{
- md_acme_acct_t *acct;
- md_pkey_t *pkey;
apr_status_t rv;
+ md_pkey_t *pkey;
+ const char *err = NULL, *uri;
+ md_pkey_spec_t spec;
+ int i;
+ acct_ctx_t ctx;
- while (APR_SUCCESS == acct_find(&acct, &pkey, store, acme, acme->p)) {
- acme->acct = acct;
- acme->acct_key = pkey;
- rv = acct_validate(acme, store, p);
-
- if (APR_SUCCESS == rv) {
- return rv;
+ 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 && md->ca_agreement) {
+ ctx.agreement = !strcmp("accepted", md->ca_agreement)?
+ acme->ca_agreement : md->ca_agreement;
+ }
+
+ if (ctx.agreement) {
+ if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, ctx.agreement, &err))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "invalid agreement uri (%s): %s", err, ctx.agreement);
+ goto out;
}
- else {
- acme->acct = NULL;
- acme->acct_key = NULL;
- if (!APR_STATUS_IS_ENOENT(rv)) {
- /* encountered error with server */
- return rv;
+ }
+ ctx.eab_kid = md->ca_eab_kid;
+ ctx.eab_hmac = md->ca_eab_hmac;
+
+ 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);
+ goto out;
+ }
+ }
+
+ /* If there is no key selected yet, try to find an existing one for the same host.
+ * Let's Encrypt identifies accounts by their key for their ACMEv1 and v2 services.
+ * Although the account appears on both services with different urls, it is
+ * internally the same one.
+ * I think this is beneficial if someone migrates from ACMEv1 to v2 and not a leak
+ * of identifying information.
+ */
+ if (!acme->acct_key) {
+ find_ctx fctx;
+
+ memset(&fctx, 0, sizeof(fctx));
+ fctx.p = p;
+ 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,
+ (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);
+ }
+ else {
+ acme->acct_key = NULL;
}
}
}
- return APR_ENOENT;
-}
-
-apr_status_t md_acme_create_acct(md_acme_t *acme, apr_pool_t *p, apr_array_header_t *contacts,
- const char *agreement)
-{
- return acct_register(acme, p, contacts, agreement);
-}
-
-/**************************************************************************************************/
-/* Delete the account */
-
-apr_status_t md_acme_unstore_acct(md_store_t *store, apr_pool_t *p, const char *acct_id)
-{
- apr_status_t rv = APR_SUCCESS;
- rv = md_store_remove(store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCOUNT, p, 1);
+ /* If we still have no key, generate a new one */
+ if (!acme->acct_key) {
+ spec.type = MD_PKEY_TYPE_RSA;
+ spec.params.rsa.bits = MD_ACME_ACCT_PKEY_BITS;
+
+ if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, acme->p, &spec))) goto out;
+ acme->acct_key = pkey;
+ 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, 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_store_remove(store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCT_KEY, p, 1);
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p,
+ "registered new account %s", acme->acct->url);
+ }
+
+out:
+ if (APR_SUCCESS != rv && acme->acct) {
+ acme->acct = NULL;
}
return rv;
}
+/**************************************************************************************************/
+/* Deactivate the account */
+
static apr_status_t on_init_acct_del(md_acme_req_t *req, void *baton)
{
md_json_t *jpayload;
(void)baton;
jpayload = md_json_create(req->p);
- md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
- md_json_setb(1, jpayload, "delete", NULL);
-
+ md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL);
return md_acme_req_body_init(req, jpayload);
}
-static apr_status_t acct_del(md_acme_t *acme, apr_pool_t *p,
- const apr_table_t *hdrs, md_json_t *body, void *baton)
-{
- md_store_t *store = baton;
- apr_status_t rv = APR_SUCCESS;
-
- (void)hdrs;
- (void)body;
- md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, "deleted account %s", acme->acct->url);
- if (store) {
- rv = md_acme_unstore_acct(store, p, acme->acct->id);
- acme->acct = NULL;
- acme->acct_key = NULL;
- }
- return rv;
-}
-
-apr_status_t md_acme_delete_acct(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p)
{
md_acme_acct_t *acct = acme->acct;
+ acct_ctx_t ctx;
(void)p;
if (!acct) {
@@ -592,7 +690,10 @@ apr_status_t md_acme_delete_acct(md_acme_t *acme, md_store_t *store, apr_pool_t
}
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "delete account %s from %s",
acct->url, acct->ca_url);
- return md_acme_POST(acme, acct->url, on_init_acct_del, acct_del, NULL, store);
+ 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);
}
/**************************************************************************************************/
@@ -604,9 +705,9 @@ static apr_status_t on_init_agree_tos(md_acme_req_t *req, void *baton)
md_json_t *jpayload;
jpayload = md_json_create(req->p);
- md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
- md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
-
+ if (ctx->acme->acct->agreement) {
+ md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
+ }
return md_acme_req_body_init(req, jpayload);
}
@@ -615,21 +716,14 @@ apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *agreement
acct_ctx_t ctx;
acme->acct->agreement = agreement;
+ if (!strcmp("accepted", agreement) && acme->ca_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, &ctx);
-}
-
-static int agreement_required(md_acme_acct_t *acct)
-{
- /* We used to really check if the account agreement and the one
- * indicated as valid are the very same:
- * return (!acct->agreement
- * || (acct->tos_required && strcmp(acct->tos_required, acct->agreement)));
- * However, LE is happy if the account has agreed to a ToS in the past and
- * does not required a renewed acceptance.
- */
- return !acct->agreement;
+ return md_acme_POST(acme, acme->acct->url, on_init_agree_tos, acct_upd, NULL, NULL, &ctx);
}
apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p,
@@ -637,32 +731,17 @@ apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p,
{
apr_status_t rv = APR_SUCCESS;
- /* Check if (correct) Terms-of-Service for account were accepted */
+ /* We used to really check if the account agreement and the one indicated in meta
+ * are the very same. However, LE is happy if the account has agreed to a ToS in
+ * the past and does not require a renewed acceptance.
+ */
*prequired = NULL;
- if (agreement_required(acme->acct)) {
- const char *tos = acme->acct->tos_required;
- if (!tos) {
- if (APR_SUCCESS != (rv = md_acme_validate_acct(acme))) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p,
- "validate for account %s", acme->acct->id);
- return rv;
- }
- tos = acme->acct->tos_required;
- if (!tos) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p, "unknown terms-of-service "
- "required after validation of account %s", acme->acct->id);
- return APR_EGENERAL;
- }
- }
-
- if (acme->acct->agreement && !strcmp(tos, acme->acct->agreement)) {
- rv = md_acme_agree(acme, p, tos);
- }
- else if (agreement && !strcmp(tos, agreement)) {
- rv = md_acme_agree(acme, p, tos);
+ if (!acme->acct->agreement && acme->ca_agreement) {
+ if (agreement) {
+ rv = md_acme_agree(acme, p, acme->ca_agreement);
}
else {
- *prequired = apr_pstrdup(p, tos);
+ *prequired = acme->ca_agreement;
rv = APR_INCOMPLETE;
}
}