summaryrefslogtreecommitdiffstats
path: root/modules/md/md_acme_authz.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/md/md_acme_authz.c')
-rw-r--r--modules/md/md_acme_authz.c801
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, &notify_server))) {
+ (void)key_specs;
+ (void)env;
+ (void)acme_tls_1_domains;
+ (void)md;
+
+ if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_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, &notify_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, &notify_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, &notify_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;
}