summaryrefslogtreecommitdiffstats
path: root/modules/md/md_acme_order.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--modules/md/md_acme_order.c562
1 files changed, 562 insertions, 0 deletions
diff --git a/modules/md/md_acme_order.c b/modules/md/md_acme_order.c
new file mode 100644
index 0000000..9e25e84
--- /dev/null
+++ b/modules/md/md_acme_order.c
@@ -0,0 +1,562 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_file_info.h>
+#include <apr_file_io.h>
+#include <apr_fnmatch.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_jws.h"
+#include "md_result.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_authz.h"
+#include "md_acme_order.h"
+
+
+md_acme_order_t *md_acme_order_create(apr_pool_t *p)
+{
+ md_acme_order_t *order;
+
+ order = apr_pcalloc(p, sizeof(*order));
+ order->p = p;
+ order->authz_urls = apr_array_make(p, 5, sizeof(const char *));
+ order->challenge_setups = apr_array_make(p, 5, sizeof(const char *));
+
+ return order;
+}
+
+/**************************************************************************************************/
+/* order conversion */
+
+#define MD_KEY_CHALLENGE_SETUPS "challenge-setups"
+
+static md_acme_order_st order_st_from_str(const char *s)
+{
+ if (s) {
+ if (!strcmp("valid", s)) {
+ return MD_ACME_ORDER_ST_VALID;
+ }
+ else if (!strcmp("invalid", s)) {
+ return MD_ACME_ORDER_ST_INVALID;
+ }
+ else if (!strcmp("ready", s)) {
+ return MD_ACME_ORDER_ST_READY;
+ }
+ else if (!strcmp("pending", s)) {
+ return MD_ACME_ORDER_ST_PENDING;
+ }
+ else if (!strcmp("processing", s)) {
+ return MD_ACME_ORDER_ST_PROCESSING;
+ }
+ }
+ return MD_ACME_ORDER_ST_PENDING;
+}
+
+static const char *order_st_to_str(md_acme_order_st status)
+{
+ switch (status) {
+ case MD_ACME_ORDER_ST_PENDING:
+ return "pending";
+ case MD_ACME_ORDER_ST_READY:
+ return "ready";
+ case MD_ACME_ORDER_ST_PROCESSING:
+ return "processing";
+ case MD_ACME_ORDER_ST_VALID:
+ return "valid";
+ case MD_ACME_ORDER_ST_INVALID:
+ return "invalid";
+ default:
+ return "invalid";
+ }
+}
+
+md_json_t *md_acme_order_to_json(md_acme_order_t *order, apr_pool_t *p)
+{
+ md_json_t *json = md_json_create(p);
+
+ if (order->url) {
+ md_json_sets(order->url, json, MD_KEY_URL, NULL);
+ }
+ md_json_sets(order_st_to_str(order->status), json, MD_KEY_STATUS, NULL);
+ md_json_setsa(order->authz_urls, json, MD_KEY_AUTHORIZATIONS, NULL);
+ md_json_setsa(order->challenge_setups, json, MD_KEY_CHALLENGE_SETUPS, NULL);
+ if (order->finalize) {
+ md_json_sets(order->finalize, json, MD_KEY_FINALIZE, NULL);
+ }
+ if (order->certificate) {
+ md_json_sets(order->certificate, json, MD_KEY_CERTIFICATE, NULL);
+ }
+ return json;
+}
+
+static void order_update_from_json(md_acme_order_t *order, md_json_t *json, apr_pool_t *p)
+{
+ if (!order->url && md_json_has_key(json, MD_KEY_URL, NULL)) {
+ order->url = md_json_dups(p, json, MD_KEY_URL, NULL);
+ }
+ order->status = order_st_from_str(md_json_gets(json, MD_KEY_STATUS, NULL));
+ if (md_json_has_key(json, MD_KEY_AUTHORIZATIONS, NULL)) {
+ md_json_dupsa(order->authz_urls, p, json, MD_KEY_AUTHORIZATIONS, NULL);
+ }
+ if (md_json_has_key(json, MD_KEY_CHALLENGE_SETUPS, NULL)) {
+ md_json_dupsa(order->challenge_setups, p, json, MD_KEY_CHALLENGE_SETUPS, NULL);
+ }
+ if (md_json_has_key(json, MD_KEY_FINALIZE, NULL)) {
+ order->finalize = md_json_dups(p, json, MD_KEY_FINALIZE, NULL);
+ }
+ if (md_json_has_key(json, MD_KEY_CERTIFICATE, NULL)) {
+ order->certificate = md_json_dups(p, json, MD_KEY_CERTIFICATE, NULL);
+ }
+}
+
+md_acme_order_t *md_acme_order_from_json(md_json_t *json, apr_pool_t *p)
+{
+ md_acme_order_t *order = md_acme_order_create(p);
+
+ order_update_from_json(order, json, p);
+ return order;
+}
+
+apr_status_t md_acme_order_add(md_acme_order_t *order, const char *authz_url)
+{
+ assert(authz_url);
+ if (md_array_str_index(order->authz_urls, authz_url, 0, 1) < 0) {
+ APR_ARRAY_PUSH(order->authz_urls, const char*) = apr_pstrdup(order->p, authz_url);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t md_acme_order_remove(md_acme_order_t *order, const char *authz_url)
+{
+ int i;
+
+ assert(authz_url);
+ i = md_array_str_index(order->authz_urls, authz_url, 0, 1);
+ if (i >= 0) {
+ order->authz_urls = md_array_str_remove(order->p, order->authz_urls, authz_url, 1);
+ return APR_SUCCESS;
+ }
+ return APR_ENOENT;
+}
+
+static apr_status_t add_setup_token(md_acme_order_t *order, const char *token)
+{
+ if (md_array_str_index(order->challenge_setups, token, 0, 1) < 0) {
+ APR_ARRAY_PUSH(order->challenge_setups, const char*) = apr_pstrdup(order->p, token);
+ }
+ return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* persistence */
+
+apr_status_t md_acme_order_load(struct md_store_t *store, md_store_group_t group,
+ const char *md_name, md_acme_order_t **pauthz_set,
+ apr_pool_t *p)
+{
+ apr_status_t rv;
+ md_json_t *json;
+ md_acme_order_t *authz_set;
+
+ rv = md_store_load_json(store, group, md_name, MD_FN_ORDER, &json, p);
+ if (APR_SUCCESS == rv) {
+ authz_set = md_acme_order_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_order_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_order_t *);
+ create = va_arg(ap, int);
+
+ json = md_acme_order_to_json(set, ptemp);
+ assert(json);
+ return md_store_save_json(store, ptemp, group, md_name, MD_FN_ORDER, json, create);
+}
+
+apr_status_t md_acme_order_save(struct md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *md_name,
+ md_acme_order_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_order_t *order;
+ md_store_group_t group;
+ const md_t *md;
+ const char *setup_token;
+ apr_table_t *env;
+ int i;
+
+ group = (md_store_group_t)va_arg(ap, int);
+ md = va_arg(ap, const md_t *);
+ env = va_arg(ap, apr_table_t *);
+
+ if (APR_SUCCESS == md_acme_order_load(store, group, md->name, &order, p)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "order loaded for %s", md->name);
+ for (i = 0; i < order->challenge_setups->nelts; ++i) {
+ setup_token = APR_ARRAY_IDX(order->challenge_setups, i, const char*);
+ if (setup_token) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "order teardown setup %s", setup_token);
+ md_acme_authz_teardown(store, setup_token, md, env, p);
+ }
+ }
+ }
+ return md_store_remove(store, group, md->name, MD_FN_ORDER, ptemp, 1);
+}
+
+apr_status_t md_acme_order_purge(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const md_t *md, apr_table_t *env)
+{
+ return md_util_pool_vdo(p_purge, store, p, group, md, env, NULL);
+}
+
+/**************************************************************************************************/
+/* ACMEv2 order requests */
+
+typedef struct {
+ apr_pool_t *p;
+ md_acme_order_t *order;
+ md_acme_t *acme;
+ const char *name;
+ apr_array_header_t *domains;
+ md_result_t *result;
+} order_ctx_t;
+
+#define ORDER_CTX_INIT(ctx, p, o, a, n, d, r) \
+ (ctx)->p = (p); (ctx)->order = (o); (ctx)->acme = (a); \
+ (ctx)->name = (n); (ctx)->domains = d; (ctx)->result = r
+
+static apr_status_t identifier_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ md_json_t *jid;
+
+ (void)baton;
+ jid = md_json_create(p);
+ md_json_sets("dns", jid, "type", NULL);
+ md_json_sets(value, jid, "value", NULL);
+ return md_json_setj(jid, json, NULL);
+}
+
+static apr_status_t on_init_order_register(md_acme_req_t *req, void *baton)
+{
+ order_ctx_t *ctx = baton;
+ md_json_t *jpayload;
+
+ jpayload = md_json_create(req->p);
+ md_json_seta(ctx->domains, identifier_to_json, NULL, jpayload, "identifiers", NULL);
+
+ return md_acme_req_body_init(req, jpayload);
+}
+
+static apr_status_t on_order_upd(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
+ md_json_t *body, void *baton)
+{
+ order_ctx_t *ctx = baton;
+ const char *location = apr_table_get(hdrs, "location");
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)acme;
+ (void)p;
+ if (!ctx->order) {
+ if (location) {
+ ctx->order = md_acme_order_create(ctx->p);
+ ctx->order->url = apr_pstrdup(ctx->p, location);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "new order at %s", location);
+ }
+ else {
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ctx->p, "new order, no location header");
+ goto out;
+ }
+ }
+
+ order_update_from_json(ctx->order, body, ctx->p);
+out:
+ return rv;
+}
+
+apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p,
+ const char *name, apr_array_header_t *domains)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+ ORDER_CTX_INIT(&ctx, p, NULL, acme, name, domains, NULL);
+ rv = md_acme_POST(acme, acme->api.v2.new_order, on_init_order_register, on_order_upd, NULL, NULL, &ctx);
+ *porder = (APR_SUCCESS == rv)? ctx.order : NULL;
+ return rv;
+}
+
+apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme,
+ md_result_t *result, apr_pool_t *p)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+ ORDER_CTX_INIT(&ctx, p, order, acme, NULL, NULL, result);
+ rv = md_acme_GET(acme, order->url, NULL, on_order_upd, NULL, NULL, &ctx);
+ if (APR_SUCCESS != rv && APR_SUCCESS != acme->last->status) {
+ md_result_dup(result, acme->last);
+ }
+ return rv;
+}
+
+static apr_status_t await_ready(void *baton, int attempt)
+{
+ order_ctx_t *ctx = baton;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)attempt;
+ if (APR_SUCCESS != (rv = md_acme_order_update(ctx->order, ctx->acme,
+ ctx->result, ctx->p))) goto out;
+ switch (ctx->order->status) {
+ case MD_ACME_ORDER_ST_READY:
+ case MD_ACME_ORDER_ST_PROCESSING:
+ case MD_ACME_ORDER_ST_VALID:
+ break;
+ case MD_ACME_ORDER_ST_PENDING:
+ rv = APR_EAGAIN;
+ break;
+ default:
+ rv = APR_EINVAL;
+ break;
+ }
+out:
+ return rv;
+}
+
+apr_status_t md_acme_order_await_ready(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ md_result_t *result, apr_pool_t *p)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+
+ md_result_activity_setn(result, "Waiting for order to become ready");
+ rv = md_util_try(await_ready, &ctx, 0, timeout, 0, 0, 1);
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+static apr_status_t await_valid(void *baton, int attempt)
+{
+ order_ctx_t *ctx = baton;
+ apr_status_t rv = APR_SUCCESS;
+
+ (void)attempt;
+ if (APR_SUCCESS != (rv = md_acme_order_update(ctx->order, ctx->acme,
+ ctx->result, ctx->p))) goto out;
+ switch (ctx->order->status) {
+ case MD_ACME_ORDER_ST_VALID:
+ md_result_set(ctx->result, APR_EINVAL, "ACME server order status is 'valid'.");
+ break;
+ case MD_ACME_ORDER_ST_PROCESSING:
+ rv = APR_EAGAIN;
+ break;
+ case MD_ACME_ORDER_ST_INVALID:
+ md_result_set(ctx->result, APR_EINVAL, "ACME server order status is 'invalid'.");
+ rv = APR_EINVAL;
+ break;
+ default:
+ rv = APR_EINVAL;
+ break;
+ }
+out:
+ return rv;
+}
+
+apr_status_t md_acme_order_await_valid(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ md_result_t *result, apr_pool_t *p)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+
+ md_result_activity_setn(result, "Waiting for finalized order to become valid");
+ rv = md_util_try(await_valid, &ctx, 0, timeout, 0, 0, 1);
+ md_result_log(result, MD_LOG_DEBUG);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* processing */
+
+apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme,
+ apr_array_header_t *challenge_types,
+ md_store_t *store, const md_t *md,
+ apr_table_t *env, md_result_t *result,
+ apr_pool_t *p)
+{
+ apr_status_t rv = APR_SUCCESS;
+ md_acme_authz_t *authz;
+ const char *url, *setup_token;
+ int i;
+
+ md_result_activity_printf(result, "Starting challenges for domains");
+ for (i = 0; i < order->authz_urls->nelts; ++i) {
+ url = APR_ARRAY_IDX(order->authz_urls, i, const char*);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: check AUTHZ at %s", md->name, url);
+
+ if (APR_SUCCESS != (rv = md_acme_authz_retrieve(acme, p, url, &authz))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: check authz for %s",
+ md->name, authz->domain);
+ goto leave;
+ }
+
+ switch (authz->state) {
+ case MD_ACME_AUTHZ_S_VALID:
+ break;
+
+ case MD_ACME_AUTHZ_S_PENDING:
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: authorization pending for %s",
+ md->name, authz->domain);
+ rv = md_acme_authz_respond(authz, acme, store, challenge_types,
+ md->pks,
+ md->acme_tls_1_domains, md,
+ env, p, &setup_token, result);
+ if (APR_SUCCESS != rv) {
+ goto leave;
+ }
+ add_setup_token(order, setup_token);
+ md_acme_order_save(store, p, MD_SG_STAGING, md->name, order, 0);
+ break;
+
+ case MD_ACME_AUTHZ_S_INVALID:
+ rv = APR_EINVAL;
+ if (authz->error_type) {
+ md_result_problem_set(result, rv, authz->error_type, authz->error_detail, NULL);
+ goto leave;
+ }
+ /* fall through */
+ default:
+ rv = APR_EINVAL;
+ md_result_printf(result, rv, "unexpected AUTHZ state %d for domain %s",
+ authz->state, authz->domain);
+ md_result_log(result, MD_LOG_ERR);
+ goto leave;
+ }
+ }
+leave:
+ return rv;
+}
+
+static apr_status_t check_challenges(void *baton, int attempt)
+{
+ order_ctx_t *ctx = baton;
+ const char *url;
+ md_acme_authz_t *authz;
+ apr_status_t rv = APR_SUCCESS;
+ int i;
+
+ for (i = 0; i < ctx->order->authz_urls->nelts; ++i) {
+ url = APR_ARRAY_IDX(ctx->order->authz_urls, i, const char*);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ctx->p, "%s: check AUTHZ at %s (attempt %d)",
+ ctx->name, url, attempt);
+
+ rv = md_acme_authz_retrieve(ctx->acme, ctx->p, url, &authz);
+ if (APR_SUCCESS == rv) {
+ switch (authz->state) {
+ case MD_ACME_AUTHZ_S_VALID:
+ md_result_printf(ctx->result, rv,
+ "domain authorization for %s is valid", authz->domain);
+ break;
+ case MD_ACME_AUTHZ_S_PENDING:
+ rv = APR_EAGAIN;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ctx->p,
+ "%s: status pending at %s", authz->domain, authz->url);
+ goto leave;
+ case MD_ACME_AUTHZ_S_INVALID:
+ rv = APR_EINVAL;
+ md_result_printf(ctx->result, rv,
+ "domain authorization for %s failed, CA considers "
+ "answer to challenge invalid%s.",
+ authz->domain, authz->error_type? "" : ", no error given");
+ md_result_log(ctx->result, MD_LOG_ERR);
+ goto leave;
+ default:
+ rv = APR_EINVAL;
+ md_result_printf(ctx->result, rv,
+ "domain authorization for %s failed with state %d",
+ authz->domain, authz->state);
+ md_result_log(ctx->result, MD_LOG_ERR);
+ goto leave;
+ }
+ }
+ else {
+ md_result_printf(ctx->result, rv, "authorization retrieval failed for domain %s",
+ authz->domain);
+ }
+ }
+leave:
+ return rv;
+}
+
+apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acme,
+ const md_t *md, apr_interval_time_t timeout,
+ md_result_t *result, apr_pool_t *p)
+{
+ order_ctx_t ctx;
+ apr_status_t rv;
+
+ ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+
+ md_result_activity_printf(result, "Monitoring challenge status for %s", md->name);
+ rv = md_util_try(check_challenges, &ctx, 0, timeout, 0, 0, 1);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: checked authorizations", md->name);
+ return rv;
+}
+