diff options
Diffstat (limited to 'modules/md/md_acme_authz.c')
-rw-r--r-- | modules/md/md_acme_authz.c | 801 |
1 files changed, 397 insertions, 404 deletions
diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c index 2b5cbdc..f4579b3 100644 --- a/modules/md/md_acme_authz.c +++ b/modules/md/md_acme_authz.c @@ -32,6 +32,7 @@ #include "md_http.h" #include "md_log.h" #include "md_jws.h" +#include "md_result.h" #include "md_store.h" #include "md_util.h" @@ -46,64 +47,6 @@ md_acme_authz_t *md_acme_authz_create(apr_pool_t *p) return authz; } -md_acme_authz_set_t *md_acme_authz_set_create(apr_pool_t *p) -{ - md_acme_authz_set_t *authz_set; - - authz_set = apr_pcalloc(p, sizeof(*authz_set)); - authz_set->authzs = apr_array_make(p, 5, sizeof(md_acme_authz_t *)); - - return authz_set; -} - -md_acme_authz_t *md_acme_authz_set_get(md_acme_authz_set_t *set, const char *domain) -{ - md_acme_authz_t *authz; - int i; - - assert(domain); - for (i = 0; i < set->authzs->nelts; ++i) { - authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *); - if (!apr_strnatcasecmp(domain, authz->domain)) { - return authz; - } - } - return NULL; -} - -apr_status_t md_acme_authz_set_add(md_acme_authz_set_t *set, md_acme_authz_t *authz) -{ - md_acme_authz_t *existing; - - assert(authz->domain); - if (NULL != (existing = md_acme_authz_set_get(set, authz->domain))) { - return APR_EINVAL; - } - APR_ARRAY_PUSH(set->authzs, md_acme_authz_t*) = authz; - return APR_SUCCESS; -} - -apr_status_t md_acme_authz_set_remove(md_acme_authz_set_t *set, const char *domain) -{ - md_acme_authz_t *authz; - int i; - - assert(domain); - for (i = 0; i < set->authzs->nelts; ++i) { - authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *); - if (!apr_strnatcasecmp(domain, authz->domain)) { - int n = i + 1; - if (n < set->authzs->nelts) { - void **elems = (void **)set->authzs->elts; - memmove(elems + i, elems + n, (size_t)(set->authzs->nelts - n) * sizeof(*elems)); - } - --set->authzs->nelts; - return APR_SUCCESS; - } - } - return APR_ENOENT; -} - /**************************************************************************************************/ /* Register a new authorization */ @@ -133,88 +76,65 @@ static void authz_req_ctx_init(authz_req_ctx *ctx, md_acme_t *acme, ctx->authz = authz; } -static apr_status_t on_init_authz(md_acme_req_t *req, void *baton) -{ - authz_req_ctx *ctx = baton; - md_json_t *jpayload; - - jpayload = md_json_create(req->p); - md_json_sets("new-authz", jpayload, MD_KEY_RESOURCE, NULL); - md_json_sets("dns", jpayload, MD_KEY_IDENTIFIER, MD_KEY_TYPE, NULL); - md_json_sets(ctx->domain, jpayload, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL); - - return md_acme_req_body_init(req, jpayload); -} +/**************************************************************************************************/ +/* Update an existing authorization */ -static apr_status_t authz_created(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs, - md_json_t *body, void *baton) +apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url, + md_acme_authz_t **pauthz) { - authz_req_ctx *ctx = baton; - const char *location = apr_table_get(hdrs, "location"); - apr_status_t rv = APR_SUCCESS; + md_acme_authz_t *authz; + apr_status_t rv; - (void)acme; - (void)p; - if (location) { - ctx->authz = md_acme_authz_create(ctx->p); - ctx->authz->domain = apr_pstrdup(ctx->p, ctx->domain); - ctx->authz->location = apr_pstrdup(ctx->p, location); - ctx->authz->resource = md_json_clone(ctx->p, body); - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "authz_new at %s", location); - } - else { - rv = APR_EINVAL; - md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ctx->p, "new authz, no location header"); - } + authz = apr_pcalloc(p, sizeof(*authz)); + authz->url = apr_pstrdup(p, url); + rv = md_acme_authz_update(authz, acme, p); + + *pauthz = (APR_SUCCESS == rv)? authz : NULL; return rv; } -apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, md_acme_t *acme, - md_store_t *store, const char *domain, apr_pool_t *p) +typedef struct { + apr_pool_t *p; + md_acme_authz_t *authz; +} error_ctx_t; + +static int copy_challenge_error(void *baton, size_t index, md_json_t *json) { - apr_status_t rv; - authz_req_ctx ctx; + error_ctx_t *ctx = baton; - (void)store; - authz_req_ctx_init(&ctx, acme, domain, NULL, p); - - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "create new authz"); - rv = md_acme_POST(acme, acme->new_authz, on_init_authz, authz_created, NULL, &ctx); - - *pauthz = (APR_SUCCESS == rv)? ctx.authz : NULL; - return rv; + (void)index; + if (md_json_has_key(json, MD_KEY_ERROR, NULL)) { + ctx->authz->error_type = md_json_dups(ctx->p, json, MD_KEY_ERROR, MD_KEY_TYPE, NULL); + ctx->authz->error_detail = md_json_dups(ctx->p, json, MD_KEY_ERROR, MD_KEY_DETAIL, NULL); + ctx->authz->error_subproblems = md_json_dupj(ctx->p, json, MD_KEY_ERROR, MD_KEY_SUBPROBLEMS, NULL); + } + return 1; } -/**************************************************************************************************/ -/* Update an existing authorization */ - -apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, - md_store_t *store, apr_pool_t *p) +apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_pool_t *p) { md_json_t *json; const char *s, *err; md_log_level_t log_level; apr_status_t rv; - MD_CHK_VARS; + error_ctx_t ctx; - (void)store; assert(acme); assert(acme->http); assert(authz); - assert(authz->location); + assert(authz->url); authz->state = MD_ACME_AUTHZ_S_UNKNOWN; json = NULL; + authz->error_type = authz->error_detail = NULL; + authz->error_subproblems = NULL; err = "unable to parse response"; log_level = MD_LOG_ERR; - if (MD_OK(md_acme_get_json(&json, acme, authz->location, p)) - && (s = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_TYPE, NULL)) - && !strcmp(s, "dns") - && (s = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL)) - && !strcmp(s, authz->domain) + if (APR_SUCCESS == (rv = md_acme_get_json(&json, acme, authz->url, p)) && (s = md_json_gets(json, MD_KEY_STATUS, NULL))) { - + + authz->domain = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL); authz->resource = json; if (!strcmp(s, "pending")) { authz->state = MD_ACME_AUTHZ_S_PENDING; @@ -227,7 +147,10 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, log_level = MD_LOG_DEBUG; } else if (!strcmp(s, "invalid")) { + ctx.p = p; + ctx.authz = authz; authz->state = MD_ACME_AUTHZ_S_INVALID; + md_json_itera(copy_challenge_error, &ctx, json, MD_KEY_CHALLENGES, NULL); err = "challenge 'invalid'"; } } @@ -239,7 +162,7 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, if (md_log_is_level(p, log_level)) { md_log_perror(MD_LOG_MARK, log_level, rv, p, "ACME server authz: %s for %s at %s. " - "Exact repsonse was: %s", err? err : "", authz->domain, authz->location, + "Exact response was: %s", err, authz->domain, authz->url, json? md_json_writep(json, p, MD_JSON_FMT_COMPACT) : "not available"); } @@ -256,7 +179,12 @@ static md_acme_authz_cha_t *cha_from_json(apr_pool_t *p, size_t index, md_json_t cha = apr_pcalloc(p, sizeof(*cha)); cha->index = index; cha->type = md_json_dups(p, json, MD_KEY_TYPE, NULL); - cha->uri = md_json_dups(p, json, MD_KEY_URI, NULL); + if (md_json_has_key(json, MD_KEY_URL, NULL)) { /* ACMEv2 */ + cha->uri = md_json_dups(p, json, MD_KEY_URL, NULL); + } + else { /* ACMEv1 */ + cha->uri = md_json_dups(p, json, MD_KEY_URI, NULL); + } cha->token = md_json_dups(p, json, MD_KEY_TOKEN, NULL); cha->key_authz = md_json_dups(p, json, MD_KEY_KEYAUTHZ, NULL); @@ -265,13 +193,10 @@ static md_acme_authz_cha_t *cha_from_json(apr_pool_t *p, size_t index, md_json_t static apr_status_t on_init_authz_resp(md_acme_req_t *req, void *baton) { - authz_req_ctx *ctx = baton; md_json_t *jpayload; + (void)baton; jpayload = md_json_create(req->p); - md_json_sets("challenge", jpayload, MD_KEY_RESOURCE, NULL); - md_json_sets(ctx->challenge->key_authz, jpayload, MD_KEY_KEYAUTHZ, NULL); - return md_acme_req_body_init(req, jpayload); } @@ -284,7 +209,7 @@ static apr_status_t authz_http_set(md_acme_t *acme, apr_pool_t *p, const apr_tab (void)p; (void)hdrs; (void)body; - md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "updated authz %s", ctx->authz->location); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ctx->p, "updated authz %s", ctx->authz->url); return APR_SUCCESS; } @@ -293,14 +218,13 @@ static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *a { const char *thumb64, *key_authz; apr_status_t rv; - MD_CHK_VARS; (void)authz; assert(cha); assert(cha->token); *pchanged = 0; - if (MD_OK(md_jws_pkey_thumb(&thumb64, p, acme->acct_key))) { + if (APR_SUCCESS == (rv = md_jws_pkey_thumb(&thumb64, p, acme->acct_key))) { key_authz = apr_psprintf(p, "%s.%s", cha->token, thumb64); if (cha->key_authz) { if (strcmp(key_authz, cha->key_authz)) { @@ -316,136 +240,334 @@ static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *a return rv; } -static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, +static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, - md_pkey_spec_t *key_spec, apr_pool_t *p) + md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const md_t *md, + apr_table_t *env, md_result_t *result, + const char **psetup_token, apr_pool_t *p) { const char *data; apr_status_t rv; int notify_server; - MD_CHK_VARS; - (void)key_spec; - if (!MD_OK(setup_key_authz(cha, authz, acme, p, ¬ify_server))) { + (void)key_specs; + (void)env; + (void)acme_tls_1_domains; + (void)md; + + if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) { goto out; } rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01, MD_SV_TEXT, (void**)&data, p); if ((APR_SUCCESS == rv && strcmp(cha->key_authz, data)) || APR_STATUS_IS_ENOENT(rv)) { + const char *content = apr_psprintf(p, "%s\n", cha->key_authz); rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01, - MD_SV_TEXT, (void*)cha->key_authz, 0); - authz->dir = authz->domain; + MD_SV_TEXT, (void*)content, 0); notify_server = 1; } if (APR_SUCCESS == rv && notify_server) { authz_req_ctx ctx; - + const char *event; + + /* 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_HTTP01, authz->domain); + 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); ctx.challenge = cha; - rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, &ctx); + rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx); } out: + *psetup_token = (APR_SUCCESS == rv)? + apr_psprintf(p, "%s:%s", MD_AUTHZ_TYPE_HTTP01, authz->domain) : NULL; return rv; } -static apr_status_t setup_cha_dns(const char **pdns, md_acme_authz_cha_t *cha, apr_pool_t *p) +void tls_alpn01_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn ) { - const char *dhex; - char *dns; - apr_size_t dhex_len; - apr_status_t rv; - - rv = md_crypt_sha256_digest_hex(&dhex, p, cha->key_authz, strlen(cha->key_authz)); - if (APR_SUCCESS == rv) { - dhex = md_util_str_tolower((char*)dhex); - dhex_len = strlen(dhex); - assert(dhex_len > 32); - dns = apr_pcalloc(p, dhex_len + 1 + sizeof(MD_TLSSNI01_DNS_SUFFIX)); - strncpy(dns, dhex, 32); - dns[32] = '.'; - strncpy(dns+33, dhex+32, dhex_len-32); - memcpy(dns+(dhex_len+1), MD_TLSSNI01_DNS_SUFFIX, sizeof(MD_TLSSNI01_DNS_SUFFIX)); - } - *pdns = (APR_SUCCESS == rv)? dns : NULL; - return rv; + *keyfn = apr_pstrcat(p, "acme-tls-alpn-01-", md_pkey_filename(kspec, p), NULL); + *certfn = apr_pstrcat(p, "acme-tls-alpn-01-", md_chain_filename(kspec, p), NULL); } -static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, - md_acme_t *acme, md_store_t *store, - md_pkey_spec_t *key_spec, apr_pool_t *p) +static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, + md_acme_t *acme, md_store_t *store, + md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const md_t *md, + apr_table_t *env, md_result_t *result, + const char **psetup_token, apr_pool_t *p) { - md_cert_t *cha_cert; - md_pkey_t *cha_key; - const char *cha_dns; + const char *acme_id, *token; apr_status_t rv; int notify_server; - apr_array_header_t *domains; - MD_CHK_VARS; - - if ( !MD_OK(setup_key_authz(cha, authz, acme, p, ¬ify_server)) - || !MD_OK(setup_cha_dns(&cha_dns, cha, p))) { - goto out; - } + md_data_t data; + int i; - rv = md_store_load(store, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT, - MD_SV_CERT, (void**)&cha_cert, p); - if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, cha_dns)) - || APR_STATUS_IS_ENOENT(rv)) { - - if (APR_SUCCESS != (rv = md_pkey_gen(&cha_key, p, key_spec))) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-sni-01 challenge key", - authz->domain); - goto out; + (void)env; + (void)md; + if (md_array_str_index(acme_tls_1_domains, authz->domain, 0, 0) < 0) { + rv = APR_ENOTIMPL; + if (acme_tls_1_domains->nelts) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, + "%s: protocol 'acme-tls/1' seems not enabled for this domain, " + "but is enabled for other associated domains. " + "Continuing with fingers crossed.", authz->domain); } - - /* setup a certificate containing the challenge dns */ - domains = apr_array_make(p, 5, sizeof(const char*)); - APR_ARRAY_PUSH(domains, const char*) = cha_dns; - if (!MD_OK(md_cert_self_sign(&cha_cert, authz->domain, domains, cha_key, - apr_time_from_sec(7 * MD_SECS_PER_DAY), p))) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: setup self signed cert for %s", - authz->domain, cha_dns); + else { + md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, + "%s: protocol 'acme-tls/1' seems not enabled for this or " + "any other associated domain. Not attempting challenge " + "type tls-alpn-01.", authz->domain); goto out; } + } + if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) { + goto out; + } + + /* Create a "tls-alpn-01" certificate for the domain we want to authenticate. + * The server will need to answer a TLS connection with SNI == authz->domain + * and ALPN protocol "acme-tls/1" with this certificate. + */ + md_data_init_str(&data, cha->key_authz); + rv = md_crypt_sha256_digest_hex(&token, p, &data); + if (APR_SUCCESS != rv) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 validation token", + authz->domain); + goto out; + } + acme_id = apr_psprintf(p, "critical,DER:04:20:%s", token); + + /* Each configured key type must be generated to ensure: + * that any fallback certs already given to mod_ssl are replaced. + * We expect that the validation client (at the CA) can deal with at + * least one of them. + */ + + for (i = 0; i < md_pkeys_spec_count(key_specs); ++i) { + char *kfn, *cfn; + md_cert_t *cha_cert; + md_pkey_t *cha_key; + md_pkey_spec_t *key_spec; + + key_spec = md_pkeys_spec_get(key_specs, i); + tls_alpn01_fnames(p, key_spec, &kfn, &cfn); + + rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, cfn, + MD_SV_CERT, (void**)&cha_cert, p); + if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, authz->domain)) + || APR_STATUS_IS_ENOENT(rv)) { + if (APR_SUCCESS != (rv = md_pkey_gen(&cha_key, p, key_spec))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 %s challenge key", + authz->domain, md_pkey_spec_name(key_spec)); + goto out; + } + + if (APR_SUCCESS != (rv = md_cert_make_tls_alpn_01(&cha_cert, authz->domain, acme_id, cha_key, + apr_time_from_sec(7 * MD_SECS_PER_DAY), p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 %s challenge cert", + authz->domain, md_pkey_spec_name(key_spec)); + goto out; + } - if (MD_OK(md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_PKEY, - MD_SV_PKEY, (void*)cha_key, 0))) { - rv = md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT, - MD_SV_CERT, (void*)cha_cert, 0); + if (APR_SUCCESS == (rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, kfn, + MD_SV_PKEY, (void*)cha_key, 0))) { + rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, cfn, + MD_SV_CERT, (void*)cha_cert, 0); + } + ++notify_server; } - authz->dir = cha_dns; - notify_server = 1; } if (APR_SUCCESS == rv && notify_server) { authz_req_ctx ctx; - + const char *event; + + /* 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); + 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); ctx.challenge = cha; - rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, &ctx); + rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx); } out: + *psetup_token = (APR_SUCCESS == rv)? + apr_psprintf(p, "%s:%s", MD_AUTHZ_TYPE_TLSALPN01, authz->domain) : NULL; return rv; } -typedef apr_status_t cha_starter(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, - md_acme_t *acme, md_store_t *store, - md_pkey_spec_t *key_spec, apr_pool_t *p); +static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, + md_acme_t *acme, md_store_t *store, + md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const md_t *md, + apr_table_t *env, md_result_t *result, + const char **psetup_token, apr_pool_t *p) +{ + const char *token; + const char * const *argv; + const char *cmdline, *dns01_cmd; + apr_status_t rv; + int exit_code, notify_server; + authz_req_ctx ctx; + md_data_t data; + const char *event; + + (void)store; + (void)key_specs; + (void)acme_tls_1_domains; + + dns01_cmd = md->dns01_cmd; + if (!dns01_cmd) + dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01); + if (!dns01_cmd) { + rv = APR_ENOTIMPL; + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 command not set", + authz->domain); + goto out; + } + + if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) { + goto out; + } + + md_data_init_str(&data, cha->key_authz); + rv = md_crypt_sha256_digest64(&token, p, &data); + if (APR_SUCCESS != rv) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create dns-01 token for %s", + md->name, authz->domain); + goto out; + } + + cmdline = apr_psprintf(p, "%s setup %s %s", dns01_cmd, authz->domain, token); + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, + "%s: dns-01 setup command: %s", authz->domain, cmdline); + + apr_tokenize_to_argv(cmdline, (char***)&argv, p); + if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, &exit_code))) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, + "%s: dns-01 setup command failed to execute for %s", md->name, authz->domain); + goto out; + } + if (exit_code) { + rv = APR_EGENERAL; + md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, + "%s: dns-01 setup command returns %d for %s", md->name, exit_code, authz->domain); + goto out; + } + + /* 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); + 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", + md->name, authz->domain); + authz_req_ctx_init(&ctx, acme, NULL, authz, p); + ctx.challenge = cha; + rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx); + +out: + *psetup_token = (APR_SUCCESS == rv)? + apr_psprintf(p, "%s:%s %s", MD_AUTHZ_TYPE_DNS01, authz->domain, token) : NULL; + return rv; +} + +static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, const md_t *md, + apr_table_t *env, apr_pool_t *p) +{ + const char * const *argv; + const char *cmdline, *dns01_cmd, *dns01v; + char *tmp, *s; + apr_status_t rv; + int exit_code; + + (void)store; + + dns01_cmd = md->dns01_cmd; + if (!dns01_cmd) + dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01); + if (!dns01_cmd) { + rv = APR_ENOTIMPL; + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 command not set for %s", + md->name, domain); + goto out; + } + dns01v = apr_table_get(env, MD_KEY_DNS01_VERSION); + if (!dns01v || strcmp(dns01v, "2")) { + /* use older version of teardown args with only domain, remove token */ + tmp = apr_pstrdup(p, domain); + s = strchr(tmp, ' '); + if (s) { + *s = '\0'; + domain = tmp; + } + } + + cmdline = apr_psprintf(p, "%s teardown %s", dns01_cmd, domain); + apr_tokenize_to_argv(cmdline, (char***)&argv, p); + if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, &exit_code)) || exit_code) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, + "%s: dns-01 teardown command failed (exit code=%d) for %s", + md->name, exit_code, domain); + } +out: + return rv; +} + +static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, const md_t *md, + apr_table_t *env, apr_pool_t *p) +{ + (void)md; + (void)env; + return md_store_purge(store, p, MD_SG_CHALLENGES, domain); +} + +typedef apr_status_t cha_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, + md_acme_t *acme, md_store_t *store, + md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const md_t *md, + apr_table_t *env, md_result_t *result, + const char **psetup_token, apr_pool_t *p); + +typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, const md_t *md, + apr_table_t *env, apr_pool_t *p); typedef struct { const char *name; - cha_starter *start; + cha_setup *setup; + cha_teardown *teardown; } cha_type; static const cha_type CHA_TYPES[] = { - { MD_AUTHZ_TYPE_HTTP01, cha_http_01_setup }, - { MD_AUTHZ_TYPE_TLSSNI01, cha_tls_sni_01_setup }, + { MD_AUTHZ_TYPE_HTTP01, cha_http_01_setup, cha_teardown_dir }, + { MD_AUTHZ_TYPE_TLSALPN01, cha_tls_alpn_01_setup, cha_teardown_dir }, + { MD_AUTHZ_TYPE_DNS01, cha_dns_01_setup, cha_dns_01_teardown }, }; static const apr_size_t CHA_TYPES_LEN = (sizeof(CHA_TYPES)/sizeof(CHA_TYPES[0])); @@ -481,13 +603,15 @@ static apr_status_t find_type(void *baton, size_t index, md_json_t *json) } apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, - apr_array_header_t *challenges, - md_pkey_spec_t *key_spec, apr_pool_t *p) + apr_array_header_t *challenges, md_pkeys_spec_t *key_specs, + apr_array_header_t *acme_tls_1_domains, const md_t *md, + apr_table_t *env, apr_pool_t *p, const char **psetup_token, + md_result_t *result) { apr_status_t rv; - int i; + int i, j; cha_find_ctx fctx; - + assert(acme); assert(authz); assert(authz->resource); @@ -495,229 +619,98 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s fctx.p = p; fctx.accepted = NULL; - /* Look in the order challenge types are defined */ - for (i = 0; i < challenges->nelts && !fctx.accepted; ++i) { + /* Look in the order challenge types are defined: + * - if they are offered by the CA, try to set it up + * - if setup was successful, we are done and the CA will evaluate us + * - if setup failed, continue to look for another supported challenge type + * - if there is no overlap in types, tell the user that she has to configure + * either more types (dns, tls-alpn-01), make ports available or refrain + * from using wildcard domains when dns is not available. etc. + * - if there was an overlap, but no setup was successful, report that. We + * will retry this, maybe the failure is temporary (e.g. command to setup DNS + */ + md_result_printf(result, 0, "%s: selecting suitable authorization challenge " + "type, this domain supports %s", + authz->domain, apr_array_pstrcat(p, challenges, ' ')); + rv = APR_ENOTIMPL; + *psetup_token = NULL; + for (i = 0; i < challenges->nelts; ++i) { fctx.type = APR_ARRAY_IDX(challenges, i, const char *); + fctx.accepted = NULL; md_json_itera(find_type, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, + "%s: challenge type '%s' for %s: %s", + authz->domain, fctx.type, md->name, + fctx.accepted? "maybe acceptable" : "not applicable"); + + if (fctx.accepted) { + for (j = 0; j < (int)CHA_TYPES_LEN; ++j) { + if (!apr_strnatcasecmp(CHA_TYPES[j].name, fctx.accepted->type)) { + md_result_activity_printf(result, "Setting up challenge '%s' for domain %s", + fctx.accepted->type, authz->domain); + rv = CHA_TYPES[j].setup(fctx.accepted, authz, acme, store, key_specs, + acme_tls_1_domains, md, env, result, + psetup_token, p); + if (APR_SUCCESS == rv) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, + "%s: set up challenge '%s' for %s", + authz->domain, fctx.accepted->type, md->name); + goto out; + } + md_result_printf(result, rv, "error setting up challenge '%s' for %s, " + "for domain %s, looking for other option", + fctx.accepted->type, authz->domain, md->name); + md_result_log(result, MD_LOG_INFO); + } + } + } } - if (!fctx.accepted) { +out: + if (!fctx.accepted || APR_ENOTIMPL == rv) { rv = APR_EINVAL; fctx.offered = apr_array_make(p, 5, sizeof(const char*)); md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL); - md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, - "%s: the server offers no ACME challenge that is configured " - "for this MD. The server offered '%s' and available for this " - "MD are: '%s' (via %s).", + md_result_printf(result, rv, "None of offered challenge types for domain %s are supported. " + "The server offered '%s' and available are: '%s'.", authz->domain, apr_array_pstrcat(p, fctx.offered, ' '), - apr_array_pstrcat(p, challenges, ' '), - authz->location); - return rv; + apr_array_pstrcat(p, challenges, ' ')); + result->problem = "challenge-mismatch"; + md_result_log(result, MD_LOG_ERR); } - - for (i = 0; i < (int)CHA_TYPES_LEN; ++i) { - if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) { - return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, key_spec, p); - } + else if (APR_SUCCESS != rv) { + fctx.offered = apr_array_make(p, 5, sizeof(const char*)); + md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL); + md_result_printf(result, rv, "None of the offered challenge types %s offered " + "for domain %s could be setup successfully. Please check the " + "log for errors.", authz->domain, + apr_array_pstrcat(p, fctx.offered, ' ')); + result->problem = "challenge-setup-failure"; + md_result_log(result, MD_LOG_ERR); } - - rv = APR_ENOTIMPL; - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, - "%s: no implementation found for challenge '%s'", - authz->domain, fctx.accepted->type); return rv; } -/**************************************************************************************************/ -/* Delete an existing authz resource */ - -typedef struct { - apr_pool_t *p; - md_acme_authz_t *authz; -} del_ctx; - -static apr_status_t on_init_authz_del(md_acme_req_t *req, void *baton) -{ - md_json_t *jpayload; - - (void)baton; - jpayload = md_json_create(req->p); - md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL); - - return md_acme_req_body_init(req, jpayload); -} - -static apr_status_t authz_del(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs, - md_json_t *body, void *baton) -{ - authz_req_ctx *ctx = baton; - - (void)p; - (void)body; - (void)hdrs; - md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "deleted authz %s", ctx->authz->location); - acme->acct = NULL; - return APR_SUCCESS; -} - -apr_status_t md_acme_authz_del(md_acme_authz_t *authz, md_acme_t *acme, - md_store_t *store, apr_pool_t *p) -{ - authz_req_ctx ctx; - - (void)store; - ctx.p = p; - ctx.authz = authz; - - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "delete authz for %s from %s", - authz->domain, authz->location); - return md_acme_POST(acme, authz->location, on_init_authz_del, authz_del, NULL, &ctx); -} - -/**************************************************************************************************/ -/* authz conversion */ - -md_json_t *md_acme_authz_to_json(md_acme_authz_t *a, apr_pool_t *p) -{ - md_json_t *json = md_json_create(p); - if (json) { - md_json_sets(a->domain, json, MD_KEY_DOMAIN, NULL); - md_json_sets(a->location, json, MD_KEY_LOCATION, NULL); - md_json_sets(a->dir, json, MD_KEY_DIR, NULL); - md_json_setl(a->state, json, MD_KEY_STATE, NULL); - return json; - } - return NULL; -} - -md_acme_authz_t *md_acme_authz_from_json(struct md_json_t *json, apr_pool_t *p) +apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *token, + const md_t *md, apr_table_t *env, apr_pool_t *p) { - md_acme_authz_t *authz = md_acme_authz_create(p); - if (authz) { - authz->domain = md_json_dups(p, json, MD_KEY_DOMAIN, NULL); - authz->location = md_json_dups(p, json, MD_KEY_LOCATION, NULL); - authz->dir = md_json_dups(p, json, MD_KEY_DIR, NULL); - authz->state = (md_acme_authz_state_t)md_json_getl(json, MD_KEY_STATE, NULL); - return authz; - } - return NULL; -} - -/**************************************************************************************************/ -/* authz_set conversion */ - -#define MD_KEY_ACCOUNT "account" -#define MD_KEY_AUTHZS "authorizations" - -static apr_status_t authz_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton) -{ - (void)baton; - return md_json_setj(md_acme_authz_to_json(value, p), json, NULL); -} - -static apr_status_t authz_from_json(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton) -{ - (void)baton; - *pvalue = md_acme_authz_from_json(json, p); - return (*pvalue)? APR_SUCCESS : APR_EINVAL; -} - -md_json_t *md_acme_authz_set_to_json(md_acme_authz_set_t *set, apr_pool_t *p) -{ - md_json_t *json = md_json_create(p); - if (json) { - md_json_seta(set->authzs, authz_to_json, NULL, json, MD_KEY_AUTHZS, NULL); - return json; - } - return NULL; -} - -md_acme_authz_set_t *md_acme_authz_set_from_json(md_json_t *json, apr_pool_t *p) -{ - md_acme_authz_set_t *set = md_acme_authz_set_create(p); - if (set) { - md_json_geta(set->authzs, authz_from_json, NULL, json, MD_KEY_AUTHZS, NULL); - return set; - } - return NULL; -} - -/**************************************************************************************************/ -/* persistence */ - -apr_status_t md_acme_authz_set_load(struct md_store_t *store, md_store_group_t group, - const char *md_name, md_acme_authz_set_t **pauthz_set, - apr_pool_t *p) -{ - apr_status_t rv; - md_json_t *json; - md_acme_authz_set_t *authz_set; - - rv = md_store_load_json(store, group, md_name, MD_FN_AUTHZ, &json, p); - if (APR_SUCCESS == rv) { - authz_set = md_acme_authz_set_from_json(json, p); - } - *pauthz_set = (APR_SUCCESS == rv)? authz_set : NULL; - return rv; -} - -static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap) -{ - md_store_t *store = baton; - md_json_t *json; - md_store_group_t group; - md_acme_authz_set_t *set; - const char *md_name; - int create; - - (void)p; - group = (md_store_group_t)va_arg(ap, int); - md_name = va_arg(ap, const char *); - set = va_arg(ap, md_acme_authz_set_t *); - create = va_arg(ap, int); - - json = md_acme_authz_set_to_json(set, ptemp); - assert(json); - return md_store_save_json(store, ptemp, group, md_name, MD_FN_AUTHZ, json, create); -} - -apr_status_t md_acme_authz_set_save(struct md_store_t *store, apr_pool_t *p, - md_store_group_t group, const char *md_name, - md_acme_authz_set_t *authz_set, int create) -{ - return md_util_pool_vdo(p_save, store, p, group, md_name, authz_set, create, NULL); -} - -static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap) -{ - md_store_t *store = baton; - md_acme_authz_set_t *authz_set; - const md_acme_authz_t *authz; - md_store_group_t group; - const char *md_name; + char *challenge, *domain; int i; - - group = (md_store_group_t)va_arg(ap, int); - md_name = va_arg(ap, const char *); - - if (APR_SUCCESS == md_acme_authz_set_load(store, group, md_name, &authz_set, p)) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz_set loaded for %s", md_name); - for (i = 0; i < authz_set->authzs->nelts; ++i) { - authz = APR_ARRAY_IDX(authz_set->authzs, i, const md_acme_authz_t*); - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz check %s", authz->domain); - if (authz->dir) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz purge %s", authz->dir); - md_store_purge(store, p, MD_SG_CHALLENGES, authz->dir); + + if (strchr(token, ':')) { + challenge = apr_pstrdup(p, token); + domain = strchr(challenge, ':'); + *domain = '\0'; domain++; + for (i = 0; i < (int)CHA_TYPES_LEN; ++i) { + if (!apr_strnatcasecmp(CHA_TYPES[i].name, challenge)) { + if (CHA_TYPES[i].teardown) { + return CHA_TYPES[i].teardown(store, domain, md, env, p); + } + break; } } } - return md_store_remove(store, group, md_name, MD_FN_AUTHZ, ptemp, 1); -} - -apr_status_t md_acme_authz_set_purge(md_store_t *store, md_store_group_t group, - apr_pool_t *p, const char *md_name) -{ - return md_util_pool_vdo(p_purge, store, p, group, md_name, NULL); + return APR_SUCCESS; } |