summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES61
-rw-r--r--CMakeLists.txt4
-rw-r--r--modules/md/config2.m42
-rw-r--r--modules/md/md.h10
-rw-r--r--modules/md/md_acme.c172
-rw-r--r--modules/md/md_acme.h12
-rw-r--r--modules/md/md_acme_acct.c53
-rw-r--r--modules/md/md_acme_authz.c266
-rw-r--r--modules/md/md_acme_authz.h17
-rw-r--r--modules/md/md_acme_drive.c354
-rw-r--r--modules/md/md_acme_drive.h20
-rw-r--r--modules/md/md_acme_order.c7
-rw-r--r--modules/md/md_acmev1_drive.c189
-rw-r--r--modules/md/md_acmev1_drive.h27
-rw-r--r--modules/md/md_acmev2_drive.c26
-rw-r--r--modules/md/md_core.c16
-rw-r--r--modules/md/md_crypt.c496
-rw-r--r--modules/md/md_crypt.h48
-rw-r--r--modules/md/md_curl.c63
-rw-r--r--modules/md/md_event.c89
-rw-r--r--modules/md/md_event.h46
-rw-r--r--modules/md/md_http.c28
-rw-r--r--modules/md/md_http.h16
-rw-r--r--modules/md/md_json.c35
-rw-r--r--modules/md/md_json.h4
-rw-r--r--modules/md/md_ocsp.c21
-rw-r--r--modules/md/md_ocsp.h1
-rw-r--r--modules/md/md_reg.c217
-rw-r--r--modules/md/md_reg.h15
-rw-r--r--modules/md/md_result.c23
-rw-r--r--modules/md/md_result.h14
-rw-r--r--modules/md/md_status.c209
-rw-r--r--modules/md/md_status.h14
-rw-r--r--modules/md/md_store.c76
-rw-r--r--modules/md/md_store.h43
-rw-r--r--modules/md/md_store_fs.c5
-rw-r--r--modules/md/md_time.c12
-rw-r--r--modules/md/md_time.h5
-rw-r--r--modules/md/md_util.c40
-rw-r--r--modules/md/md_util.h4
-rw-r--r--modules/md/md_version.h4
-rw-r--r--modules/md/mod_md.c526
-rw-r--r--modules/md/mod_md.dsp8
-rw-r--r--modules/md/mod_md.h5
-rw-r--r--modules/md/mod_md_config.c100
-rw-r--r--modules/md/mod_md_config.h9
-rw-r--r--modules/md/mod_md_drive.c27
-rw-r--r--modules/md/mod_md_status.c90
48 files changed, 2254 insertions, 1275 deletions
diff --git a/CHANGES b/CHANGES
index 9cb04ee372..52933fcfd4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,67 @@
-*- coding: utf-8 -*-
Changes with Apache 2.5.1
+
+ *) mod_md: v2.4.0 with improvements and bugfixes
+ - MDPrivateKeys allows the specification of several types. Beside "RSA" plus
+ optional key lengths elliptic curves can be configured. This means you can
+ have multiple certificates for a Managed Domain with different key types.
+ With ```MDPrivateKeys secp384r1 rsa2048``` you get one ECDSA and one RSA
+ certificate and all modern client will use the shorter ECDSA, while older
+ client will get the RSA certificate.
+ Many thanks to @tlhackque who pushed and helped on this.
+ - Support added for MDomains consisting of a wildcard. Configuring
+ ```MDomain *.host.net``` will match all virtual hosts matching that pattern
+ and obtain one certificate for it (assuming you have 'dns-01' challenge
+ support configured). Addresses #239.
+ - Removed support for ACMEv1 servers. The only known installation used to
+ be Let's Encrypt which has disabled that version more than a year ago for
+ new accounts.
+ - Andreas Ulm (<https://github.com/root360-AndreasUlm>) implemented the
+ ```renewing``` call to ```MDMessageCmd``` that can deny a certificate
+ renewal attempt. This is useful in clustered installations, as
+ discussed in #233).
+ - New event ```challenge-setup:<type>:<domain>```, triggered when the
+ challenge data for a domain has been created. This is invoked before the
+ ACME server is told to check for it. The type is one of the ACME challenge
+ types. This is invoked for every DNS name in a MDomain.
+ - The max delay for retries has been raised to daily (this is like all
+ retries jittered somewhat to avoid repeats at fixed time of day).
+ - Certain error codes reported by the ACME server that indicate a problem
+ with the configured data now immediately switch to daily retries. For
+ example: if the ACME server rejects a contact email or a domain name,
+ frequent retries will most likely not solve the problem. But daily retries
+ still make sense as there might be an error at the server and un-supervised
+ certificate renewal is the goal. Refs #222.
+ - Test case and work around for domain names > 64 octets. Fixes #227.
+ When the first DNS name of an MD is longer than 63 octets, the certificate
+ request will not contain a CN field, but leave it up to the CA to choose one.
+ Currently, Lets Encrypt looks for a shorter name in the SAN list given and
+ fails the request if none is found. But it is really up to the CA (and what
+ browsers/libs accept here) and may change over the years. That is why
+ the decision is best made at the CA.
+ - Retry delays now have a random +/-[0-50]% modification applied to let
+ retries from several servers spread out more, should they have been
+ restarted at the same time of day.
+ - Fixed several places where the 'badNonce' return code from an ACME server
+ was not handled correctly. The test server 'pebble' simulates this behaviour
+ by default and helps nicely in verifying this behaviour. Thanks, pebble!
+ - Set the default `MDActivationDelay` to 0. This was confusing to users that
+ new certificates were deemed not usably before a day of delay. When clocks are
+ correct, using a new certificate right away should not pose a problem.
+ - When handling ACME authorization resources, the module no longer requires
+ the server to return a "Location" header, as was necessary in ACMEv1.
+ Fixes #216.
+ - Fixed a theoretical uninitialized read when testing for JSON error responses
+ from the ACME CA. Reported at <https://bz.apache.org/bugzilla/show_bug.cgi?id=64297>.
+ - ACME problem reports from CAs that include parameters in the Content-Type
+ header are handled correctly. (Previously, the problem text would not be
+ reported and retries could exist CA limits.)
+ - Account Update transactions to V2 CAs now use the correct POST-AS-GET method.
+ Previously, an empty JSON object was sent - which apparently LE accepted,
+ but others reject.
+ [Stefan Eissing, @tlhackque, Andreas Ulm]
+
*) mod_session: Improve session parsing. [Yann Yalvic]
*) mod_proxy_hcheck: Don't pile up health checks if the previous one did
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2ee5e17517..fe1fe09e5d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -504,7 +504,7 @@ SET(mod_md_extra_libs ${OPENSSL_LIBRARIES} ${CURL_LIBRARIES} ${JA
SET(mod_md_extra_sources
modules/md/md_acme.c modules/md/md_acme_acct.c
modules/md/md_acme_authz.c modules/md/md_acme_drive.c
- modules/md/md_acmev1_drive.c modules/md/md_acmev2_drive.c
+ modules/md/md_acmev2_drive.c modules/md/md_event.c
modules/md/md_acme_order.c modules/md/md_core.c
modules/md/md_curl.c modules/md/md_crypt.c
modules/md/md_http.c modules/md/md_json.c
@@ -515,7 +515,7 @@ SET(mod_md_extra_sources
modules/md/md_ocsp.c modules/md/md_util.c
modules/md/mod_md_config.c modules/md/mod_md_drive.c
modules/md/mod_md_os.c modules/md/mod_md_status.c
- modules/md/mod_md_ocsp.c
+ modules/md/mod_md_ocsp.c
)
SET(mod_optional_hook_export_extra_defines AP_DECLARE_EXPORT) # bogus reuse of core API prefix
SET(mod_proxy_extra_defines PROXY_DECLARE_EXPORT)
diff --git a/modules/md/config2.m4 b/modules/md/config2.m4
index 9517f997c1..b20ab3b45e 100644
--- a/modules/md/config2.m4
+++ b/modules/md/config2.m4
@@ -140,12 +140,12 @@ md_acme.lo dnl
md_acme_acct.lo dnl
md_acme_authz.lo dnl
md_acme_drive.lo dnl
-md_acmev1_drive.lo dnl
md_acmev2_drive.lo dnl
md_acme_order.lo dnl
md_core.lo dnl
md_curl.lo dnl
md_crypt.lo dnl
+md_event.lo dnl
md_http.lo dnl
md_json.lo dnl
md_jws.lo dnl
diff --git a/modules/md/md.h b/modules/md/md.h
index 182d00b484..d2b3af00b9 100644
--- a/modules/md/md.h
+++ b/modules/md/md.h
@@ -17,6 +17,8 @@
#ifndef mod_md_md_h
#define mod_md_md_h
+#include <apr_time.h>
+
#include "md_time.h"
#include "md_version.h"
@@ -80,10 +82,10 @@ struct md_t {
md_require_t require_https; /* Iff https: is required for this MD */
int renew_mode; /* mode of obtaining credentials */
- struct md_pkey_spec_t *pkey_spec;/* specification for generating new private keys */
+ struct md_pkeys_spec_t *pks; /* specification for generating private keys */
int must_staple; /* certificates should set the OCSP Must Staple extension */
- md_timeslice_t *renew_window; /* time before expiration that starts renewal */
- md_timeslice_t *warn_window; /* time before expiration that warnings are sent out */
+ md_timeslice_t *renew_window; /* time before expiration that starts renewal */
+ md_timeslice_t *warn_window; /* time before expiration that warnings are sent out */
const char *ca_url; /* url of CA certificate service */
const char *ca_proto; /* protocol used vs CA (e.g. ACME) */
@@ -125,6 +127,7 @@ struct md_t {
#define MD_KEY_CONTACT "contact"
#define MD_KEY_CONTACTS "contacts"
#define MD_KEY_CSR "csr"
+#define MD_KEY_CURVE "curve"
#define MD_KEY_DETAIL "detail"
#define MD_KEY_DISABLED "disabled"
#define MD_KEY_DIR "dir"
@@ -155,6 +158,7 @@ struct md_t {
#define MD_KEY_NAME "name"
#define MD_KEY_NEXT_RUN "next-run"
#define MD_KEY_NOTIFIED "notified"
+#define MD_KEY_NOTIFIED_RENEWED "notified-renewed"
#define MD_KEY_OCSP "ocsp"
#define MD_KEY_OCSPS "ocsps"
#define MD_KEY_ORDERS "orders"
diff --git a/modules/md/md_acme.c b/modules/md/md_acme.c
index e3702eb0ad..52fab0af1e 100644
--- a/modules/md/md_acme.c
+++ b/modules/md/md_acme.c
@@ -43,29 +43,30 @@ static const char *base_product= "-";
typedef struct acme_problem_status_t acme_problem_status_t;
struct acme_problem_status_t {
- const char *type;
- apr_status_t rv;
+ const char *type; /* the ACME error string */
+ apr_status_t rv; /* what Apache status code we give it */
+ int input_related; /* if error indicates wrong input value */
};
static acme_problem_status_t Problems[] = {
- { "acme:error:badCSR", APR_EINVAL },
- { "acme:error:badNonce", APR_EAGAIN },
- { "acme:error:badSignatureAlgorithm", APR_EINVAL },
- { "acme:error:invalidContact", APR_BADARG },
- { "acme:error:unsupportedContact", APR_EGENERAL },
- { "acme:error:malformed", APR_EINVAL },
- { "acme:error:rateLimited", APR_BADARG },
- { "acme:error:rejectedIdentifier", APR_BADARG },
- { "acme:error:serverInternal", APR_EGENERAL },
- { "acme:error:unauthorized", APR_EACCES },
- { "acme:error:unsupportedIdentifier", APR_BADARG },
- { "acme:error:userActionRequired", APR_EAGAIN },
- { "acme:error:badRevocationReason", APR_EINVAL },
- { "acme:error:caa", APR_EGENERAL },
- { "acme:error:dns", APR_EGENERAL },
- { "acme:error:connection", APR_EGENERAL },
- { "acme:error:tls", APR_EGENERAL },
- { "acme:error:incorrectResponse", APR_EGENERAL },
+ { "acme:error:badCSR", APR_EINVAL, 1 },
+ { "acme:error:badNonce", APR_EAGAIN, 0 },
+ { "acme:error:badSignatureAlgorithm", APR_EINVAL, 1 },
+ { "acme:error:invalidContact", APR_BADARG, 1 },
+ { "acme:error:unsupportedContact", APR_EGENERAL, 1 },
+ { "acme:error:malformed", APR_EINVAL, 1 },
+ { "acme:error:rateLimited", APR_BADARG, 0 },
+ { "acme:error:rejectedIdentifier", APR_BADARG, 1 },
+ { "acme:error:serverInternal", APR_EGENERAL, 0 },
+ { "acme:error:unauthorized", APR_EACCES, 0 },
+ { "acme:error:unsupportedIdentifier", APR_BADARG, 1 },
+ { "acme:error:userActionRequired", APR_EAGAIN, 0 },
+ { "acme:error:badRevocationReason", APR_EINVAL, 1 },
+ { "acme:error:caa", APR_EGENERAL, 0 },
+ { "acme:error:dns", APR_EGENERAL, 0 },
+ { "acme:error:connection", APR_EGENERAL, 0 },
+ { "acme:error:tls", APR_EGENERAL, 0 },
+ { "acme:error:incorrectResponse", APR_EGENERAL, 0 },
};
static apr_status_t problem_status_get(const char *type) {
@@ -86,6 +87,25 @@ static apr_status_t problem_status_get(const char *type) {
return APR_EGENERAL;
}
+int md_acme_problem_is_input_related(const char *problem) {
+ size_t i;
+
+ if (!problem) return 0;
+ if (strstr(problem, "urn:ietf:params:") == problem) {
+ problem += strlen("urn:ietf:params:");
+ }
+ else if (strstr(problem, "urn:") == problem) {
+ problem += strlen("urn:");
+ }
+
+ for(i = 0; i < (sizeof(Problems)/sizeof(Problems[0])); ++i) {
+ if (!apr_strnatcasecmp(problem, Problems[i].type)) {
+ return Problems[i].input_related;
+ }
+ }
+ return 0;
+}
+
/**************************************************************************************************/
/* acme requests */
@@ -101,13 +121,7 @@ static void req_update_nonce(md_acme_t *acme, apr_table_t *hdrs)
static apr_status_t http_update_nonce(const md_http_response_t *res, void *data)
{
- md_acme_t *acme = data;
- if (res->headers) {
- const char *nonce = apr_table_get(res->headers, "Replay-Nonce");
- if (nonce) {
- acme->nonce = apr_pstrdup(acme->p, nonce);
- }
- }
+ req_update_nonce(data, res->headers);
return APR_SUCCESS;
}
@@ -143,11 +157,6 @@ static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, co
return req;
}
-static apr_status_t acmev1_new_nonce(md_acme_t *acme)
-{
- return md_http_HEAD_perform(acme->http, acme->api.v1.new_reg, NULL, http_update_nonce, acme);
-}
-
static apr_status_t acmev2_new_nonce(md_acme_t *acme)
{
return md_http_HEAD_perform(acme->http, acme->api.v2.new_nonce, NULL, http_update_nonce, acme);
@@ -163,13 +172,15 @@ apr_status_t md_acme_init(apr_pool_t *p, const char *base, int init_ssl)
static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t *res)
{
const char *ctype;
- md_json_t *problem;
-
+ md_json_t *problem = NULL;
+ apr_status_t rv;
+
ctype = apr_table_get(req->resp_hdrs, "content-type");
+ ctype = md_util_parse_ct(res->req->pool, ctype);
if (ctype && !strcmp(ctype, "application/problem+json")) {
/* RFC 7807 */
- md_json_read_http(&problem, req->p, res);
- if (problem) {
+ rv = md_json_read_http(&problem, req->p, res);
+ if (rv == APR_SUCCESS && problem) {
const char *ptype, *pdetail;
req->resp_json = problem;
@@ -213,30 +224,6 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
/**************************************************************************************************/
/* ACME requests with nonce handling */
-static apr_status_t acmev1_req_init(md_acme_req_t *req, md_json_t *jpayload)
-{
- md_data_t payload;
-
- if (!req->acme->acct) {
- return APR_EINVAL;
- }
- if (jpayload) {
- payload.data = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
- if (!payload.data) {
- return APR_EINVAL;
- }
- }
- else {
- payload.data = "";
- }
-
- payload.len = strlen(payload.data);
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p,
- "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload.len, payload.data);
- return md_jws_sign(&req->req_json, req->p, &payload,
- req->prot_hdrs, req->acme->acct_key, NULL);
-}
-
static apr_status_t acmev2_req_init(md_acme_req_t *req, md_json_t *jpayload)
{
md_data_t payload;
@@ -366,8 +353,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
if (APR_SUCCESS != rv) goto leave;
}
- if (!strcmp("GET", req->method) && !req->on_init && !req->req_json
- && MD_ACME_VERSION_MAJOR(acme->version) > 1) {
+ if (!strcmp("GET", req->method) && !req->on_init && !req->req_json) {
/* See <https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.6.3>
* and <https://mailarchive.ietf.org/arch/msg/acme/sotffSQ0OWV-qQJodLwWYWcEVKI>
* and <https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380>
@@ -376,6 +362,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
* our HTTP client. */
req->method = "POST";
req->on_init = acmev2_GET_as_POST_init;
+ /*req->max_retries = 0; don't do retries on these "GET"s */
}
/* Besides GET/HEAD, we always need a fresh nonce */
@@ -391,9 +378,7 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
}
apr_table_set(req->prot_hdrs, "nonce", acme->nonce);
- if (MD_ACME_VERSION_MAJOR(acme->version) > 1) {
- apr_table_set(req->prot_hdrs, "url", req->url);
- }
+ apr_table_set(req->prot_hdrs, "url", req->url);
acme->nonce = NULL;
}
@@ -573,7 +558,7 @@ apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store,
rv = md_acme_acct_validate(acme, store, p);
}
else {
- /* account is from a nother server or, more likely, from another
+ /* account is from another server or, more likely, from another
* protocol endpoint on the same server */
rv = APR_ENOENT;
}
@@ -586,17 +571,7 @@ apr_status_t md_acme_save_acct(md_acme_t *acme, apr_pool_t *p, md_store_t *store
return md_acme_acct_save(store, p, acme, &acme->acct_id, acme->acct, acme->acct_key);
}
-static apr_status_t acmev1_POST_new_account(md_acme_t *acme,
- md_acme_req_init_cb *on_init,
- md_acme_req_json_cb *on_json,
- md_acme_req_res_cb *on_res,
- md_acme_req_err_cb *on_err,
- void *baton)
-{
- return md_acme_POST(acme, acme->api.v1.new_reg, on_init, on_json, on_res, on_err, baton);
-}
-
-static apr_status_t acmev2_POST_new_account(md_acme_t *acme,
+static apr_status_t acmev2_POST_new_account(md_acme_t *acme,
md_acme_req_init_cb *on_init,
md_acme_req_json_cb *on_json,
md_acme_req_res_cb *on_res,
@@ -620,7 +595,7 @@ apr_status_t md_acme_POST_new_account(md_acme_t *acme,
/* ACME setup */
apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
- const char *proxy_url)
+ const char *proxy_url, const char *ca_file)
{
md_acme_t *acme;
const char *err = NULL;
@@ -644,8 +619,9 @@ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
acme->user_agent = apr_psprintf(p, "%s mod_md/%s",
base_product, MOD_MD_VERSION);
acme->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
- acme->max_retries = 3;
-
+ acme->max_retries = 99;
+ acme->ca_file = ca_file;
+
if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: %s", url);
return APR_EINVAL;
@@ -706,21 +682,7 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data)
}
/* What have we got? */
- if ((s = md_json_dups(acme->p, json, "new-authz", NULL))) {
- acme->api.v1.new_authz = s;
- acme->api.v1.new_cert = md_json_dups(acme->p, json, "new-cert", NULL);
- acme->api.v1.new_reg = md_json_dups(acme->p, json, "new-reg", NULL);
- acme->api.v1.revoke_cert = md_json_dups(acme->p, json, "revoke-cert", NULL);
- if (acme->api.v1.new_authz && acme->api.v1.new_cert
- && acme->api.v1.new_reg && acme->api.v1.revoke_cert) {
- acme->version = MD_ACME_VERSION_1;
- }
- acme->ca_agreement = md_json_dups(acme->p, json, "meta", "terms-of-service", NULL);
- acme->new_nonce_fn = acmev1_new_nonce;
- acme->req_init_fn = acmev1_req_init;
- acme->post_new_account_fn = acmev1_POST_new_account;
- }
- else if ((s = md_json_dups(acme->p, json, "newAccount", NULL))) {
+ if ((s = md_json_dups(acme->p, json, "newAccount", NULL))) {
acme->api.v2.new_account = s;
acme->api.v2.new_order = md_json_dups(acme->p, json, "newOrder", NULL);
acme->api.v2.revoke_cert = md_json_dups(acme->p, json, "revokeCert", NULL);
@@ -729,7 +691,10 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data)
/* RFC 8555 only requires "directory" and "newNonce" resources.
* mod_md uses "newAccount" and "newOrder" so check for them.
* But mod_md does not use the "revokeCert" or "keyChange"
- * resources, so tolerate the absense of those keys. */
+ * resources, so tolerate the absense of those keys. In the
+ * future if mod_md implements revocation or key rollover then
+ * the use of those features should be predicated on the
+ * server's advertised capabilities. */
if (acme->api.v2.new_account
&& acme->api.v2.new_order
&& acme->api.v2.new_nonce) {
@@ -740,7 +705,19 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data)
acme->req_init_fn = acmev2_req_init;
acme->post_new_account_fn = acmev2_POST_new_account;
}
-
+ else if ((s = md_json_dups(acme->p, json, "new-authz", NULL))) {
+ acme->api.v1.new_authz = s;
+ acme->api.v1.new_cert = md_json_dups(acme->p, json, "new-cert", NULL);
+ acme->api.v1.new_reg = md_json_dups(acme->p, json, "new-reg", NULL);
+ acme->api.v1.revoke_cert = md_json_dups(acme->p, json, "revoke-cert", NULL);
+ if (acme->api.v1.new_authz && acme->api.v1.new_cert
+ && acme->api.v1.new_reg && acme->api.v1.revoke_cert) {
+ acme->version = MD_ACME_VERSION_1;
+ }
+ acme->ca_agreement = md_json_dups(acme->p, json, "meta", "terms-of-service", NULL);
+ /* we init that far, but will not use the v1 api */
+ }
+
if (MD_ACME_VERSION_UNKNOWN == acme->version) {
md_result_printf(result, APR_EINVAL,
"Unable to understand ACME server response from <%s>. "
@@ -770,6 +747,7 @@ apr_status_t md_acme_setup(md_acme_t *acme, md_result_t *result)
md_http_set_timeout_default(acme->http, apr_time_from_sec(10 * 60));
md_http_set_connect_timeout_default(acme->http, apr_time_from_sec(30));
md_http_set_stalling_default(acme->http, 10, apr_time_from_sec(30));
+ md_http_set_ca_file(acme->http, acme->ca_file);
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url);
diff --git a/modules/md/md_acme.h b/modules/md/md_acme.h
index f6af75fb04..004e7867ea 100644
--- a/modules/md/md_acme.h
+++ b/modules/md/md_acme.h
@@ -97,6 +97,7 @@ struct md_acme_t {
apr_pool_t *p;
const char *user_agent;
const char *proxy_url;
+ const char *ca_file;
const char *acct_id; /* local storage id account was loaded from or NULL */
struct md_acme_acct_t *acct; /* account at ACME server to use for requests */
@@ -104,7 +105,7 @@ struct md_acme_t {
int version; /* as detected from the server */
union {
- struct {
+ struct { /* obsolete */
const char *new_authz;
const char *new_cert;
const char *new_reg;
@@ -149,7 +150,7 @@ apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version, int init_s
* @param proxy_url optional url of a HTTP(S) proxy to use
*/
apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
- const char *proxy_url);
+ const char *proxy_url, const char *ca_file);
/**
* Contact the ACME server and retrieve its directory information.
@@ -288,4 +289,11 @@ apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *jpayloa
apr_status_t md_acme_protos_add(struct apr_hash_t *protos, apr_pool_t *p);
+/**
+ * Return != 0 iff the given problem identifier is an ACME error string
+ * indicating something is wrong with the input values, e.g. from our
+ * configuration.
+ */
+int md_acme_problem_is_input_related(const char *problem);
+
#endif /* md_acme_h */
diff --git a/modules/md/md_acme_acct.c b/modules/md/md_acme_acct.c
index 98443d2fd8..3c0f453605 100644
--- a/modules/md/md_acme_acct.c
+++ b/modules/md/md_acme_acct.c
@@ -406,18 +406,8 @@ typedef struct {
static apr_status_t on_init_acct_upd(md_acme_req_t *req, void *baton)
{
- md_json_t *jpayload;
-
(void)baton;
- jpayload = md_json_create(req->p);
- switch (MD_ACME_VERSION_MAJOR(req->acme->version)) {
- case 1:
- md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
- break;
- default:
- break;
- }
- return md_acme_req_body_init(req, jpayload);
+ return md_acme_req_body_init(req, NULL);
}
static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p,
@@ -495,23 +485,10 @@ static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
md_json_t *jpayload;
jpayload = md_json_create(req->p);
-
- switch (MD_ACME_VERSION_MAJOR(req->acme->version)) {
- case 1:
- md_json_sets("new-reg", jpayload, MD_KEY_RESOURCE, NULL);
- md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
- if (ctx->agreement) {
- md_json_sets(ctx->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
- }
- break;
- default:
- md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
- if (ctx->agreement) {
- md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
- }
- break;
+ md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
+ if (ctx->agreement) {
+ md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
}
-
return md_acme_req_body_init(req, jpayload);
}
@@ -616,15 +593,7 @@ static apr_status_t on_init_acct_del(md_acme_req_t *req, void *baton)
(void)baton;
jpayload = md_json_create(req->p);
- switch (MD_ACME_VERSION_MAJOR(req->acme->version)) {
- case 1:
- md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
- md_json_setb(1, jpayload, "delete", NULL);
- break;
- default:
- md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL);
- break;
- }
+ md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL);
return md_acme_req_body_init(req, jpayload);
}
@@ -653,16 +622,8 @@ 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);
- switch (MD_ACME_VERSION_MAJOR(req->acme->version)) {
- case 1:
- md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
- md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
- break;
- default:
- if (ctx->acme->acct->agreement) {
- md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
- }
- break;
+ if (ctx->acme->acct->agreement) {
+ md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
}
return md_acme_req_body_init(req, jpayload);
}
diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c
index b76a5a6ac6..883e319b48 100644
--- a/modules/md/md_acme_authz.c
+++ b/modules/md/md_acme_authz.c
@@ -76,57 +76,6 @@ 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);
-}
-
-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)
-{
- authz_req_ctx *ctx = baton;
- const char *location = apr_table_get(hdrs, "location");
- apr_status_t rv = APR_SUCCESS;
-
- (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->url = 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");
- }
- return rv;
-}
-
-apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, md_acme_t *acme,
- const char *domain, apr_pool_t *p)
-{
- apr_status_t rv;
- authz_req_ctx ctx;
-
- 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->api.v1.new_authz, on_init_authz, authz_created, NULL, NULL, &ctx);
-
- *pauthz = (APR_SUCCESS == rv)? ctx.authz : NULL;
- return rv;
-}
-
/**************************************************************************************************/
/* Update an existing authorization */
@@ -244,17 +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);
- if (MD_ACME_VERSION_MAJOR(req->acme->version) <= 1) {
- md_json_sets(MD_KEY_CHALLENGE, jpayload, MD_KEY_RESOURCE, NULL);
- }
- if (ctx->challenge->key_authz) {
- md_json_sets(ctx->challenge->key_authz, jpayload, MD_KEY_KEYAUTHZ, NULL);
- }
-
return md_acme_req_body_init(req, jpayload);
}
@@ -298,19 +240,21 @@ 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_array_header_t *acme_tls_1_domains,
- apr_table_t *env, apr_pool_t *p)
+ md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_table_t *env, md_result_t *result, apr_pool_t *p)
{
const char *data;
apr_status_t rv;
int notify_server;
- (void)key_spec;
+ (void)key_specs;
(void)env;
(void)acme_tls_1_domains;
+ (void)mdomain;
+
if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))) {
goto out;
}
@@ -318,14 +262,20 @@ static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t
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);
+ 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);
+ md_result_holler(result, event, p);
/* 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);
@@ -336,20 +286,26 @@ out:
return rv;
}
+void tls_alpn01_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn )
+{
+ *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_alpn_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_array_header_t *acme_tls_1_domains,
- apr_table_t *env, apr_pool_t *p)
+ md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_table_t *env, md_result_t *result, apr_pool_t *p)
{
- md_cert_t *cha_cert;
- md_pkey_t *cha_key;
const char *acme_id, *token;
apr_status_t rv;
int notify_server;
md_data_t data;
-
+ int i;
+
(void)env;
+ (void)mdomain;
if (md_array_str_index(acme_tls_1_domains, authz->domain, 0, 0) < 0) {
rv = APR_ENOTIMPL;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
@@ -360,48 +316,69 @@ static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_auth
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_TLSALPN01_CERT,
- 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 challenge key",
- authz->domain);
- 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 procotol "acme-tls/1" with this certificate.
- */
- MD_DATA_SET_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 cert",
- authz->domain);
- goto out;
- }
-
- acme_id = apr_psprintf(p, "critical,DER:04:20:%s", token);
- 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 cert",
- authz->domain);
- 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 procotol "acme-tls/1" with this certificate.
+ */
+ MD_DATA_SET_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 (APR_SUCCESS == (rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_PKEY,
- MD_SV_PKEY, (void*)cha_key, 0))) {
- rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_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;
}
- 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);
+ md_result_holler(result, event, p);
/* 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);
@@ -414,9 +391,9 @@ out:
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_pkey_spec_t *key_spec,
- apr_array_header_t *acme_tls_1_domains,
- apr_table_t *env, apr_pool_t *p)
+ md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_table_t *env, md_result_t *result, apr_pool_t *p)
{
const char *token;
const char * const *argv;
@@ -425,11 +402,12 @@ static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *
int exit_code, notify_server;
authz_req_ctx ctx;
md_data_t data;
-
+ const char *event;
+
(void)store;
- (void)key_spec;
+ (void)key_specs;
(void)acme_tls_1_domains;
-
+
dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
if (!dns01_cmd) {
rv = APR_ENOTIMPL;
@@ -445,29 +423,35 @@ static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *
MD_DATA_SET_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",
- authz->domain);
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create dns-01 token for %s",
+ mdomain, 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))) {
+ if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code))) {
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
- "%s: dns-01 setup command failed to execute", authz->domain);
+ "%s: dns-01 setup command failed to execute for %s", mdomain, 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", authz->domain, exit_code);
+ "%s: dns-01 setup command returns %d for %s", mdomain, exit_code, authz->domain);
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", authz->domain);
+ /* Raise event that challenge data has been set up before we tell the
+ ACME server. Clusters might want to distribute it. */
+ event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_DNS01, authz->domain);
+ md_result_holler(result, event, p);
+ /* challenge is setup, tell ACME server so it may (re)try verification */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 setup succeeded for %s",
+ mdomain, authz->domain);
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);
@@ -476,7 +460,7 @@ out:
return rv;
}
-static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain,
+static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, const char *mdomain,
apr_table_t *env, apr_pool_t *p)
{
const char * const *argv;
@@ -489,35 +473,37 @@ static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain,
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", domain);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 command not set for %s",
+ mdomain, domain);
goto out;
}
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) {
+ if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code)) || exit_code) {
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
- "%s: dns-01 teardown command failed (exit code=%d)",
- domain, exit_code);
+ "%s: dns-01 teardown command failed (exit code=%d) for %s",
+ mdomain, exit_code, domain);
}
out:
return rv;
}
-static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain,
+static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, const char *mdomain,
apr_table_t *env, apr_pool_t *p)
{
(void)env;
+ (void)mdomain;
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_pkey_spec_t *key_spec,
- apr_array_header_t *acme_tls_1_domains,
- apr_table_t *env, apr_pool_t *p);
+ md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_table_t *env, md_result_t *result, apr_pool_t *p);
-typedef apr_status_t cha_teardown(md_store_t *store, const char *domain,
+typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, const char *mdomain,
apr_table_t *env, apr_pool_t *p);
typedef struct {
@@ -565,8 +551,8 @@ 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_array_header_t *acme_tls_1_domains,
+ apr_array_header_t *challenges, md_pkeys_spec_t *key_specs,
+ apr_array_header_t *acme_tls_1_domains, const char *mdomain,
apr_table_t *env, apr_pool_t *p, const char **psetup_token,
md_result_t *result)
{
@@ -603,18 +589,18 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s
if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) {
md_result_activity_printf(result, "Setting up challenge '%s' for domain %s",
fctx.accepted->type, authz->domain);
- rv = CHA_TYPES[i].setup(fctx.accepted, authz, acme, store, key_spec,
- acme_tls_1_domains, env, p);
+ rv = CHA_TYPES[i].setup(fctx.accepted, authz, acme, store, key_specs,
+ acme_tls_1_domains, mdomain, env, result, p);
if (APR_SUCCESS == rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "%s: set up challenge '%s'",
- authz->domain, fctx.accepted->type);
- challenge_setup = CHA_TYPES[i].name;
+ "%s: set up challenge '%s' for %s",
+ authz->domain, fctx.accepted->type, mdomain);
+ challenge_setup = CHA_TYPES[i].name;
goto out;
}
- md_result_printf(result, rv, "error setting up challenge '%s', "
+ md_result_printf(result, rv, "error setting up challenge '%s' for %s, "
"for domain %s, looking for other option",
- fctx.accepted->type, authz->domain);
+ fctx.accepted->type, authz->domain, mdomain);
md_result_log(result, MD_LOG_INFO);
}
}
@@ -648,8 +634,8 @@ out:
return rv;
}
-apr_status_t md_acme_authz_teardown(struct md_store_t *store,
- const char *token, apr_table_t *env, apr_pool_t *p)
+apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *token,
+ const char *mdomain, apr_table_t *env, apr_pool_t *p)
{
char *challenge, *domain;
int i;
@@ -661,7 +647,7 @@ apr_status_t md_acme_authz_teardown(struct md_store_t *store,
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, env, p);
+ return CHA_TYPES[i].teardown(store, domain, mdomain, env, p);
}
break;
}
diff --git a/modules/md/md_acme_authz.h b/modules/md/md_acme_authz.h
index fe1abe5528..90193af14c 100644
--- a/modules/md/md_acme_authz.h
+++ b/modules/md/md_acme_authz.h
@@ -56,31 +56,24 @@ struct md_acme_authz_t {
};
#define MD_FN_HTTP01 "acme-http-01.txt"
-#define MD_FN_TLSSNI01_CERT "acme-tls-sni-01.cert.pem"
-#define MD_FN_TLSSNI01_PKEY "acme-tls-sni-01.key.pem"
-#define MD_FN_TLSALPN01_CERT "acme-tls-alpn-01.cert.pem"
-#define MD_FN_TLSALPN01_PKEY "acme-tls-alpn-01.key.pem"
+void tls_alpn01_fnames(apr_pool_t *p, struct md_pkey_spec_t *kspec, char **keyfn, char **certfn );
md_acme_authz_t *md_acme_authz_create(apr_pool_t *p);
-/* authz interaction with ACME server */
-apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, struct md_acme_t *acme,
- const char *domain, apr_pool_t *p);
-
-apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url,
+apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url,
md_acme_authz_t **pauthz);
apr_status_t md_acme_authz_update(md_acme_authz_t *authz, struct md_acme_t *acme, apr_pool_t *p);
apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, struct md_acme_t *acme,
struct md_store_t *store, apr_array_header_t *challenges,
- struct md_pkey_spec_t *key_spec,
- apr_array_header_t *acme_tls_1_domains,
+ struct md_pkeys_spec_t *key_spec,
+ apr_array_header_t *acme_tls_1_domains, const char *mdomain,
struct apr_table_t *env,
apr_pool_t *p, const char **setup_token,
struct md_result_t *result);
apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *setup_token,
- struct apr_table_t *env, apr_pool_t *p);
+ const char *mdomain, struct apr_table_t *env, apr_pool_t *p);
#endif /* md_acme_authz_h */
diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c
index b88da757c5..bc5eb2b2a0 100644
--- a/modules/md/md_acme_drive.c
+++ b/modules/md/md_acme_drive.c
@@ -40,7 +40,6 @@
#include "md_acme_order.h"
#include "md_acme_drive.h"
-#include "md_acmev1_drive.h"
#include "md_acmev2_drive.h"
/**************************************************************************************************/
@@ -140,7 +139,7 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
/* ACMEv1 allowed registration of accounts without accepted Terms-of-Service.
* ACMEv2 requires it. Fail early in this case with a meaningful error message.
*/
- if (!md->ca_agreement && MD_ACME_VERSION_MAJOR(ad->acme->version) > 1) {
+ if (!md->ca_agreement) {
md_result_printf(result, APR_EINVAL,
"the CA requires you to accept the terms-of-service "
"as specified in <%s>. "
@@ -187,10 +186,10 @@ static void get_up_link(md_proto_driver_t *d, apr_table_t *headers)
{
md_acme_driver_t *ad = d->baton;
- ad->next_up_link = md_link_find_relation(headers, d->p, "up");
- if (ad->next_up_link) {
+ ad->chain_up_link = md_link_find_relation(headers, d->p, "up");
+ if (ad->chain_up_link) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p,
- "server reports up link as %s", ad->next_up_link);
+ "server reports up link as %s", ad->chain_up_link);
}
}
@@ -201,6 +200,7 @@ static apr_status_t add_http_certs(apr_array_header_t *chain, apr_pool_t *p,
const char *ct;
ct = apr_table_get(res->headers, "Content-Type");
+ ct = md_util_parse_ct(res->req->pool, ct);
if (ct && !strcmp("application/x-pkcs7-mime", ct)) {
/* this looks like a root cert and we do not want those in our chain */
goto out;
@@ -225,10 +225,10 @@ static apr_status_t on_add_cert(md_acme_t *acme, const md_http_response_t *res,
int count;
(void)acme;
- count = ad->certs->nelts;
- if (APR_SUCCESS == (rv = add_http_certs(ad->certs, d->p, res))) {
+ count = ad->cred->chain->nelts;
+ if (APR_SUCCESS == (rv = add_http_certs(ad->cred->chain, d->p, res))) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%d certs parsed",
- ad->certs->nelts - count);
+ ad->cred->chain->nelts - count);
get_up_link(d, res->headers);
}
return rv;
@@ -276,9 +276,6 @@ static apr_status_t on_init_csr_req(md_acme_req_t *req, void *baton)
md_json_t *jpayload;
jpayload = md_json_create(req->p);
- if (MD_ACME_VERSION_MAJOR(req->acme->version) == 1) {
- md_json_sets("new-cert", jpayload, MD_KEY_RESOURCE, NULL);
- }
md_json_sets(ad->csr_der_64, jpayload, MD_KEY_CSR, NULL);
return md_acme_req_body_init(req, jpayload);
@@ -308,11 +305,11 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void
}
/* Check if it already was sent with this response */
- ad->next_up_link = NULL;
+ ad->chain_up_link = NULL;
if (APR_SUCCESS == (rv = md_cert_read_http(&cert, d->p, res))) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "cert parsed");
- apr_array_clear(ad->certs);
- APR_ARRAY_PUSH(ad->certs, md_cert_t*) = cert;
+ apr_array_clear(ad->cred->chain);
+ APR_ARRAY_PUSH(ad->cred->chain, md_cert_t*) = cert;
get_up_link(d, res->headers);
}
else if (APR_STATUS_IS_ENOENT(rv)) {
@@ -329,6 +326,7 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void
/**
* Pre-Req: all domains have been validated by the ACME server, e.g. all have AUTHZ
* resources that have status 'valid'
+ * - acme_driver->cred keeps the credentials to setup (key spec)
* - Setup private key, if not already there
* - Generate a CSR with org, contact, etc
* - Optionally enable must-staple OCSP extension
@@ -339,39 +337,39 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void
* - GET cert chain
* - store cert chain
*/
-apr_status_t md_acme_drive_setup_certificate(md_proto_driver_t *d, md_result_t *result)
+apr_status_t md_acme_drive_setup_cred_chain(md_proto_driver_t *d, md_result_t *result)
{
md_acme_driver_t *ad = d->baton;
+ md_pkey_spec_t *spec;
md_pkey_t *privkey;
apr_status_t rv;
md_result_activity_printf(result, "Finalizing order for %s", ad->md->name);
-
- rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, &privkey, d->p);
+
+ assert(ad->cred);
+ spec = ad->cred->spec;
+
+ rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, spec, &privkey, d->p);
if (APR_STATUS_IS_ENOENT(rv)) {
- if (APR_SUCCESS == (rv = md_pkey_gen(&privkey, d->p, d->md->pkey_spec))) {
- rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, d->md->name, privkey, 1);
+ if (APR_SUCCESS == (rv = md_pkey_gen(&privkey, d->p, spec))) {
+ rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, d->md->name, spec, privkey, 1);
}
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate privkey", d->md->name);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "%s: generate %s privkey", d->md->name, md_pkey_spec_name(spec));
}
if (APR_SUCCESS != rv) goto leave;
- md_result_activity_printf(result, "Creating CSR for %s", d->md->name);
+ md_result_activity_printf(result, "Creating %s CSR", md_pkey_spec_name(spec));
rv = md_cert_req_create(&ad->csr_der_64, d->md->name, ad->domains,
ad->md->must_staple, privkey, d->p);
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create CSR", d->md->name);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create %s CSR",
+ d->md->name, md_pkey_spec_name(spec));
if (APR_SUCCESS != rv) goto leave;
+
+ md_result_activity_printf(result, "Submitting %s CSR to CA", md_pkey_spec_name(spec));
+ assert(ad->order->finalize);
+ rv = md_acme_POST(ad->acme, ad->order->finalize, on_init_csr_req, NULL, csr_req, NULL, d);
- md_result_activity_printf(result, "Submitting CSR to CA for %s", d->md->name);
- switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) {
- case 1:
- rv = md_acme_POST(ad->acme, ad->acme->api.v1.new_cert, on_init_csr_req, NULL, csr_req, NULL, d);
- break;
- default:
- assert(ad->order->finalize);
- rv = md_acme_POST(ad->acme, ad->order->finalize, on_init_csr_req, NULL, csr_req, NULL, d);
- break;
- }
leave:
md_acme_report_result(ad->acme, rv, result);
return rv;
@@ -389,12 +387,13 @@ static apr_status_t on_add_chain(md_acme_t *acme, const md_http_response_t *res,
(void)acme;
ct = apr_table_get(res->headers, "Content-Type");
+ ct = md_util_parse_ct(res->req->pool, ct);
if (ct && !strcmp("application/x-pkcs7-mime", ct)) {
/* root cert most likely, end it here */
return APR_SUCCESS;
}
- if (APR_SUCCESS == (rv = add_http_certs(ad->certs, d->p, res))) {
+ if (APR_SUCCESS == (rv = add_http_certs(ad->cred->chain, d->p, res))) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain cert parsed");
get_up_link(d, res->headers);
}
@@ -408,21 +407,26 @@ static apr_status_t get_chain(void *baton, int attempt)
const char *prev_link = NULL;
apr_status_t rv = APR_SUCCESS;
- while (APR_SUCCESS == rv && ad->certs->nelts < 10) {
- int nelts = ad->certs->nelts;
+ while (APR_SUCCESS == rv && ad->cred->chain->nelts < 10) {
+ int nelts = ad->cred->chain->nelts;
- if (ad->next_up_link && (!prev_link || strcmp(prev_link, ad->next_up_link))) {
- prev_link = ad->next_up_link;
+ if (ad->chain_up_link && (!prev_link || strcmp(prev_link, ad->chain_up_link))) {
+ prev_link = ad->chain_up_link;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
- "next chain cert at %s", ad->next_up_link);
- rv = md_acme_GET(ad->acme, ad->next_up_link, NULL, NULL, on_add_chain, NULL, d);
+ "next chain cert at %s", ad->chain_up_link);
+ rv = md_acme_GET(ad->acme, ad->chain_up_link, NULL, NULL, on_add_chain, NULL, d);
- if (APR_SUCCESS == rv && nelts == ad->certs->nelts) {
+ if (APR_SUCCESS == rv && nelts == ad->cred->chain->nelts) {
break;
}
+ else if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
+ "error retrieving certificate from %s", ad->chain_up_link);
+ return rv;
+ }
}
- else if (ad->certs->nelts <= 1) {
+ else if (ad->cred->chain->nelts <= 1) {
/* This cannot be the complete chain (no one signs new web certs with their root)
* and we did not see a "Link: ...rel=up", so we do not know how to continue. */
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
@@ -436,7 +440,7 @@ static apr_status_t get_chain(void *baton, int attempt)
}
}
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
- "got chain with %d certs (%d. attempt)", ad->certs->nelts, attempt);
+ "got chain with %d certs (%d. attempt)", ad->cred->chain->nelts, attempt);
return rv;
}
@@ -446,16 +450,16 @@ static apr_status_t ad_chain_retrieve(md_proto_driver_t *d)
apr_status_t rv;
/* This may be called repeatedly and needs to progress. The relevant state is in
- * ad->certs the certificate chain, starting with the new cert for the md
+ * ad->cred->chain the certificate chain, starting with the new cert for the md
* ad->order->certificate the url where ACME offers us the new md certificate. This may
* be a single one or even the complete chain
- * ad->next_up_link in case the last certificate retrieval did not end the chain,
+ * ad->chain_up_link in case the last certificate retrieval did not end the chain,
* the link header with relation "up" gives us the location
* for the next cert in the chain
*/
- if (md_array_is_empty(ad->certs)) {
+ if (md_array_is_empty(ad->cred->chain)) {
/* Need to start at the order */
- ad->next_up_link = NULL;
+ ad->chain_up_link = NULL;
if (!ad->order) {
rv = APR_EGENERAL;
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
@@ -487,6 +491,8 @@ out:
static apr_status_t acme_driver_preload_init(md_proto_driver_t *d, md_result_t *result)
{
md_acme_driver_t *ad;
+ md_credentials_t *cred;
+ int i;
md_result_set(result, APR_SUCCESS, NULL);
@@ -498,7 +504,15 @@ static apr_status_t acme_driver_preload_init(md_proto_driver_t *d, md_result_t *
ad->authz_monitor_timeout = apr_time_from_sec(30);
ad->cert_poll_timeout = apr_time_from_sec(30);
ad->ca_challenges = apr_array_make(d->p, 3, sizeof(const char*));
- ad->certs = apr_array_make(d->p, 5, sizeof(md_cert_t*));
+
+ /* We want to obtain credentials (key+certificate) for every key spec in this MD */
+ ad->creds = apr_array_make(d->p, md_pkeys_spec_count(d->md->pks), sizeof(md_credentials_t*));
+ for (i = 0; i < md_pkeys_spec_count(d->md->pks); ++i) {
+ cred = apr_pcalloc(d->p, sizeof(*cred));
+ cred->spec = md_pkeys_spec_get(d->md->pks, i);
+ cred->chain = apr_array_make(d->p, 5, sizeof(md_cert_t*));
+ APR_ARRAY_PUSH(ad->creds, md_credentials_t*) = cred;
+ }
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, result->status, d->p,
"%s: init_base driver", d->md->name);
@@ -570,7 +584,7 @@ static apr_status_t acme_driver_init(md_proto_driver_t *d, md_result_t *result)
dis_http? " The http: challenge 'http-01' is disabled because the server seems not reachable on public port 80." : "",
dis_https? " The https: challenge 'tls-alpn-01' is disabled because the server seems not reachable on public port 443." : "",
dis_alpn_acme? " The https: challenge 'tls-alpn-01' is disabled because the Protocols configuration does not include the 'acme-tls/1' protocol." : "",
- dis_dns? "The DNS challenge 'dns-01' is disabled because the directive 'MDChallengeDns01' is not configured." : ""
+ dis_dns? " The DNS challenge 'dns-01' is disabled because the directive 'MDChallengeDns01' is not configured." : ""
);
goto leave;
}
@@ -584,15 +598,47 @@ leave:
/**************************************************************************************************/
/* ACME staging */
+static apr_status_t load_missing_creds(md_proto_driver_t *d)
+{
+ md_acme_driver_t *ad = d->baton;
+ md_credentials_t *cred;
+ apr_array_header_t *chain;
+ int i, complete;
+ apr_status_t rv;
+
+ complete = 1;
+ for (i = 0; i < ad->creds->nelts; ++i) {
+ rv = APR_SUCCESS;
+ cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
+ if (!cred->pkey) {
+ rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, cred->spec, &cred->pkey, d->p);
+ }
+ if (APR_SUCCESS == rv && md_array_is_empty(cred->chain)) {
+ rv = md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, cred->spec, &chain, d->p);
+ if (APR_SUCCESS == rv) {
+ apr_array_cat(cred->chain, chain);
+ }
+ }
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "%s: credentials staged for %s certificate",
+ d->md->name, md_pkey_spec_name(cred->spec));
+ }
+ else {
+ complete = 0;
+ }
+ }
+ return complete? APR_SUCCESS : APR_EAGAIN;
+}
+
static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
{
md_acme_driver_t *ad = d->baton;
int reset_staging = d->reset;
apr_status_t rv = APR_SUCCESS;
- apr_time_t now;
- apr_array_header_t *staged_certs;
+ apr_time_t now, t, t2;
+ md_credentials_t *cred;
char ts[APR_RFC822_DATE_LEN];
- int first = 0;
+ int i, first = 0;
if (md_log_is_level(d->p, MD_LOG_DEBUG)) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, "
@@ -640,27 +686,15 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
goto out;
}
- if (ad->md) {
- const char *keyfile, *certfile;
-
- rv = md_reg_get_cred_files(&keyfile, &certfile, d->reg, MD_SG_STAGING, d->md, d->p);
- if (APR_SUCCESS == rv) {
- if (md_array_is_empty(ad->certs)
- && APR_SUCCESS == md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, &staged_certs, d->p)) {
- apr_array_cat(ad->certs, staged_certs);
- }
- if (!md_array_is_empty(ad->certs)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: all data staged", d->md->name);
- rv = APR_SUCCESS;
- goto ready;
- }
- }
+ if (ad->md && APR_SUCCESS == load_missing_creds(d)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: all credentials staged", d->md->name);
+ goto ready;
}
/* Need to renew */
md_result_activity_printf(result, "Contacting ACME server for %s at %s",
d->md->name, d->md->ca_url);
- if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url, d->proxy_url))) {
+ if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url, d->proxy_url, d->ca_file))) {
md_result_printf(result, rv, "setup ACME communications");
md_result_log(result, MD_LOG_ERR);
goto out;
@@ -688,51 +722,63 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
ad->domains = md_dns_make_minimal(d->p, ad->md->domains);
}
- if (md_array_is_empty(ad->certs)
- && APR_SUCCESS == md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, &staged_certs, d->p)) {
- apr_array_cat(ad->certs, staged_certs);
- }
-
- if (md_array_is_empty(ad->certs)) {
- md_result_activity_printf(result, "Driving ACME protocol for renewal of %s", d->md->name);
- /* The process of setting up challenges and verifying domain
- * names differs between ACME versions. */
- switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) {
- case 1:
- rv = md_acmev1_drive_renew(ad, d, result);
- break;
- case 2:
- rv = md_acmev2_drive_renew(ad, d, result);
- break;
- default:
- md_result_printf(result, APR_EINVAL,
- "ACME server has unknown major version %d (%x)",
- MD_ACME_VERSION_MAJOR(ad->acme->version), ad->acme->version);
- rv = result->status;
- break;
- }
- if (APR_SUCCESS != rv) goto out;
- }
-
- if (md_array_is_empty(ad->certs) || ad->next_up_link) {
- md_result_activity_printf(result, "Retrieving certificate chain for %s", d->md->name);
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p,
- "%s: retrieving certificate chain", d->md->name);
- rv = ad_chain_retrieve(d);
- if (APR_SUCCESS != rv) {
- md_result_printf(result, rv, "Unable to retrieve certificate chain.");
- goto out;
- }
-
- if (!md_array_is_empty(ad->certs)) {
- rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, d->md->name, ad->certs, 0);
- if (APR_SUCCESS != rv) {
- md_result_printf(result, rv, "Saving new certificate chain.");
- goto out;
+ if (APR_SUCCESS != load_missing_creds(d)) {
+ for (i = 0; i < ad->creds->nelts; ++i) {
+ ad->cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
+ if (!ad->cred->pkey || md_array_is_empty(ad->cred->chain)) {
+ md_result_activity_printf(result, "Driving ACME to renew %s certificate for %s",
+ md_pkey_spec_name(ad->cred->spec),d->md->name);
+ /* The process of setting up challenges and verifying domain
+ * names differs between ACME versions. */
+ switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) {
+ case 1:
+ md_result_printf(result, APR_EINVAL,
+ "ACME server speaks version 1, an obsolete version of the ACME "
+ "protocol that is no longer supported.");
+ rv = result->status;
+ break;
+ default:
+ /* In principle, we only know ACME version 2. But we assume
+ that a new protocol which announces a directory with all members
+ from version 2 will act backward compatible.
+ This is, of course, an assumption...
+ */
+ rv = md_acmev2_drive_renew(ad, d, result);
+ break;
+ }
+ if (APR_SUCCESS != rv) goto out;
+
+ if (md_array_is_empty(ad->cred->chain) || ad->chain_up_link) {
+ md_result_activity_printf(result, "Retrieving %s certificate chain for %s",
+ md_pkey_spec_name(ad->cred->spec), d->md->name);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p,
+ "%s: retrieving %s certificate chain",
+ d->md->name, md_pkey_spec_name(ad->cred->spec));
+ rv = ad_chain_retrieve(d);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "Unable to retrieve %s certificate chain.",
+ md_pkey_spec_name(ad->cred->spec));
+ goto out;
+ }
+
+ if (!md_array_is_empty(ad->cred->chain)) {
+ rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, d->md->name,
+ ad->cred->spec, ad->cred->chain, 0);
+ if (APR_SUCCESS != rv) {
+ md_result_printf(result, rv, "Saving new %s certificate chain.",
+ md_pkey_spec_name(ad->cred->spec));
+ goto out;
+ }
+ }
+ }
+
+ /* Clean up the order, so the next pkey spec sets up a new one */
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env);
}
}
}
+
/* As last step, cleanup any order we created so that challenge data
* may be removed asap. */
md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env);
@@ -742,43 +788,44 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
ready:
md_result_activity_setn(result, NULL);
/* we should have the complete cert chain now */
- assert(!md_array_is_empty(ad->certs));
- assert(ad->certs->nelts > 1);
-
+ assert(APR_SUCCESS == load_missing_creds(d));
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
- "%s: certificate ready, activation delay set to %s",
+ "%s: certificates ready, activation delay set to %s",
d->md->name, md_duration_format(d->p, d->activation_delay));
/* determine when it should be activated */
- md_result_delay_set(result, md_cert_get_not_before(APR_ARRAY_IDX(ad->certs, 0, md_cert_t*)));
+ t = apr_time_now();
+ for (i = 0; i < ad->creds->nelts; ++i) {
+ cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
+ t2 = md_cert_get_not_before(APR_ARRAY_IDX(cred->chain, 0, md_cert_t*));
+ if (t2 > t) t = t2;
+ }
+ md_result_delay_set(result, t);
/* If the existing MD is complete and un-expired, delay the activation
* to 24 hours after new cert is valid (if there is enough time left), so
* that cients with skewed clocks do not see a problem. */
now = apr_time_now();
if (d->md->state == MD_S_COMPLETE) {
- const md_pubcert_t *pub;
apr_time_t valid_until, delay_activation;
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
- "%s: state is COMPLETE, checking existing certificate", d->md->name);
- if (APR_SUCCESS == md_reg_get_pubcert(&pub, d->reg, d->md, d->p)) {
- valid_until = md_cert_get_not_after(APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*));
- if (d->activation_delay < 0) {
- /* special simulation for test case */
- if (first) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
- "%s: delay ready_at to now+1s", d->md->name);
- md_result_delay_set(result, apr_time_now() + apr_time_from_sec(1));
- }
+ "%s: state is COMPLETE, checking existing certificates", d->md->name);
+ valid_until = md_reg_valid_until(d->reg, d->md, d->p);
+ if (d->activation_delay < 0) {
+ /* special simulation for test case */
+ if (first) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
+ "%s: delay ready_at to now+1s", d->md->name);
+ md_result_delay_set(result, apr_time_now() + apr_time_from_sec(1));
}
- else if (valid_until > now) {
- delay_activation = d->activation_delay;
- if (delay_activation > (valid_until - now)) {
- delay_activation = (valid_until - now);
- }
- md_result_delay_set(result, result->ready_at + delay_activation);
+ }
+ else if (valid_until > now) {
+ delay_activation = d->activation_delay;
+ if (delay_activation > (valid_until - now)) {
+ delay_activation = (valid_until - now);
}
+ md_result_delay_set(result, result->ready_at + delay_activation);
}
}
@@ -815,10 +862,13 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro
const char *name, md_result_t *result)
{
apr_status_t rv;
- md_pkey_t *privkey, *acct_key;
+ md_pkey_t *acct_key;
md_t *md;
- apr_array_header_t *pubcert;
+ md_pkey_spec_t *pkspec;
+ md_credentials_t *creds;
+ apr_array_header_t *all_creds;
struct md_acme_acct_t *acct;
+ int i;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name);
/* Load data from MD_SG_STAGING and save it into "load_group".
@@ -833,15 +883,22 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro
md_result_set(result, rv, "loading staged md.json");
goto leave;
}
- if (APR_SUCCESS != (rv = md_pkey_load(d->store, MD_SG_STAGING, name, &privkey, d->p))) {
- md_result_set(result, rv, "loading staged privkey.pem");
- goto leave;
- }
- if (APR_SUCCESS != (rv = md_pubcert_load(d->store, MD_SG_STAGING, name, &pubcert, d->p))) {
- md_result_set(result, rv, "loading staged pubcert.pem");
- goto leave;
+
+ all_creds = apr_array_make(d->p, 5, sizeof(md_credentials_t*));
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ pkspec = md_pkeys_spec_get(md->pks, i);
+ if (APR_SUCCESS != (rv = md_creds_load(d->store, MD_SG_STAGING, name, pkspec, &creds, d->p))) {
+ md_result_printf(result, rv, "loading staged credentials #%d", i);
+ goto leave;
+ }
+ if (!creds->chain) {
+ rv = APR_ENOENT;
+ md_result_printf(result, rv, "no certificate in staged credentials #%d", i);
+ goto leave;
+ }
+ APR_ARRAY_PUSH(all_creds, md_credentials_t*) = creds;
}
-
+
/* See if staging holds a new or modified account data */
rv = md_acme_acct_load(&acct, &acct_key, d->store, MD_SG_STAGING, name, d->p);
if (APR_STATUS_IS_ENOENT(rv)) {
@@ -885,7 +942,7 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro
}
}
- if (APR_SUCCESS != (rv = md_acme_create(&acme, d->p, md->ca_url, d->proxy_url))) {
+ if (APR_SUCCESS != (rv = md_acme_create(&acme, d->p, md->ca_url, d->proxy_url, d->ca_file))) {
md_result_set(result, rv, "error setting up acme");
goto leave;
}
@@ -902,14 +959,15 @@ static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_gro
md_result_set(result, rv, "writing md.json");
goto leave;
}
- if (APR_SUCCESS != (rv = md_pubcert_save(d->store, d->p, load_group, name, pubcert, 1))) {
- md_result_set(result, rv, "writing pubcert.pem");
- goto leave;
- }
- if (APR_SUCCESS != (rv = md_pkey_save(d->store, d->p, load_group, name, privkey, 1))) {
- md_result_set(result, rv, "writing privkey.pem");
- goto leave;
+
+ for (i = 0; i < all_creds->nelts; ++i) {
+ creds = APR_ARRAY_IDX(all_creds, i, md_credentials_t*);
+ if (APR_SUCCESS != (rv = md_creds_save(d->store, d->p, load_group, name, creds, 1))) {
+ md_result_printf(result, rv, "writing credentials #%d", i);
+ goto leave;
+ }
}
+
md_result_set(result, APR_SUCCESS, "saved staged data successfully");
leave:
diff --git a/modules/md/md_acme_drive.h b/modules/md/md_acme_drive.h
index 5111408c8c..88761fab95 100644
--- a/modules/md/md_acme_drive.h
+++ b/modules/md/md_acme_drive.h
@@ -18,23 +18,25 @@
struct apr_array_header_t;
struct md_acme_order_t;
+struct md_credentials_t;
struct md_result_t;
typedef struct md_acme_driver_t {
md_proto_driver_t *driver;
void *sub_driver;
- int complete;
-
- md_pkey_t *privkey; /* the new private key */
- apr_array_header_t *certs; /* the certifiacte chain, starting with the new one */
- const char *next_up_link; /* where the next chain cert is */
-
md_acme_t *acme;
md_t *md;
struct apr_array_header_t *domains;
-
apr_array_header_t *ca_challenges;
+
+ int complete;
+ apr_array_header_t *creds; /* the new md_credentials_t */
+
+ struct md_credentials_t *cred; /* credentials currently being processed */
+ const char *chain_up_link; /* Link header "up" from last chain retrieval,
+ needs to be followed */
+
struct md_acme_order_t *order;
apr_interval_time_t authz_monitor_timeout;
@@ -45,8 +47,8 @@ typedef struct md_acme_driver_t {
apr_status_t md_acme_drive_set_acct(struct md_proto_driver_t *d,
struct md_result_t *result);
-apr_status_t md_acme_drive_setup_certificate(struct md_proto_driver_t *d,
- struct md_result_t *result);
+apr_status_t md_acme_drive_setup_cred_chain(struct md_proto_driver_t *d,
+ struct md_result_t *result);
apr_status_t md_acme_drive_cert_poll(struct md_proto_driver_t *d, int only_once);
#endif /* md_acme_drive_h */
diff --git a/modules/md/md_acme_order.c b/modules/md/md_acme_order.c
index 974334a79c..5dde962afa 100644
--- a/modules/md/md_acme_order.c
+++ b/modules/md/md_acme_order.c
@@ -241,7 +241,7 @@ static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_li
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, env, p);
+ md_acme_authz_teardown(store, setup_token, md_name, env, p);
}
}
}
@@ -445,7 +445,7 @@ apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *a
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, 0, p, "%s: check authz for %s",
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: check authz for %s",
md->name, authz->domain);
goto leave;
}
@@ -456,7 +456,8 @@ apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *a
case MD_ACME_AUTHZ_S_PENDING:
rv = md_acme_authz_respond(authz, acme, store, challenge_types,
- md->pkey_spec, md->acme_tls_1_domains,
+ md->pks,
+ md->acme_tls_1_domains, md->name,
env, p, &setup_token, result);
if (APR_SUCCESS != rv) {
goto leave;
diff --git a/modules/md/md_acmev1_drive.c b/modules/md/md_acmev1_drive.c
deleted file mode 100644
index 027d494b4a..0000000000
--- a/modules/md/md_acmev1_drive.c
+++ /dev/null
@@ -1,189 +0,0 @@
-/* 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 <stdlib.h>
-
-#include <apr_lib.h>
-#include <apr_strings.h>
-#include <apr_buckets.h>
-#include <apr_hash.h>
-#include <apr_uri.h>
-
-#include "md.h"
-#include "md_crypt.h"
-#include "md_json.h"
-#include "md_jws.h"
-#include "md_http.h"
-#include "md_log.h"
-#include "md_result.h"
-#include "md_reg.h"
-#include "md_store.h"
-#include "md_util.h"
-
-#include "md_acme.h"
-#include "md_acme_acct.h"
-#include "md_acme_authz.h"
-#include "md_acme_order.h"
-
-#include "md_acme_drive.h"
-#include "md_acmev1_drive.h"
-
-/**************************************************************************************************/
-/* authz/challenge setup */
-
-/**
- * Pre-Req: we have an account for the ACME server that has accepted the current license agreement
- * For each domain in MD:
- * - check if there already is a valid AUTHZ resource
- * - if not, create an AUTHZ resource with challenge data
- */
-static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result)
-{
- md_acme_driver_t *ad = d->baton;
- apr_status_t rv;
- md_t *md = ad->md;
- const char *url;
- md_acme_authz_t *authz;
- apr_array_header_t *domains_covered;
- int i;
- int changed = 0;
-
- assert(ad->md);
- assert(ad->acme);
-
- md_result_activity_printf(result, "Setup order resource for %s", ad->md->name);
-
- /* For each domain in MD: AUTHZ setup
- * if an AUTHZ resource is known, check if it is still valid
- * if known AUTHZ resource is not valid, remove, goto 4.1.1
- * if no AUTHZ available, create a new one for the domain, store it
- */
- rv = md_acme_order_load(d->store, MD_SG_STAGING, md->name, &ad->order, d->p);
- if (!ad->order || APR_STATUS_IS_ENOENT(rv)) {
- ad->order = md_acme_order_create(d->p);
- rv = APR_SUCCESS;
- }
- else if (APR_SUCCESS != rv) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: loading authz data", md->name);
- md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md->name, d->env);
- return APR_EAGAIN;
- }
-
- /* Retrieve all known authz from ACME server and check status etc. */
- domains_covered = apr_array_make(d->p, 5, sizeof(const char *));
-
- for (i = 0; i < ad->order->authz_urls->nelts;) {
- url = APR_ARRAY_IDX(ad->order->authz_urls, i, const char*);
- rv = md_acme_authz_retrieve(ad->acme, d->p, url, &authz);
- if (APR_SUCCESS == rv) {
- if (md_array_str_index(ad->domains, authz->domain, 0, 0) < 0) {
- md_acme_order_remove(ad->order, url);
- changed = 1;
- continue;
- }
-
- APR_ARRAY_PUSH(domains_covered, const char *) = authz->domain;
- ++i;
- }
- else if (APR_STATUS_IS_ENOENT(rv)) {
- md_acme_order_remove(ad->order, url);
- changed = 1;
- continue;
- }
- else {
- goto leave;
- }
- }
-
- /* Do we have authz urls for all domains? If not, register a new one */
- for (i = 0; i < ad->domains->nelts && APR_SUCCESS == rv; ++i) {
- const char *domain = APR_ARRAY_IDX(ad->domains, i, const char *);
-
- if (md_array_str_index(domains_covered, domain, 0, 0) < 0) {
- md_result_activity_printf(result, "Creating authz resource for %s", domain);
- rv = md_acme_authz_register(&authz, ad->acme, domain, d->p);
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: created authz for %s (last problem: %s)",
- md->name, domain, ad->acme->last->problem);
- if (APR_SUCCESS != rv) goto leave;
- rv = md_acme_order_add(ad->order, authz->url);
- changed = 1;
- }
- }
-
- if (changed) {
- rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING, md->name, ad->order, 0);
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: saved", md->name);
- }
-
-leave:
- md_acme_report_result(ad->acme, rv, result);
- return rv;
-}
-
-apr_status_t md_acmev1_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, md_result_t *result)
-{
- apr_status_t rv = APR_SUCCESS;
- const char *required;
-
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: (ACMEv1) need certificate", d->md->name);
-
- /* Chose (or create) and ACME account to use */
- if (APR_SUCCESS != (rv = md_acme_drive_set_acct(d, result))) goto leave;
-
- /* Check that the account agreed to the terms-of-service, otherwise
- * requests for new authorizations are denied. ToS may change during the
- * lifetime of an account */
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p,
- "%s: (ACMEv1) check Tems-of-Service agreement", d->md->name);
-
- rv = md_acme_check_agreement(ad->acme, d->p, ad->md->ca_agreement, &required);
- if (APR_STATUS_IS_INCOMPLETE(rv) && required) {
- /* The CA wants the user to agree to Terms-of-Services. Until the user
- * has reconfigured and restarted the server, this MD cannot be
- * driven further */
- ad->md->state = MD_S_MISSING_INFORMATION;
- md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
- md_result_printf(result, rv,
- "the CA requires you to accept the terms-of-service as specified in <%s>. "
- "Please read the document that you find at that URL and, if you agree to "
- "the conditions, configure \"MDCertificateAgreement accepted\" "
- "in your Apache. Then (graceful) restart the server to activate.",
- required);
- goto leave;
- }
- else if (APR_SUCCESS != rv) goto leave;
-
- if (!md_array_is_empty(ad->certs)) goto leave;
-
- rv = ad_setup_order(d, result);
- if (APR_SUCCESS != rv) goto leave;
-
- rv = md_acme_order_start_challenges(ad->order, ad->acme, ad->ca_challenges,
- d->store, d->md, d->env, result, d->p);
- if (APR_SUCCESS != rv) goto leave;
-
- rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md,
- ad->authz_monitor_timeout, result, d->p);
- if (APR_SUCCESS != rv) goto leave;
-
- rv = md_acme_drive_setup_certificate(d, result);
-
-leave:
- md_result_log(result, MD_LOG_DEBUG);
- return result->status;
-}
-
diff --git a/modules/md/md_acmev1_drive.h b/modules/md/md_acmev1_drive.h
deleted file mode 100644
index 9dfa72981a..0000000000
--- a/modules/md/md_acmev1_drive.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* Copyright 2019 greenbytes GmbH (https://www.greenbytes.de)
- *
- * Licensed 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.
- */
-
-#ifndef md_acmev1_drive_h
-#define md_acmev1_drive_h
-
-struct md_acme_driver_t;
-struct md_proto_driver_t;
-struct md_result_t;
-
-apr_status_t md_acmev1_drive_renew(struct md_acme_driver_t *ad,
- struct md_proto_driver_t *d,
- struct md_result_t *result);
-
-#endif /* md_acmev1_drive_h */
diff --git a/modules/md/md_acmev2_drive.c b/modules/md/md_acmev2_drive.c
index 342e60a90f..40add6f565 100644
--- a/modules/md/md_acmev2_drive.c
+++ b/modules/md/md_acmev2_drive.c
@@ -101,7 +101,7 @@ apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m
rv = md_acme_drive_set_acct(d, result);
if (APR_SUCCESS != rv) goto leave;
- if (!md_array_is_empty(ad->certs)) goto leave;
+ if (!md_array_is_empty(ad->cred->chain)) goto leave;
/* ACMEv2 strategy:
* 1. load an md_acme_order_t from STAGING, if present
@@ -141,22 +141,26 @@ apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m
rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md,
ad->authz_monitor_timeout, result, d->p);
if (APR_SUCCESS != rv) goto leave;
-
- rv = md_acme_order_await_ready(ad->order, ad->acme, d->md,
+
+ rv = md_acme_order_await_ready(ad->order, ad->acme, d->md,
ad->authz_monitor_timeout, result, d->p);
if (APR_SUCCESS != rv) goto leave;
-
- rv = md_acme_drive_setup_certificate(d, result);
- if (APR_SUCCESS != rv) goto leave;
-
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: finalized order", d->md->name);
-
+
+ if (MD_ACME_ORDER_ST_READY == ad->order->status) {
+ rv = md_acme_drive_setup_cred_chain(d, result);
+ if (APR_SUCCESS != rv) goto leave;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: finalized order", d->md->name);
+ }
+
rv = md_acme_order_await_valid(ad->order, ad->acme, d->md,
ad->authz_monitor_timeout, result, d->p);
if (APR_SUCCESS != rv) goto leave;
- if (ad->order->certificate) goto leave;
- md_result_set(result, APR_EINVAL, "Order valid, but certifiate url is missing.");
+ if (!ad->order->certificate) {
+ md_result_set(result, APR_EINVAL, "Order valid, but certifiate url is missing.");
+ goto leave;
+ }
+ md_result_set(result, APR_SUCCESS, NULL);
leave:
md_result_log(result, MD_LOG_DEBUG);
diff --git a/modules/md/md_core.c b/modules/md/md_core.c
index 5d59b3d593..f4b8ebd052 100644
--- a/modules/md/md_core.c
+++ b/modules/md/md_core.c
@@ -33,7 +33,10 @@
int md_contains(const md_t *md, const char *domain, int case_sensitive)
{
- return md_array_str_index(md->domains, domain, 0, case_sensitive) >= 0;
+ if (md_array_str_index(md->domains, domain, 0, case_sensitive) >= 0) {
+ return 1;
+ }
+ return md_dns_domains_match(md->domains, domain);
}
const char *md_common_name(const md_t *md1, const md_t *md2)
@@ -207,6 +210,7 @@ md_t *md_copy(apr_pool_t *p, const md_t *src)
md->ca_challenges = apr_array_copy(p, src->ca_challenges);
}
md->acme_tls_1_domains = apr_array_copy(p, src->acme_tls_1_domains);
+ md->pks = md_pkeys_spec_clone(p, src->pks);
}
return md;
}
@@ -223,7 +227,7 @@ md_t *md_clone(apr_pool_t *p, const md_t *src)
md->must_staple = src->must_staple;
md->renew_mode = src->renew_mode;
md->domains = md_array_str_compact(p, src->domains, 0);
- md->pkey_spec = src->pkey_spec;
+ md->pks = md_pkeys_spec_clone(p, src->pks);
md->renew_window = src->renew_window;
md->warn_window = src->warn_window;
md->contacts = md_array_str_clone(p, src->contacts);
@@ -260,8 +264,8 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
md_json_sets(md->ca_proto, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
md_json_sets(md->ca_url, json, MD_KEY_CA, MD_KEY_URL, NULL);
md_json_sets(md->ca_agreement, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
- if (md->pkey_spec) {
- md_json_setj(md_pkey_spec_to_json(md->pkey_spec, p), json, MD_KEY_PKEY, NULL);
+ if (!md_pkeys_spec_is_empty(md->pks)) {
+ md_json_setj(md_pkeys_spec_to_json(md->pks, p), json, MD_KEY_PKEY, NULL);
}
md_json_setl(md->state, json, MD_KEY_STATE, NULL);
md_json_setl(md->renew_mode, json, MD_KEY_RENEW_MODE, NULL);
@@ -306,8 +310,8 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
md->ca_proto = md_json_dups(p, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
md->ca_url = md_json_dups(p, json, MD_KEY_CA, MD_KEY_URL, NULL);
md->ca_agreement = md_json_dups(p, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
- if (md_json_has_key(json, MD_KEY_PKEY, MD_KEY_TYPE, NULL)) {
- md->pkey_spec = md_pkey_spec_from_json(md_json_getj(json, MD_KEY_PKEY, NULL), p);
+ if (md_json_has_key(json, MD_KEY_PKEY, NULL)) {
+ md->pks = md_pkeys_spec_from_json(md_json_getj(json, MD_KEY_PKEY, NULL), p);
}
md->state = (md_state_t)md_json_getl(json, MD_KEY_STATE, NULL);
if (MD_S_EXPIRED_DEPRECATED == md->state) md->state = MD_S_COMPLETE;
diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c
index 55155975de..237a85df3d 100644
--- a/modules/md/md_crypt.c
+++ b/modules/md/md_crypt.c
@@ -35,6 +35,7 @@
#include "md_json.h"
#include "md_log.h"
#include "md_http.h"
+#include "md_time.h"
#include "md_util.h"
/* getpid for *NIX */
@@ -190,8 +191,10 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton)
size = (int)ctx->pass_len;
}
memcpy(buf, ctx->pass_phrase, (size_t)size);
+ } else {
+ return 0;
}
- return ctx->pass_len;
+ return size;
}
/**************************************************************************************************/
@@ -258,10 +261,93 @@ apr_time_t md_asn1_generalized_time_get(void *ASN1_GENERALIZEDTIME)
return md_asn1_time_get(ASN1_GENERALIZEDTIME);
}
+/**************************************************************************************************/
+/* OID/NID things */
+
+static int get_nid(const char *num, const char *sname, const char *lname)
+{
+ /* Funny API, an OID for a feature might be configured or
+ * maybe not. In the second case, we need to add it. But adding
+ * when it already is there is an error... */
+ int nid = OBJ_txt2nid(num);
+ if (NID_undef == nid) {
+ nid = OBJ_create(num, sname, lname);
+ }
+ return nid;
+}
+
+#define MD_GET_NID(x) get_nid(MD_OID_##x##_NUM, MD_OID_##x##_SNAME, MD_OID_##x##_LNAME)
/**************************************************************************************************/
/* private keys */
+md_pkeys_spec_t *md_pkeys_spec_make(apr_pool_t *p)
+{
+ md_pkeys_spec_t *pks;
+
+ pks = apr_pcalloc(p, sizeof(*pks));
+ pks->p = p;
+ pks->specs = apr_array_make(p, 2, sizeof(md_pkey_spec_t*));
+ return pks;
+}
+
+void md_pkeys_spec_add(md_pkeys_spec_t *pks, md_pkey_spec_t *spec)
+{
+ APR_ARRAY_PUSH(pks->specs, md_pkey_spec_t*) = spec;
+}
+
+void md_pkeys_spec_add_default(md_pkeys_spec_t *pks)
+{
+ md_pkey_spec_t *spec;
+
+ spec = apr_pcalloc(pks->p, sizeof(*spec));
+ spec->type = MD_PKEY_TYPE_DEFAULT;
+ md_pkeys_spec_add(pks, spec);
+}
+
+int md_pkeys_spec_contains_rsa(md_pkeys_spec_t *pks)
+{
+ md_pkey_spec_t *spec;
+ int i;
+ for (i = 0; i < pks->specs->nelts; ++i) {
+ spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*);
+ if (MD_PKEY_TYPE_RSA == spec->type) return 1;
+ }
+ return 0;
+}
+
+void md_pkeys_spec_add_rsa(md_pkeys_spec_t *pks, unsigned int bits)
+{
+ md_pkey_spec_t *spec;
+
+ spec = apr_pcalloc(pks->p, sizeof(*spec));
+ spec->type = MD_PKEY_TYPE_RSA;
+ spec->params.rsa.bits = bits;
+ md_pkeys_spec_add(pks, spec);
+}
+
+int md_pkeys_spec_contains_ec(md_pkeys_spec_t *pks, const char *curve)
+{
+ md_pkey_spec_t *spec;
+ int i;
+ for (i = 0; i < pks->specs->nelts; ++i) {
+ spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*);
+ if (MD_PKEY_TYPE_EC == spec->type
+ && !apr_strnatcasecmp(curve, spec->params.ec.curve)) return 1;
+ }
+ return 0;
+}
+
+void md_pkeys_spec_add_ec(md_pkeys_spec_t *pks, const char *curve)
+{
+ md_pkey_spec_t *spec;
+
+ spec = apr_pcalloc(pks->p, sizeof(*spec));
+ spec->type = MD_PKEY_TYPE_EC;
+ spec->params.ec.curve = apr_pstrdup(pks->p, curve);
+ md_pkeys_spec_add(pks, spec);
+}
+
md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p)
{
md_json_t *json = md_json_create(p);
@@ -276,6 +362,12 @@ md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p)
md_json_setl((long)spec->params.rsa.bits, json, MD_KEY_BITS, NULL);
}
break;
+ case MD_PKEY_TYPE_EC:
+ md_json_sets("EC", json, MD_KEY_TYPE, NULL);
+ if (spec->params.ec.curve) {
+ md_json_sets(spec->params.ec.curve, json, MD_KEY_CURVE, NULL);
+ }
+ break;
default:
md_json_sets("Unsupported", json, MD_KEY_TYPE, NULL);
break;
@@ -284,6 +376,27 @@ md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p)
return json;
}
+static apr_status_t spec_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ md_json_t *jspec;
+
+ (void)baton;
+ jspec = md_pkey_spec_to_json((md_pkey_spec_t*)value, p);
+ return md_json_setj(jspec, json, NULL);
+}
+
+md_json_t *md_pkeys_spec_to_json(const md_pkeys_spec_t *pks, apr_pool_t *p)
+{
+ md_json_t *j;
+
+ if (pks->specs->nelts == 1) {
+ return md_pkey_spec_to_json(md_pkeys_spec_get(pks, 0), p);
+ }
+ j = md_json_create(p);
+ md_json_seta(pks->specs, spec_to_json, (void*)pks, j, "specs", NULL);
+ return md_json_getj(j, "specs", NULL);
+}
+
md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p)
{
md_pkey_spec_t *spec = apr_pcalloc(p, sizeof(*spec));
@@ -305,29 +418,161 @@ md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p)
spec->params.rsa.bits = MD_PKEY_RSA_BITS_DEF;
}
}
+ else if (!apr_strnatcasecmp("EC", s)) {
+ spec->type = MD_PKEY_TYPE_EC;
+ s = md_json_gets(json, MD_KEY_CURVE, NULL);
+ if (s) {
+ spec->params.ec.curve = apr_pstrdup(p, s);
+ }
+ else {
+ spec->params.ec.curve = NULL;
+ }
+ }
}
return spec;
}
-int md_pkey_spec_eq(md_pkey_spec_t *spec1, md_pkey_spec_t *spec2)
+static apr_status_t spec_from_json(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ (void)baton;
+ *pvalue = md_pkey_spec_from_json(json, p);
+ return APR_SUCCESS;
+}
+
+md_pkeys_spec_t *md_pkeys_spec_from_json(struct md_json_t *json, apr_pool_t *p)
+{
+ md_pkeys_spec_t *pks;
+ md_pkey_spec_t *spec;
+
+ pks = md_pkeys_spec_make(p);
+ if (md_json_is(MD_JSON_TYPE_ARRAY, json, NULL)) {
+ md_json_geta(pks->specs, spec_from_json, pks, json, NULL);
+ }
+ else {
+ spec = md_pkey_spec_from_json(json, p);
+ md_pkeys_spec_add(pks, spec);
+ }
+ return pks;
+}
+
+static int pkey_spec_eq(md_pkey_spec_t *s1, md_pkey_spec_t *s2)
{
- if (spec1 == spec2) {
+ if (s1 == s2) {
return 1;
}
- if (spec1 && spec2 && spec1->type == spec2->type) {
- switch (spec1->type) {
+ if (s1 && s2 && s1->type == s2->type) {
+ switch (s1->type) {
case MD_PKEY_TYPE_DEFAULT:
return 1;
case MD_PKEY_TYPE_RSA:
- if (spec1->params.rsa.bits == spec2->params.rsa.bits) {
+ if (s1->params.rsa.bits == s2->params.rsa.bits) {
return 1;
}
break;
+ case MD_PKEY_TYPE_EC:
+ if (s1->params.ec.curve == s2->params.ec.curve) {
+ return 1;
+ }
+ else if (!s1->params.ec.curve || !s2->params.ec.curve) {
+ return 0;
+ }
+ return !strcmp(s1->params.ec.curve, s2->params.ec.curve);
+ }
+ }
+ return 0;
+}
+
+int md_pkeys_spec_eq(md_pkeys_spec_t *pks1, md_pkeys_spec_t *pks2)
+{
+ int i;
+
+ if (pks1 == pks2) {
+ return 1;
+ }
+ if (pks1 && pks2 && pks1->specs->nelts == pks2->specs->nelts) {
+ for(i = 0; i < pks1->specs->nelts; ++i) {
+ if (!pkey_spec_eq(APR_ARRAY_IDX(pks1->specs, i, md_pkey_spec_t *),
+ APR_ARRAY_IDX(pks2->specs, i, md_pkey_spec_t *))) {
+ return 0;
+ }
}
+ return 1;
}
return 0;
}
+static md_pkey_spec_t *pkey_spec_clone(apr_pool_t *p, md_pkey_spec_t *spec)
+{
+ md_pkey_spec_t *nspec;
+
+ nspec = apr_pcalloc(p, sizeof(*nspec));
+ nspec->type = spec->type;
+ switch (spec->type) {
+ case MD_PKEY_TYPE_DEFAULT:
+ break;
+ case MD_PKEY_TYPE_RSA:
+ nspec->params.rsa.bits = spec->params.rsa.bits;
+ break;
+ case MD_PKEY_TYPE_EC:
+ nspec->params.ec.curve = apr_pstrdup(p, spec->params.ec.curve);
+ break;
+ }
+ return nspec;
+}
+
+const char *md_pkey_spec_name(const md_pkey_spec_t *spec)
+{
+ if (!spec) return "rsa";
+ switch (spec->type) {
+ case MD_PKEY_TYPE_DEFAULT:
+ case MD_PKEY_TYPE_RSA:
+ return "rsa";
+ case MD_PKEY_TYPE_EC:
+ return spec->params.ec.curve;
+ }
+ return "unknown";
+}
+
+int md_pkeys_spec_is_empty(const md_pkeys_spec_t *pks)
+{
+ return NULL == pks || 0 == pks->specs->nelts;
+}
+
+md_pkeys_spec_t *md_pkeys_spec_clone(apr_pool_t *p, const md_pkeys_spec_t *pks)
+{
+ md_pkeys_spec_t *npks = NULL;
+ md_pkey_spec_t *spec;
+ int i;
+
+ if (pks && pks->specs->nelts > 0) {
+ npks = apr_pcalloc(p, sizeof(*npks));
+ npks->specs = apr_array_make(p, pks->specs->nelts, sizeof(md_pkey_spec_t*));
+ for (i = 0; i < pks->specs->nelts; ++i) {
+ spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*);
+ APR_ARRAY_PUSH(npks->specs, md_pkey_spec_t*) = pkey_spec_clone(p, spec);
+ }
+ }
+ return npks;
+}
+
+int md_pkeys_spec_count(const md_pkeys_spec_t *pks)
+{
+ return md_pkeys_spec_is_empty(pks)? 1 : pks->specs->nelts;
+}
+
+static md_pkey_spec_t PkeySpecDef = { MD_PKEY_TYPE_DEFAULT, {{ 0 }} };
+
+md_pkey_spec_t *md_pkeys_spec_get(const md_pkeys_spec_t *pks, int index)
+{
+ if (md_pkeys_spec_is_empty(pks)) {
+ return index == 1? &PkeySpecDef : NULL;
+ }
+ else if (pks && index >= 0 && index < pks->specs->nelts) {
+ return APR_ARRAY_IDX(pks->specs, index, md_pkey_spec_t*);
+ }
+ return NULL;
+}
+
static md_pkey_t *make_pkey(apr_pool_t *p)
{
md_pkey_t *pkey = apr_pcalloc(p, sizeof(*pkey));
@@ -418,7 +663,11 @@ static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *
}
ERR_clear_error();
+#if 1
+ if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) {
+#else
if (!PEM_write_bio_PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) {
+#endif
BIO_free(bio);
err = ERR_get_error();
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "PEM_write key: %ld %s",
@@ -451,6 +700,24 @@ apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
return rv;
}
+/* Determine the message digest used for signing with the given private key.
+ */
+static const EVP_MD *pkey_get_MD(md_pkey_t *pkey)
+{
+ switch (EVP_PKEY_id(pkey->pkey)) {
+#ifdef NID_ED25519
+ case NID_ED25519:
+ return NULL;
+#endif
+#ifdef NID_ED448
+ case NID_ED448:
+ return NULL;
+#endif
+ default:
+ return EVP_sha256();
+ }
+}
+
static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits)
{
EVP_PKEY_CTX *ctx = NULL;
@@ -476,6 +743,125 @@ static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits)
return rv;
}
+static apr_status_t check_EC_curve(int nid, apr_pool_t *p) {
+ EC_builtin_curve *curves = NULL;
+ size_t nc, i;
+ int rv = APR_ENOENT;
+
+ nc = EC_get_builtin_curves(NULL, 0);
+ if (NULL == (curves = OPENSSL_malloc(sizeof(*curves) * nc)) ||
+ nc != EC_get_builtin_curves(curves, nc)) {
+ rv = APR_EGENERAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
+ "error looking up OpenSSL builtin EC curves");
+ goto leave;
+ }
+ for (i = 0; i < nc; ++i) {
+ if (nid == curves[i].nid) {
+ rv = APR_SUCCESS;
+ break;
+ }
+ }
+leave:
+ OPENSSL_free(curves);
+ return rv;
+}
+
+static apr_status_t gen_ec(md_pkey_t **ppkey, apr_pool_t *p, const char *curve)
+{
+ EVP_PKEY_CTX *ctx = NULL;
+ apr_status_t rv;
+ int curve_nid = NID_undef;
+
+ /* 1. Convert the cure into its registered identifier. Curves can be known under
+ * different names.
+ * 2. Determine, if the curve is supported by OpenSSL (or whatever is linked).
+ * 3. Generate the key, respecting the specific quirks some curves require.
+ */
+ curve_nid = EC_curve_nist2nid(curve);
+ /* In case this fails, try some names from other standards, like SECG */
+#ifdef NID_secp384r1
+ if (NID_undef == curve_nid && !apr_strnatcasecmp("secp384r1", curve)) {
+ curve_nid = NID_secp384r1;
+ }
+#endif
+#ifdef NID_X9_62_prime256v1
+ if (NID_undef == curve_nid && !apr_strnatcasecmp("secp256r1", curve)) {
+ curve_nid = NID_X9_62_prime256v1;
+ }
+#endif
+#ifdef NID_X9_62_prime192v1
+ if (NID_undef == curve_nid && !apr_strnatcasecmp("secp192r1", curve)) {
+ curve_nid = NID_X9_62_prime192v1;
+ }
+#endif
+#ifdef NID_X25519
+ if (NID_undef == curve_nid && !apr_strnatcasecmp("X25519", curve)) {
+ curve_nid = NID_X25519;
+ }
+#endif
+ if (NID_undef == curve_nid) {
+ /* OpenSSL object/curve names */
+ curve_nid = OBJ_sn2nid(curve);
+ }
+ if (NID_undef == curve_nid) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "ec curve unknown: %s", curve);
+ rv = APR_ENOTIMPL; goto leave;
+ }
+
+ *ppkey = make_pkey(p);
+ switch (curve_nid) {
+
+#ifdef NID_X25519
+ case NID_X25519:
+ /* no parameters */
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL))
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
+ "error generate EC key for group: %s", curve);
+ rv = APR_EGENERAL; goto leave;
+ }
+ rv = APR_SUCCESS;
+ break;
+#endif
+
+#ifdef NID_X448
+ case NID_X448:
+ /* no parameters */
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X448, NULL))
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
+ "error generate EC key for group: %s", curve);
+ rv = APR_EGENERAL; goto leave;
+ }
+ rv = APR_SUCCESS;
+ break;
+#endif
+
+ default:
+ if (APR_SUCCESS != (rv = check_EC_curve(curve_nid, p))) goto leave;
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL))
+ || EVP_PKEY_paramgen_init(ctx) <= 0
+ || EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_nid) <= 0
+ || EVP_PKEY_CTX_set_ec_param_enc(ctx, OPENSSL_EC_NAMED_CURVE) <= 0
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
+ "error generate EC key for group: %s", curve);
+ rv = APR_EGENERAL; goto leave;
+ }
+ rv = APR_SUCCESS;
+ break;
+ }
+
+leave:
+ if (APR_SUCCESS != rv) *ppkey = NULL;
+ EVP_PKEY_CTX_free(ctx);
+ return rv;
+}
+
apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec)
{
md_pkey_type_t ptype = spec? spec->type : MD_PKEY_TYPE_DEFAULT;
@@ -484,6 +870,8 @@ apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec)
return gen_rsa(ppkey, p, MD_PKEY_RSA_BITS_DEF);
case MD_PKEY_TYPE_RSA:
return gen_rsa(ppkey, p, spec->params.rsa.bits);
+ case MD_PKEY_TYPE_EC:
+ return gen_ec(ppkey, p, spec->params.ec.curve);
default:
return APR_ENOTIMPL;
}
@@ -727,6 +1115,14 @@ apr_time_t md_cert_get_not_before(const md_cert_t *cert)
return md_asn1_time_get(X509_get_notBefore(cert->x509));
}
+md_timeperiod_t md_cert_get_valid(const md_cert_t *cert)
+{
+ md_timeperiod_t p;
+ p.start = md_cert_get_not_before(cert);
+ p.end = md_cert_get_not_after(cert);
+ return p;
+}
+
int md_cert_covers_domain(md_cert_t *cert, const char *domain_name)
{
apr_array_header_t *alt_names;
@@ -799,10 +1195,12 @@ apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t
STACK_OF(GENERAL_NAME) *xalt_names;
unsigned char *buf;
int i;
-
+
xalt_names = X509_get_ext_d2i(cert->x509, NID_subject_alt_name, NULL, NULL);
if (xalt_names) {
GENERAL_NAME *cval;
+ const unsigned char *ip;
+ int len;
names = apr_array_make(p, sk_GENERAL_NAME_num(xalt_names), sizeof(char *));
for (i = 0; i < sk_GENERAL_NAME_num(xalt_names); ++i) {
@@ -810,11 +1208,29 @@ apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t
switch (cval->type) {
case GEN_DNS:
case GEN_URI:
- case GEN_IPADD:
ASN1_STRING_to_UTF8(&buf, cval->d.ia5);
APR_ARRAY_PUSH(names, const char *) = apr_pstrdup(p, (char*)buf);
OPENSSL_free(buf);
break;
+ case GEN_IPADD:
+ len = ASN1_STRING_length(cval->d.iPAddress);
+ ip = ASN1_STRING_get0_data(cval->d.iPAddress);
+ if (len == 4) /* IPv4 address */
+ APR_ARRAY_PUSH(names, const char *) = apr_psprintf(p, "%u.%u.%u.%u",
+ ip[0], ip[1], ip[2], ip[3]);
+ else if (len == 16) /* IPv6 address */
+ APR_ARRAY_PUSH(names, const char *) = apr_psprintf(p, "%02x%02x%02x%02x:"
+ "%02x%02x%02x%02x:"
+ "%02x%02x%02x%02x:"
+ "%02x%02x%02x%02x",
+ ip[0], ip[1], ip[2], ip[3],
+ ip[4], ip[5], ip[6], ip[7],
+ ip[8], ip[9], ip[10], ip[11],
+ ip[12], ip[13], ip[14], ip[15]);
+ else {
+ ; /* Unknown address type - Log? Assert? */
+ }
+ break;
default:
break;
}
@@ -964,6 +1380,7 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p,
apr_status_t rv;
ct = apr_table_get(res->headers, "Content-Type");
+ ct = md_util_parse_ct(res->req->pool, ct);
if (!res->body || !ct || strcmp("application/pkix-cert", ct)) {
rv = APR_ENOENT;
goto out;
@@ -984,7 +1401,8 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p,
else {
cert = md_cert_make(p, x509);
rv = APR_SUCCESS;
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed");
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
+ "parsing cert from content-type=%s, content-length=%ld", ct, (long)data_len);
}
}
}
@@ -1015,7 +1433,10 @@ apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain,
rv = APR_ENOENT;
goto out;
}
- else if (!strcmp("application/pem-certificate-chain", ct)) {
+ ct = md_util_parse_ct(res->req->pool, ct);
+ if (!strcmp("application/pem-certificate-chain", ct)
+ || !strncmp("text/plain", ct, sizeof("text/plain")-1)) {
+ /* Some servers seem to think 'text/plain' is sufficient, see #232 */
if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool))) {
int added = 0;
md_cert_t *cert;
@@ -1033,7 +1454,8 @@ apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain,
rv = APR_SUCCESS;
}
}
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed");
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
+ "parsing cert from content-type=%s, content-length=%ld", ct, (long)data_len);
}
else if (!strcmp("application/pkix-cert", ct)) {
md_cert_t *cert;
@@ -1211,23 +1633,10 @@ static apr_status_t sk_add_alt_names(STACK_OF(X509_EXTENSION) *exts,
#define MD_OID_MUST_STAPLE_SNAME "tlsfeature"
#define MD_OID_MUST_STAPLE_LNAME "TLS Feature"
-static int get_must_staple_nid(void)
-{
- /* Funny API, the OID for must staple might be configured or
- * might be not. In the second case, we need to add it. But adding
- * when it already is there is an error... */
- int nid = OBJ_txt2nid(MD_OID_MUST_STAPLE_NUM);
- if (NID_undef == nid) {
- nid = OBJ_create(MD_OID_MUST_STAPLE_NUM,
- MD_OID_MUST_STAPLE_SNAME, MD_OID_MUST_STAPLE_LNAME);
- }
- return nid;
-}
-
int md_cert_must_staple(const md_cert_t *cert)
{
/* In case we do not get the NID for it, we treat this as not set. */
- int nid = get_must_staple_nid();
+ int nid = MD_GET_NID(MUST_STAPLE);
return ((NID_undef != nid)) && X509_get_ext_by_NID(cert->x509, nid, -1) >= 0;
}
@@ -1236,7 +1645,7 @@ static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const char *
X509_EXTENSION *x;
int nid;
- nid = get_must_staple_nid();
+ nid = MD_GET_NID(MUST_STAPLE);
if (NID_undef == nid) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
"%s: unable to get NID for v3 must-staple TLS feature", name);
@@ -1277,8 +1686,18 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name,
/* subject name == first domain */
domain = APR_ARRAY_IDX(domains, 0, const unsigned char *);
- if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0)
- || !X509_REQ_set_subject_name(csr, n)) {
+ /* Do not set the domain in the CN if it is longer than 64 octets.
+ * Instead, let the CA choose a 'proper' name. At the moment (2021-01), LE will
+ * inspect all SAN names and use one < 64 chars if it can be found. It will fail
+ * otherwise.
+ * The reason we do not check this beforehand is that the restrictions on CNs
+ * are in flux. They used to be authoritative, now browsers no longer do that, but
+ * no one wants to hand out a cert with "google.com" as CN either. So, we leave
+ * it for the CA to decide if and how it hands out a cert for this or fails.
+ * This solves issue where the name is too long, see #227 */
+ if (strlen((const char*)domain) < 64
+ && (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0)
+ || !X509_REQ_set_subject_name(csr, n))) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: REQ name add entry", name);
rv = APR_EGENERAL; goto out;
}
@@ -1306,7 +1725,7 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name,
rv = APR_EGENERAL; goto out;
}
/* sign, der encode and base64url encode */
- if (!X509_REQ_sign(csr, pkey->pkey, EVP_sha256())) {
+ if (!X509_REQ_sign(csr, pkey->pkey, pkey_get_MD(pkey))) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign csr", name);
rv = APR_EGENERAL; goto out;
}
@@ -1377,7 +1796,7 @@ static apr_status_t mk_x509(X509 **px, md_pkey_t *pkey, const char *cn,
rv = APR_EGENERAL; goto out;
}
/* cert are unconstrained (but not very trustworthy) */
- if (APR_SUCCESS != (rv = add_ext(x, NID_basic_constraints, "CA:FALSE, pathlen:0", p))) {
+ if (APR_SUCCESS != (rv = add_ext(x, NID_basic_constraints, "critical,CA:FALSE", p))) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set basic constraints ext", cn);
goto out;
}
@@ -1422,8 +1841,19 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
goto out;
}
+ /* keyUsage, ExtendedKeyUsage */
+
+ if (APR_SUCCESS != (rv = add_ext(x, NID_key_usage, "critical,digitalSignature", p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set keyUsage", cn);
+ goto out;
+ }
+ if (APR_SUCCESS != (rv = add_ext(x, NID_ext_key_usage, "serverAuth", p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set extKeyUsage", cn);
+ goto out;
+ }
+
/* sign with same key */
- if (!X509_sign(x, pkey->pkey, EVP_sha256())) {
+ if (!X509_sign(x, pkey->pkey, pkey_get_MD(pkey))) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", cn);
rv = APR_EGENERAL; goto out;
}
@@ -1460,7 +1890,7 @@ apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain,
const char *alts;
apr_status_t rv;
- if (APR_SUCCESS != (rv = mk_x509(&x, pkey, domain, valid_for, p))) goto out;
+ if (APR_SUCCESS != (rv = mk_x509(&x, pkey, "tls-alpn-01-challenge", valid_for, p))) goto out;
/* add the domain as alt name */
alts = apr_psprintf(p, "DNS:%s", domain);
@@ -1475,7 +1905,7 @@ apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain,
}
/* sign with same key */
- if (!X509_sign(x, pkey->pkey, EVP_sha256())) {
+ if (!X509_sign(x, pkey->pkey, pkey_get_MD(pkey))) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", domain);
rv = APR_EGENERAL; goto out;
}
diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h
index 2fba40912b..4706207da9 100644
--- a/modules/md/md_crypt.h
+++ b/modules/md/md_crypt.h
@@ -25,7 +25,7 @@ struct md_http_response_t;
struct md_cert_t;
struct md_pkey_t;
struct md_data_t;
-
+struct md_timeperiod_t;
/**************************************************************************************************/
/* random */
@@ -51,22 +51,54 @@ typedef struct md_pkey_t md_pkey_t;
typedef enum {
MD_PKEY_TYPE_DEFAULT,
MD_PKEY_TYPE_RSA,
+ MD_PKEY_TYPE_EC,
} md_pkey_type_t;
-typedef struct md_pkey_rsa_spec_t {
+typedef struct md_pkey_rsa_params_t {
apr_uint32_t bits;
-} md_pkey_rsa_spec_t;
+} md_pkey_rsa_params_t;
+
+typedef struct md_pkey_ec_params_t {
+ const char *curve;
+} md_pkey_ec_params_t;
typedef struct md_pkey_spec_t {
md_pkey_type_t type;
union {
- md_pkey_rsa_spec_t rsa;
+ md_pkey_rsa_params_t rsa;
+ md_pkey_ec_params_t ec;
} params;
} md_pkey_spec_t;
+typedef struct md_pkeys_spec_t {
+ apr_pool_t *p;
+ struct apr_array_header_t *specs;
+} md_pkeys_spec_t;
+
apr_status_t md_crypt_init(apr_pool_t *pool);
-apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec);
+const char *md_pkey_spec_name(const md_pkey_spec_t *spec);
+
+md_pkeys_spec_t *md_pkeys_spec_make(apr_pool_t *p);
+void md_pkeys_spec_add_default(md_pkeys_spec_t *pks);
+int md_pkeys_spec_contains_rsa(md_pkeys_spec_t *pks);
+void md_pkeys_spec_add_rsa(md_pkeys_spec_t *pks, unsigned int bits);
+int md_pkeys_spec_contains_ec(md_pkeys_spec_t *pks, const char *curve);
+void md_pkeys_spec_add_ec(md_pkeys_spec_t *pks, const char *curve);
+int md_pkeys_spec_eq(md_pkeys_spec_t *pks1, md_pkeys_spec_t *pks2);
+md_pkeys_spec_t *md_pkeys_spec_clone(apr_pool_t *p, const md_pkeys_spec_t *pks);
+int md_pkeys_spec_is_empty(const md_pkeys_spec_t *pks);
+md_pkey_spec_t *md_pkeys_spec_get(const md_pkeys_spec_t *pks, int index);
+int md_pkeys_spec_count(const md_pkeys_spec_t *pks);
+void md_pkeys_spec_add(md_pkeys_spec_t *pks, md_pkey_spec_t *spec);
+
+struct md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p);
+md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p);
+struct md_json_t *md_pkeys_spec_to_json(const md_pkeys_spec_t *pks, apr_pool_t *p);
+md_pkeys_spec_t *md_pkeys_spec_from_json(struct md_json_t *json, apr_pool_t *p);
+
+
+apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *key_props);
void md_pkey_free(md_pkey_t *pkey);
const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p);
@@ -84,10 +116,6 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *
void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey);
-struct md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p);
-md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p);
-int md_pkey_spec_eq(md_pkey_spec_t *spec1, md_pkey_spec_t *spec2);
-
/**************************************************************************************************/
/* X509 certificates */
@@ -142,6 +170,7 @@ int md_cert_covers_md(md_cert_t *cert, const struct md_t *md);
int md_cert_must_staple(const md_cert_t *cert);
apr_time_t md_cert_get_not_after(const md_cert_t *cert);
apr_time_t md_cert_get_not_before(const md_cert_t *cert);
+struct md_timeperiod_t md_cert_get_valid(const md_cert_t *cert);
apr_status_t md_cert_get_issuers_uri(const char **puri, const md_cert_t *cert, apr_pool_t *p);
apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t *cert, apr_pool_t *p);
@@ -183,7 +212,6 @@ apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain,
apr_status_t md_cert_get_ct_scts(apr_array_header_t *scts, apr_pool_t *p, const md_cert_t *cert);
-
/**************************************************************************************************/
/* X509 certificate transparency */
diff --git a/modules/md/md_curl.c b/modules/md/md_curl.c
index ed40e7604e..e93bea4d42 100644
--- a/modules/md/md_curl.c
+++ b/modules/md/md_curl.c
@@ -244,22 +244,29 @@ static apr_status_t internals_setup(md_http_request_t *req)
md_curl_internals_t *internals;
CURL *curl;
apr_status_t rv = APR_SUCCESS;
-
- curl = curl_easy_init();
+
+ curl = md_http_get_impl_data(req->http);
if (!curl) {
- rv = APR_EGENERAL;
- goto leave;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "creating curl instance");
+ curl = curl_easy_init();
+ if (!curl) {
+ rv = APR_EGENERAL;
+ goto leave;
+ }
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb);
+ curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "reusing curl instance from http");
}
+
internals = apr_pcalloc(req->pool, sizeof(*internals));
internals->curl = curl;
- curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
- curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL);
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb);
- curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
-
internals->response = apr_pcalloc(req->pool, sizeof(md_http_response_t));
internals->response->req = req;
internals->response->status = 400;
@@ -293,7 +300,10 @@ static apr_status_t internals_setup(md_http_request_t *req)
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, req->timeout.stall_bytes_per_sec);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec(req->timeout.stalled));
}
-
+ if (req->ca_file) {
+ curl_easy_setopt(curl, CURLOPT_CAINFO, req->ca_file);
+ }
+
if (req->body_len >= 0) {
/* set the Content-Length */
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)req->body_len);
@@ -568,17 +578,44 @@ static void md_curl_req_cleanup(md_http_request_t *req)
{
md_curl_internals_t *internals = req->internals;
if (internals) {
- if (internals->curl) curl_easy_cleanup(internals->curl);
+ if (internals->curl) {
+ CURL *curl = md_http_get_impl_data(req->http);
+ if (curl == internals->curl) {
+ /* NOP: we have this curl at the md_http_t already */
+ }
+ else if (!curl) {
+ /* no curl at the md_http_t yet, install this one */
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "register curl instance at http");
+ md_http_set_impl_data(req->http, internals->curl);
+ }
+ else {
+ /* There already is a curl at the md_http_t and it's not this one. */
+ curl_easy_cleanup(internals->curl);
+ }
+ }
if (internals->req_hdrs) curl_slist_free_all(internals->req_hdrs);
req->internals = NULL;
}
}
+static void md_curl_cleanup(md_http_t *http, apr_pool_t *pool)
+{
+ CURL *curl;
+
+ curl = md_http_get_impl_data(http);
+ if (curl) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, pool, "cleanup curl instance");
+ md_http_set_impl_data(http, NULL);
+ curl_easy_cleanup(curl);
+ }
+}
+
static md_http_impl_t impl = {
md_curl_init,
md_curl_req_cleanup,
md_curl_perform,
md_curl_multi_perform,
+ md_curl_cleanup,
};
md_http_impl_t * md_curl_get_impl(apr_pool_t *p)
diff --git a/modules/md/md_event.c b/modules/md/md_event.c
new file mode 100644
index 0000000000..c731d55e0c
--- /dev/null
+++ b/modules/md/md_event.c
@@ -0,0 +1,89 @@
+/* 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 <apr_optional.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_event.h"
+
+
+typedef struct md_subscription {
+ struct md_subscription *next;
+ md_event_cb *cb;
+ void *baton;
+} md_subscription;
+
+static struct {
+ apr_pool_t *p;
+ md_subscription *subs;
+} EVNT;
+
+static apr_status_t cleanup_setup(void *dummy)
+{
+ (void)dummy;
+ memset(&EVNT, 0, sizeof(EVNT));
+ return APR_SUCCESS;
+}
+
+void md_event_init(apr_pool_t *p)
+{
+ memset(&EVNT, 0, sizeof(EVNT));
+ EVNT.p = p;
+ apr_pool_cleanup_register(p, NULL, cleanup_setup, apr_pool_cleanup_null);
+}
+
+void md_event_subscribe(md_event_cb *cb, void *baton)
+{
+ md_subscription *sub;
+
+ sub = apr_pcalloc(EVNT.p, sizeof(*sub));
+ sub->cb = cb;
+ sub->baton = baton;
+ sub->next = EVNT.subs;
+ EVNT.subs = sub;
+}
+
+apr_status_t md_event_raise(const char *event,
+ const char *mdomain,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p)
+{
+ md_subscription *sub = EVNT.subs;
+ apr_status_t rv;
+
+ while (sub) {
+ rv = sub->cb(event, mdomain, sub->baton, job, result, p);
+ if (APR_SUCCESS != rv) return rv;
+ sub = sub->next;
+ }
+ return APR_SUCCESS;
+}
+
+void md_event_holler(const char *event,
+ const char *mdomain,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p)
+{
+ md_subscription *sub = EVNT.subs;
+ while (sub) {
+ sub->cb(event, mdomain, sub->baton, job, result, p);
+ sub = sub->next;
+ }
+}
diff --git a/modules/md/md_event.h b/modules/md/md_event.h
new file mode 100644
index 0000000000..e66c3c2395
--- /dev/null
+++ b/modules/md/md_event.h
@@ -0,0 +1,46 @@
+/* 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.
+*/
+
+#ifndef md_event_h
+#define md_event_h
+
+struct md_job_t;
+struct md_result_t;
+
+typedef apr_status_t md_event_cb(const char *event,
+ const char *mdomain,
+ void *baton,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p);
+
+void md_event_init(apr_pool_t *p);
+
+void md_event_subscribe(md_event_cb *cb, void *baton);
+
+apr_status_t md_event_raise(const char *event,
+ const char *mdomain,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p);
+
+void md_event_holler(const char *event,
+ const char *mdomain,
+ struct md_job_t *job,
+ struct md_result_t *result,
+ apr_pool_t *p);
+
+#endif /* md_event_h */
diff --git a/modules/md/md_http.c b/modules/md/md_http.c
index 9c2790cd66..53e4f89d99 100644
--- a/modules/md/md_http.c
+++ b/modules/md/md_http.c
@@ -30,9 +30,11 @@ struct md_http_t {
int next_id;
apr_off_t resp_limit;
md_http_impl_t *impl;
+ void *impl_data; /* to be used by the implementation */
const char *user_agent;
const char *proxy_url;
md_http_timeouts_t timeout;
+ const char *ca_file;
};
static md_http_impl_t *cur_impl;
@@ -46,6 +48,15 @@ void md_http_use_implementation(md_http_impl_t *impl)
}
}
+static apr_status_t http_cleanup(void *data)
+{
+ md_http_t *http = data;
+ if (http && http->impl && http->impl->cleanup) {
+ http->impl->cleanup(http, http->pool);
+ }
+ return APR_SUCCESS;
+}
+
apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_agent,
const char *proxy_url)
{
@@ -75,10 +86,21 @@ apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_a
if (!http->bucket_alloc) {
return APR_EGENERAL;
}
+ apr_pool_cleanup_register(p, http, http_cleanup, apr_pool_cleanup_null);
*phttp = http;
return APR_SUCCESS;
}
+void md_http_set_impl_data(md_http_t *http, void *data)
+{
+ http->impl_data = data;
+}
+
+void *md_http_get_impl_data(md_http_t *http)
+{
+ return http->impl_data;
+}
+
void md_http_set_response_limit(md_http_t *http, apr_off_t resp_limit)
{
http->resp_limit = resp_limit;
@@ -116,6 +138,11 @@ void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t
req->timeout.stalled = timeout;
}
+void md_http_set_ca_file(md_http_t *http, const char *ca_file)
+{
+ http->ca_file = ca_file;
+}
+
static apr_status_t req_set_body(md_http_request_t *req, const char *content_type,
apr_bucket_brigade *body, apr_off_t body_len,
int detect_len)
@@ -183,6 +210,7 @@ static apr_status_t req_create(md_http_request_t **preq, md_http_t *http,
req->user_agent = http->user_agent;
req->proxy_url = http->proxy_url;
req->timeout = http->timeout;
+ req->ca_file = http->ca_file;
*preq = req;
return rv;
}
diff --git a/modules/md/md_http.h b/modules/md/md_http.h
index 78668b31d2..e24de03113 100644
--- a/modules/md/md_http.h
+++ b/modules/md/md_http.h
@@ -64,6 +64,7 @@ struct md_http_request_t {
const char *url;
const char *user_agent;
const char *proxy_url;
+ const char *ca_file;
apr_table_t *headers;
struct apr_bucket_brigade *body;
apr_off_t body_len;
@@ -110,6 +111,13 @@ void md_http_set_stalling_default(md_http_t *http, long bytes_per_sec, apr_time_
void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t timeout);
/**
+ * Set a CA file (in PERM format) to use for root certificates when
+ * verifying SSL connections. If not set (or set to NULL), the systems
+ * certificate store will be used.
+ */
+void md_http_set_ca_file(md_http_t *http, const char *ca_file);
+
+/**
* Perform the request. Then this function returns, the request and
* all its memory has been freed and must no longer be used.
*/
@@ -222,6 +230,7 @@ apr_status_t md_http_multi_perform(md_http_t *http, md_http_next_req *nextreq, v
/* interface to implementation */
typedef apr_status_t md_http_init_cb(void);
+typedef void md_http_cleanup_cb(md_http_t *req, apr_pool_t *p);
typedef void md_http_req_cleanup_cb(md_http_request_t *req);
typedef apr_status_t md_http_perform_cb(md_http_request_t *req);
typedef apr_status_t md_http_multi_perform_cb(md_http_t *http, apr_pool_t *p,
@@ -233,10 +242,17 @@ struct md_http_impl_t {
md_http_req_cleanup_cb *req_cleanup;
md_http_perform_cb *perform;
md_http_multi_perform_cb *multi_perform;
+ md_http_cleanup_cb *cleanup;
};
void md_http_use_implementation(md_http_impl_t *impl);
+/**
+ * get/set data the implementation wants to remember between requests
+ * in the same md_http_t instance.
+ */
+void md_http_set_impl_data(md_http_t *http, void *data);
+void *md_http_get_impl_data(md_http_t *http);
#endif /* md_http_h */
diff --git a/modules/md/md_json.c b/modules/md/md_json.c
index 5785523ba4..c81fb0f786 100644
--- a/modules/md/md_json.c
+++ b/modules/md/md_json.c
@@ -832,6 +832,32 @@ int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...)
return 1;
}
+int md_json_iterkey(md_json_iterkey_cb *cb, void *baton, md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+ const char *key;
+ json_t *val;
+ md_json_t wrap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (!j || !json_is_object(j)) {
+ return 0;
+ }
+
+ wrap.p = json->p;
+ json_object_foreach(j, key, val) {
+ wrap.j = val;
+ if (!cb(baton, key, &wrap)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
/**************************************************************************************************/
/* array strings */
@@ -1159,8 +1185,13 @@ apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *p, const char *fpath)
apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, const md_http_response_t *res)
{
apr_status_t rv = APR_ENOENT;
- const char *ctype = apr_table_get(res->headers, "content-type");
- if (ctype && res->body && (strstr(ctype, "/json") || strstr(ctype, "+json"))) {
+ const char *ctype = apr_table_get(res->headers, "content-type"), *p;
+
+ *pjson = NULL;
+ ctype = md_util_parse_ct(res->req->pool, ctype);
+ p = ctype + strlen(ctype) +1;
+ if (ctype && res->body && (!strcmp(p - sizeof("/json"), "/json") ||
+ !strcmp(p - sizeof("+json"), "+json"))) {
rv = md_json_readb(pjson, pool, res->body);
}
return rv;
diff --git a/modules/md/md_json.h b/modules/md/md_json.h
index 95a828b351..50b882804d 100644
--- a/modules/md/md_json.h
+++ b/modules/md/md_json.h
@@ -113,6 +113,10 @@ apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb,
typedef int md_json_itera_cb(void *baton, size_t index, md_json_t *json);
int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...);
+/* Called on each object key, aborts iteration when returning 0 */
+typedef int md_json_iterkey_cb(void *baton, const char* key, md_json_t *json);
+int md_json_iterkey(md_json_iterkey_cb *cb, void *baton, md_json_t *json, ...);
+
/* Manipulating Object String values */
apr_status_t md_json_gets_dict(apr_table_t *dict, const md_json_t *json, ...);
apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...);
diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c
index dc95393249..ea9366b84a 100644
--- a/modules/md/md_ocsp.c
+++ b/modules/md/md_ocsp.c
@@ -41,6 +41,7 @@
#include "md.h"
#include "md_crypt.h"
+#include "md_event.h"
#include "md_json.h"
#include "md_log.h"
#include "md_http.h"
@@ -745,16 +746,16 @@ static apr_status_t ostat_on_req_status(const md_http_request_t *req, apr_status
md_job_end_run(update->job, update->result);
if (APR_SUCCESS != status) {
++ostat->errors;
- ostat->next_run = apr_time_now() + md_job_delay_on_errors(ostat->errors);
+ ostat->next_run = apr_time_now() + md_job_delay_on_errors(update->job, ostat->errors, NULL);
md_result_printf(update->result, status, "OCSP status update failed (%d. time)",
ostat->errors);
md_result_log(update->result, MD_LOG_DEBUG);
md_job_log_append(update->job, "ocsp-error",
update->result->problem, update->result->detail);
- md_job_holler(update->job, "ocsp-errored");
+ md_event_holler("ocsp-errored", update->job->mdomain, update->job, update->result, update->p);
goto leave;
}
- md_job_notify(update->job, "ocsp-renewed", update->result);
+ md_event_holler("ocsp-renewed", update->job->mdomain, update->job, update->result, update->p);
leave:
md_job_save(update->job, update->result, update->p);
@@ -892,7 +893,7 @@ leave:
if (ctx.time < apr_time_now()) ctx.time = apr_time_now() + apr_time_from_sec(1);
*pnext_run = ctx.time;
- if (APR_SUCCESS != rv) {
+ if (APR_SUCCESS != rv && APR_ENOENT != rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "ocsp_renew done");
}
return;
@@ -1032,17 +1033,7 @@ void md_ocsp_get_status_all(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p
*pjson = json;
}
-void md_ocsp_set_notify_cb(md_ocsp_reg_t *ocsp, md_job_notify_cb *cb, void *baton)
-{
- ocsp->notify = cb;
- ocsp->notify_ctx = baton;
-}
-
md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p)
{
- md_job_t *job;
-
- job = md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain);
- md_job_set_notify_cb(job, ocsp->notify, ocsp->notify_ctx);
- return job;
+ return md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain);
}
diff --git a/modules/md/md_ocsp.h b/modules/md/md_ocsp.h
index 9f0c0fd035..61c387ec1e 100644
--- a/modules/md/md_ocsp.h
+++ b/modules/md/md_ocsp.h
@@ -60,7 +60,6 @@ apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t
void md_ocsp_get_summary(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p);
void md_ocsp_get_status_all(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p);
-void md_ocsp_set_notify_cb(md_ocsp_reg_t *reg, md_job_notify_cb *cb, void *baton);
struct md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p);
#endif /* md_ocsp_h */
diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c
index 9c57c29046..7b039b2f03 100644
--- a/modules/md/md_reg.c
+++ b/modules/md/md_reg.c
@@ -26,6 +26,7 @@
#include "md.h"
#include "md_crypt.h"
+#include "md_event.h"
#include "md_log.h"
#include "md_json.h"
#include "md_result.h"
@@ -45,6 +46,7 @@ struct md_reg_t {
int can_http;
int can_https;
const char *proxy_url;
+ const char *ca_file;
int domains_frozen;
md_timeslice_t *renew_window;
md_timeslice_t *warn_window;
@@ -77,7 +79,7 @@ static apr_status_t load_props(md_reg_t *reg, apr_pool_t *p)
}
apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store,
- const char *proxy_url)
+ const char *proxy_url, const char *ca_file)
{
md_reg_t *reg;
apr_status_t rv;
@@ -90,7 +92,8 @@ apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *st
reg->can_http = 1;
reg->can_https = 1;
reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
-
+ reg->ca_file = ca_file? apr_pstrdup(p, ca_file) : NULL;
+
md_timeslice_create(&reg->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF);
md_timeslice_create(&reg->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF);
@@ -194,41 +197,49 @@ static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, i
static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
{
- md_state_t state = MD_S_UNKNOWN;
+ md_state_t state;
const md_pubcert_t *pub;
const md_cert_t *cert;
- apr_status_t rv;
+ apr_status_t rv = APR_SUCCESS;
+ md_pkey_spec_t *spec;
+ int i;
if (md->renew_window == NULL) md->renew_window = reg->renew_window;
if (md->warn_window == NULL) md->warn_window = reg->warn_window;
-
- if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, p))) {
- cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
- if (!md_is_covered_by_alt_names(md, pub->alt_names)) {
- state = MD_S_INCOMPLETE;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: incomplete, cert no longer covers all domains, "
- "needs sign up for a new certificate", md->name);
- goto out;
+
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, spec, p))) {
+ cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+ if (!md_is_covered_by_alt_names(md, pub->alt_names)) {
+ state = MD_S_INCOMPLETE;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "md{%s}: incomplete, certificate(%s) does not cover all domains.",
+ md->name, md_pkey_spec_name(spec));
+ goto out;
+ }
+ if (!md->must_staple != !md_cert_must_staple(cert)) {
+ state = MD_S_INCOMPLETE;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "md{%s}: incomplete, OCSP Stapling is%s requested, but "
+ "certificate(%s) has it%s enabled.",
+ md->name, md_pkey_spec_name(spec),
+ md->must_staple? "" : " not",
+ !md->must_staple? "" : " not");
+ goto out;
+ }
+ state = MD_S_COMPLETE;
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: certificate(%s) is ok",
+ md->name, md_pkey_spec_name(spec));
}
- if (!md->must_staple != !md_cert_must_staple(cert)) {
+ else if (APR_STATUS_IS_ENOENT(rv)) {
state = MD_S_INCOMPLETE;
+ rv = APR_SUCCESS;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: OCSP Stapling is%s requested, but certificate "
- "has it%s enabled. Need to get a new certificate.", md->name,
- md->must_staple? "" : " not",
- !md->must_staple? "" : " not");
+ "md{%s}: incomplete, certificate(%s) is missing", md->name,
+ md_pkey_spec_name(spec));
goto out;
}
-
- state = MD_S_COMPLETE;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: is complete", md->name);
- }
- else if (APR_STATUS_IS_ENOENT(rv)) {
- state = MD_S_INCOMPLETE;
- rv = APR_SUCCESS;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: incomplete, credentials not all there", md->name);
}
out:
@@ -465,10 +476,7 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
}
if (MD_UPD_PKEY_SPEC & fields) {
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update pkey spec: %s", name);
- nmd->pkey_spec = NULL;
- if (updates->pkey_spec) {
- nmd->pkey_spec = apr_pmemdup(p, updates->pkey_spec, sizeof(md_pkey_spec_t));
- }
+ nmd->pks = md_pkeys_spec_clone(p, updates->pks);
}
if (MD_UPD_REQUIRE_HTTPS & fields) {
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update require-https: %s", name);
@@ -524,6 +532,7 @@ static apr_status_t pubcert_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
apr_array_header_t *certs;
md_pubcert_t *pubcert, **ppubcert;
const md_t *md;
+ md_pkey_spec_t *spec;
const md_cert_t *cert;
md_cert_state_t cert_state;
md_store_group_t group;
@@ -532,12 +541,13 @@ static apr_status_t pubcert_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
ppubcert = va_arg(ap, md_pubcert_t **);
group = (md_store_group_t)va_arg(ap, int);
md = va_arg(ap, const md_t *);
+ spec = va_arg(ap, md_pkey_spec_t *);
if (md->cert_file) {
rv = md_chain_fload(&certs, p, md->cert_file);
}
else {
- rv = md_pubcert_load(reg->store, group, md->name, &certs, p);
+ rv = md_pubcert_load(reg->store, group, md->name, spec, &certs, p);
}
if (APR_SUCCESS != rv) goto leave;
@@ -561,21 +571,22 @@ leave:
}
apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg,
- const md_t *md, apr_pool_t *p)
+ const md_t *md, md_pkey_spec_t *spec, apr_pool_t *p)
{
apr_status_t rv = APR_SUCCESS;
const md_pubcert_t *pubcert;
const char *name;
- pubcert = apr_hash_get(reg->certs, md->name, (apr_ssize_t)strlen(md->name));
+ name = apr_pstrcat(p, md->name, "[", md_pkey_spec_name(spec), "]", NULL);
+ pubcert = apr_hash_get(reg->certs, name, (apr_ssize_t)strlen(name));
if (!pubcert && !reg->domains_frozen) {
- rv = md_util_pool_vdo(pubcert_load, reg, reg->p, &pubcert, MD_SG_DOMAINS, md, NULL);
+ rv = md_util_pool_vdo(pubcert_load, reg, reg->p, &pubcert, MD_SG_DOMAINS, md, spec, NULL);
if (APR_STATUS_IS_ENOENT(rv)) {
/* We cache it missing with an empty record */
pubcert = apr_pcalloc(reg->p, sizeof(*pubcert));
}
else if (APR_SUCCESS != rv) goto leave;
- name = (p != reg->p)? apr_pstrdup(reg->p, md->name) : md->name;
+ if (p != reg->p) name = apr_pstrdup(reg->p, name);
apr_hash_set(reg->certs, name, (apr_ssize_t)strlen(name), pubcert);
}
leave:
@@ -588,7 +599,7 @@ leave:
apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile,
md_reg_t *reg, md_store_group_t group,
- const md_t *md, apr_pool_t *p)
+ const md_t *md, md_pkey_spec_t *spec, apr_pool_t *p)
{
apr_status_t rv;
@@ -598,40 +609,73 @@ apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile
*pkeyfile = md->pkey_file;
return APR_SUCCESS;
}
- rv = md_store_get_fname(pkeyfile, reg->store, group, md->name, MD_FN_PRIVKEY, p);
+ rv = md_store_get_fname(pkeyfile, reg->store, group, md->name, md_pkey_filename(spec, p), p);
if (APR_SUCCESS != rv) return rv;
if (!md_file_exists(*pkeyfile, p)) return APR_ENOENT;
- rv = md_store_get_fname(pcertfile, reg->store, group, md->name, MD_FN_PUBCERT, p);
+ rv = md_store_get_fname(pcertfile, reg->store, group, md->name, md_chain_filename(spec, p), p);
if (APR_SUCCESS != rv) return rv;
if (!md_file_exists(*pcertfile, p)) return APR_ENOENT;
return APR_SUCCESS;
}
+apr_time_t md_reg_valid_until(md_reg_t *reg, const md_t *md, apr_pool_t *p)
+{
+ const md_pubcert_t *pub;
+ const md_cert_t *cert;
+ md_pkey_spec_t *spec;
+ int i;
+ apr_time_t t, valid_until = 0;
+ apr_status_t rv;
+
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ rv = md_reg_get_pubcert(&pub, reg, md, spec, p);
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+ t = md_cert_get_not_after(cert);
+ if (valid_until == 0 || t < valid_until) {
+ valid_until = t;
+ }
+ }
+ }
+ return valid_until;
+}
+
apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p)
{
const md_pubcert_t *pub;
const md_cert_t *cert;
md_timeperiod_t certlife, renewal;
+ md_pkey_spec_t *spec;
+ int i;
+ apr_time_t renew_at = 0;
apr_status_t rv;
if (md->state == MD_S_INCOMPLETE) return apr_time_now();
- rv = md_reg_get_pubcert(&pub, reg, md, p);
- if (APR_STATUS_IS_ENOENT(rv)) return apr_time_now();
- if (APR_SUCCESS == rv) {
- cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
- certlife.start = md_cert_get_not_before(cert);
- certlife.end = md_cert_get_not_after(cert);
-
- renewal = md_timeperiod_slice_before_end(&certlife, md->renew_window);
- if (md_log_is_level(p, MD_LOG_TRACE1)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
- "md[%s]: cert-life[%s] renewal[%s]", md->name,
- md_timeperiod_print(p, &certlife),
- md_timeperiod_print(p, &renewal));
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ rv = md_reg_get_pubcert(&pub, reg, md, spec, p);
+ if (APR_STATUS_IS_ENOENT(rv)) return apr_time_now();
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+ certlife.start = md_cert_get_not_before(cert);
+ certlife.end = md_cert_get_not_after(cert);
+
+ renewal = md_timeperiod_slice_before_end(&certlife, md->renew_window);
+ if (md_log_is_level(p, MD_LOG_TRACE1)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
+ "md[%s]: certificate(%s) valid[%s] renewal[%s]",
+ md->name, md_pkey_spec_name(spec),
+ md_timeperiod_print(p, &certlife),
+ md_timeperiod_print(p, &renewal));
+ }
+
+ if (renew_at == 0 || renewal.start < renew_at) {
+ renew_at = renewal.start;
+ }
}
- return renewal.start;
}
- return 0;
+ return renew_at;
}
int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p)
@@ -647,24 +691,32 @@ int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p)
const md_pubcert_t *pub;
const md_cert_t *cert;
md_timeperiod_t certlife, warn;
+ md_pkey_spec_t *spec;
+ int i;
apr_status_t rv;
if (md->state == MD_S_INCOMPLETE) return 0;
- rv = md_reg_get_pubcert(&pub, reg, md, p);
- if (APR_STATUS_IS_ENOENT(rv)) return 0;
- if (APR_SUCCESS == rv) {
- cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
- certlife.start = md_cert_get_not_before(cert);
- certlife.end = md_cert_get_not_after(cert);
-
- warn = md_timeperiod_slice_before_end(&certlife, md->warn_window);
- if (md_log_is_level(p, MD_LOG_TRACE1)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
- "md[%s]: cert-life[%s] warn[%s]", md->name,
- md_timeperiod_print(p, &certlife),
- md_timeperiod_print(p, &warn));
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ rv = md_reg_get_pubcert(&pub, reg, md, spec, p);
+ if (APR_STATUS_IS_ENOENT(rv)) return 0;
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+ certlife.start = md_cert_get_not_before(cert);
+ certlife.end = md_cert_get_not_after(cert);
+
+ warn = md_timeperiod_slice_before_end(&certlife, md->warn_window);
+ if (md_log_is_level(p, MD_LOG_TRACE1)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
+ "md[%s]: certificate(%s) life[%s] warn[%s]",
+ md->name, md_pkey_spec_name(spec),
+ md_timeperiod_print(p, &certlife),
+ md_timeperiod_print(p, &warn));
+ }
+ if (md_timeperiod_has_started(&warn, apr_time_now())) {
+ return 1;
+ }
}
- return md_timeperiod_has_started(&warn, apr_time_now());
}
return 0;
}
@@ -880,7 +932,7 @@ apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool
&& !MD_VAL_UPDATE(md, old, renew_mode)
&& md_timeslice_eq(md->renew_window, old->renew_window)
&& md_timeslice_eq(md->warn_window, old->warn_window)
- && md_pkey_spec_eq(md->pkey_spec, old->pkey_spec)
+ && md_pkeys_spec_eq(md->pks, old->pks)
&& !MD_VAL_UPDATE(md, old, require_https)
&& !MD_VAL_UPDATE(md, old, must_staple)
&& md_array_str_eq(md->acme_tls_1_domains, old->acme_tls_1_domains, 0)
@@ -984,13 +1036,14 @@ static apr_status_t run_init(void *baton, apr_pool_t *p, ...)
driver->reg = reg;
driver->store = md_reg_store_get(reg);
driver->proxy_url = reg->proxy_url;
+ driver->ca_file = reg->ca_file;
driver->md = md;
driver->can_http = reg->can_http;
driver->can_https = reg->can_https;
s = apr_table_get(driver->env, MD_KEY_ACTIVATION_DELAY);
if (!s || APR_SUCCESS != md_duration_parse(&driver->activation_delay, s, "d")) {
- driver->activation_delay = apr_time_from_sec(MD_SECS_PER_DAY);
+ driver->activation_delay = 0;
}
if (!md->ca_proto) {
@@ -1127,7 +1180,7 @@ static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *pte
md_store_purge(reg->store, p, MD_SG_STAGING, md->name);
md_store_purge(reg->store, p, MD_SG_CHALLENGES, md->name);
md_result_set(result, APR_SUCCESS, "new certificate successfully saved in domains");
- md_job_notify(job, "installed", result);
+ md_event_holler("installed", md->name, job, result, ptemp);
if (job->dirty) md_job_save(job, result, ptemp);
out:
@@ -1149,14 +1202,18 @@ apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds)
apr_status_t rv = APR_SUCCESS;
md_t *md;
const md_pubcert_t *pubcert;
- int i;
+ md_pkey_spec_t *spec;
+ int i, j;
assert(!reg->domains_frozen);
/* prefill the certs cache for all mds */
for (i = 0; i < mds->nelts; ++i) {
md = APR_ARRAY_IDX(mds, i, md_t*);
- rv = md_reg_get_pubcert(&pubcert, reg, md, reg->p);
- if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) goto leave;
+ for (j = 0; j < md_pkeys_spec_count(md->pks); ++j) {
+ spec = md_pkeys_spec_get(md->pks, j);
+ rv = md_reg_get_pubcert(&pubcert, reg, md, spec, reg->p);
+ if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) goto leave;
+ }
}
reg->domains_frozen = 1;
leave:
@@ -1173,17 +1230,7 @@ void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window)
*reg->warn_window = *warn_window;
}
-void md_reg_set_notify_cb(md_reg_t *reg, md_job_notify_cb *cb, void *baton)
-{
- reg->notify = cb;
- reg->notify_ctx = baton;
-}
-
md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p)
{
- md_job_t *job;
-
- job = md_job_make(p, reg->store, MD_SG_STAGING, mdomain);
- md_job_set_notify_cb(job, reg->notify, reg->notify_ctx);
- return job;
+ return md_job_make(p, reg->store, MD_SG_STAGING, mdomain);
}
diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h
index b656d5c0a3..c677d60687 100644
--- a/modules/md/md_reg.h
+++ b/modules/md/md_reg.h
@@ -22,6 +22,7 @@ struct apr_array_header_t;
struct md_pkey_t;
struct md_cert_t;
struct md_result_t;
+struct md_pkey_spec_t;
#include "md_store.h"
@@ -35,7 +36,7 @@ typedef struct md_reg_t md_reg_t;
* Create the MD registry, using the pool and store.
*/
apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, md_store_t *store,
- const char *proxy_url);
+ const char *proxy_url, const char *ca_file);
md_store_t *md_reg_store_get(md_reg_t *reg);
@@ -112,7 +113,7 @@ apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p,
* of the domain and going up the issuers. Returns APR_ENOENT when not available.
*/
apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg,
- const md_t *md, apr_pool_t *p);
+ const md_t *md, struct md_pkey_spec_t *spec, apr_pool_t *p);
/**
* Get the filenames of private key and pubcert of the MD - if they exist.
@@ -120,7 +121,7 @@ apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg,
*/
apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile,
md_reg_t *reg, md_store_group_t group,
- const md_t *md, apr_pool_t *p);
+ const md_t *md, struct md_pkey_spec_t *spec, apr_pool_t *p);
/**
* Synchronise the give master mds with the store.
@@ -173,6 +174,12 @@ int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p);
apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p);
/**
+ * Return the timestamp up to which *all* certificates for the MD can be used.
+ * A value of 0 indicates that there is no certificate.
+ */
+apr_time_t md_reg_valid_until(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
+/**
* Return if a warning should be issued about the certificate expiration.
* This applies the configured warn window to the remaining lifetime of the
* current certiciate. If no certificate is present, this returns 0.
@@ -199,6 +206,7 @@ struct md_proto_driver_t {
md_reg_t *reg;
md_store_t *store;
const char *proxy_url;
+ const char *ca_file;
const md_t *md;
int can_http;
@@ -254,7 +262,6 @@ apr_status_t md_reg_load_staging(md_reg_t *reg, const md_t *md, struct apr_table
void md_reg_set_renew_window_default(md_reg_t *reg, md_timeslice_t *renew_window);
void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window);
-void md_reg_set_notify_cb(md_reg_t *reg, md_job_notify_cb *cb, void *baton);
struct md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p);
#endif /* mod_md_md_reg_h */
diff --git a/modules/md/md_result.c b/modules/md/md_result.c
index 29996e94ca..64a2f70bac 100644
--- a/modules/md/md_result.c
+++ b/modules/md/md_result.c
@@ -260,3 +260,26 @@ void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *dat
result->on_change = cb;
result->on_change_data = data;
}
+
+apr_status_t md_result_raise(md_result_t *result, const char *event, apr_pool_t *p)
+{
+ if (result->on_raise) return result->on_raise(result, result->on_raise_data, event, p);
+ return APR_SUCCESS;
+}
+
+void md_result_holler(md_result_t *result, const char *event, apr_pool_t *p)
+{
+ if (result->on_holler) result->on_holler(result, result->on_holler_data, event, p);
+}
+
+void md_result_on_raise(md_result_t *result, md_result_raise_cb *cb, void *data)
+{
+ result->on_raise = cb;
+ result->on_raise_data = data;
+}
+
+void md_result_on_holler(md_result_t *result, md_result_holler_cb *cb, void *data)
+{
+ result->on_holler = cb;
+ result->on_holler_data = data;
+}
diff --git a/modules/md/md_result.h b/modules/md/md_result.h
index 58e903e08a..e83bdd2223 100644
--- a/modules/md/md_result.h
+++ b/modules/md/md_result.h
@@ -23,6 +23,8 @@ struct md_t;
typedef struct md_result_t md_result_t;
typedef void md_result_change_cb(md_result_t *result, void *data);
+typedef apr_status_t md_result_raise_cb(md_result_t *result, void *data, const char *event, apr_pool_t *p);
+typedef void md_result_holler_cb(md_result_t *result, void *data, const char *event, apr_pool_t *p);
struct md_result_t {
apr_pool_t *p;
@@ -35,6 +37,10 @@ struct md_result_t {
apr_time_t ready_at;
md_result_change_cb *on_change;
void *on_change_data;
+ md_result_raise_cb *on_raise;
+ void *on_raise_data;
+ md_result_holler_cb *on_holler;
+ void *on_holler_data;
};
md_result_t *md_result_make(apr_pool_t *p, apr_status_t status);
@@ -70,4 +76,12 @@ void md_result_log(md_result_t *result, unsigned int level);
void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data);
+/* events in the context of a result genesis */
+
+apr_status_t md_result_raise(md_result_t *result, const char *event, apr_pool_t *p);
+void md_result_holler(md_result_t *result, const char *event, apr_pool_t *p);
+
+void md_result_on_raise(md_result_t *result, md_result_raise_cb *cb, void *data);
+void md_result_on_holler(md_result_t *result, md_result_holler_cb *cb, void *data);
+
#endif /* mod_md_md_result_h */
diff --git a/modules/md/md_status.c b/modules/md/md_status.c
index 3c3d801400..0bddd7eb18 100644
--- a/modules/md/md_status.c
+++ b/modules/md/md_status.c
@@ -25,7 +25,9 @@
#include "md_json.h"
#include "md.h"
+#include "md_acme.h"
#include "md_crypt.h"
+#include "md_event.h"
#include "md_log.h"
#include "md_ocsp.h"
#include "md_store.h"
@@ -89,29 +91,6 @@ leave:
/**************************************************************************************************/
/* md status information */
-static apr_status_t get_staging_cert_json(md_json_t **pjson, apr_pool_t *p,
- md_reg_t *reg, const md_t *md)
-{
- md_json_t *json = NULL;
- apr_array_header_t *certs;
- md_cert_t *cert;
- apr_status_t rv = APR_SUCCESS;
-
- rv = md_pubcert_load(md_reg_store_get(reg), MD_SG_STAGING, md->name, &certs, p);
- if (APR_STATUS_IS_ENOENT(rv)) {
- rv = APR_SUCCESS;
- goto leave;
- }
- else if (APR_SUCCESS != rv || certs->nelts == 0) {
- goto leave;
- }
- cert = APR_ARRAY_IDX(certs, 0, md_cert_t *);
- rv = status_get_cert_json(&json, cert, p);
-leave:
- *pjson = (APR_SUCCESS == rv)? json : NULL;
- return rv;
-}
-
static apr_status_t job_loadj(md_json_t **pjson, md_store_group_t group, const char *name,
struct md_reg_t *reg, int with_log, apr_pool_t *p)
{
@@ -123,22 +102,25 @@ static apr_status_t job_loadj(md_json_t **pjson, md_store_group_t group, const c
return rv;
}
-static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
- md_reg_t *reg, md_ocsp_reg_t *ocsp,
- int with_logs, apr_pool_t *p)
+static apr_status_t status_get_certs_json(md_json_t **pjson, apr_array_header_t *certs,
+ const md_t *md, md_reg_t *reg,
+ md_ocsp_reg_t *ocsp, int with_logs,
+ apr_pool_t *p)
{
- md_json_t *mdj, *jobj, *certj;
- int renew;
- const md_pubcert_t *pubcert;
- const md_cert_t *cert = NULL;
+ md_json_t *json, *certj, *jobj;
+ md_timeperiod_t certs_valid = {0, 0}, valid, ocsp_valid;
+ md_pkey_spec_t *spec;
+ md_cert_t *cert;
md_ocsp_cert_stat_t cert_stat;
- md_timeperiod_t ocsp_valid;
- apr_status_t rv = APR_SUCCESS;
- apr_time_t renew_at;
-
- mdj = md_to_json(md, p);
- if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, p)) {
- cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
+ int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ json = md_json_create(p);
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ cert = APR_ARRAY_IDX(certs, i, md_cert_t*);
+ if (!cert) continue;
+
if (APR_SUCCESS != (rv = status_get_cert_json(&certj, cert, p))) goto leave;
if (md->stapling && ocsp) {
rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md);
@@ -152,12 +134,73 @@ static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
md_json_setj(jobj, certj, MD_KEY_OCSP, MD_KEY_RENEWAL, NULL);
}
}
- md_json_setj(certj, mdj, MD_KEY_CERT, NULL);
+ valid = md_cert_get_valid(cert);
+ certs_valid = i? md_timeperiod_common(&certs_valid, &valid) : valid;
+ md_json_setj(certj, json, md_pkey_spec_name(spec), NULL);
+ }
+
+ if (certs_valid.start) {
+ md_json_set_timeperiod(&certs_valid, json, MD_KEY_VALID, NULL);
+ }
+leave:
+ *pjson = (APR_SUCCESS == rv)? json : NULL;
+ return rv;
+}
- renew_at = md_reg_renew_at(reg, md, p);
- if (renew_at) {
- md_json_set_time(renew_at, mdj, MD_KEY_RENEW_AT, NULL);
+static apr_status_t get_staging_certs_json(md_json_t **pjson, const md_t *md,
+ md_reg_t *reg, apr_pool_t *p)
+{
+ md_pkey_spec_t *spec;
+ int i;
+ apr_array_header_t *chain, *certs;
+ const md_cert_t *cert;
+ apr_status_t rv;
+
+ certs = apr_array_make(p, 5, sizeof(md_cert_t*));
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ cert = NULL;
+ rv = md_pubcert_load(md_reg_store_get(reg), MD_SG_STAGING, md->name, spec, &chain, p);
+ if (APR_SUCCESS == rv) {
+ cert = APR_ARRAY_IDX(chain, 0, const md_cert_t*);
}
+ APR_ARRAY_PUSH(certs, const md_cert_t*) = cert;
+ }
+ return status_get_certs_json(pjson, certs, md, reg, NULL, 0, p);
+}
+
+static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
+ md_reg_t *reg, md_ocsp_reg_t *ocsp,
+ int with_logs, apr_pool_t *p)
+{
+ md_json_t *mdj, *certsj, *jobj;
+ int renew;
+ const md_pubcert_t *pubcert;
+ const md_cert_t *cert = NULL;
+ apr_array_header_t *certs;
+ apr_status_t rv = APR_SUCCESS;
+ apr_time_t renew_at;
+ md_pkey_spec_t *spec;
+ int i;
+
+ mdj = md_to_json(md, p);
+ certs = apr_array_make(p, 5, sizeof(md_cert_t*));
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ cert = NULL;
+ if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, spec, p)) {
+ cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
+ }
+ APR_ARRAY_PUSH(certs, const md_cert_t*) = cert;
+ }
+
+ rv = status_get_certs_json(&certsj, certs, md, reg, ocsp, with_logs, p);
+ if (APR_SUCCESS != rv) goto leave;
+ md_json_setj(certsj, mdj, MD_KEY_CERT, NULL);
+
+ renew_at = md_reg_renew_at(reg, md, p);
+ if (renew_at > 0) {
+ md_json_set_time(renew_at, mdj, MD_KEY_RENEW_AT, NULL);
}
md_json_setb(md->stapling, mdj, MD_KEY_STAPLING, NULL);
@@ -167,8 +210,8 @@ static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md,
md_json_setb(renew, mdj, MD_KEY_RENEW, NULL);
rv = job_loadj(&jobj, MD_SG_STAGING, md->name, reg, with_logs, p);
if (APR_SUCCESS == rv) {
- if (APR_SUCCESS == get_staging_cert_json(&certj, p, reg, md)) {
- md_json_setj(certj, jobj, MD_KEY_CERT, NULL);
+ if (APR_SUCCESS == get_staging_certs_json(&certsj, md, reg, p)) {
+ md_json_setj(certsj, jobj, MD_KEY_CERT, NULL);
}
md_json_setj(jobj, mdj, MD_KEY_RENEWAL, NULL);
}
@@ -236,6 +279,7 @@ static void md_job_from_json(md_job_t *job, md_json_t *json, apr_pool_t *p)
/*job->mdomain = md_json_gets(json, MD_KEY_NAME, NULL);*/
job->finished = md_json_getb(json, MD_KEY_FINISHED, NULL);
job->notified = md_json_getb(json, MD_KEY_NOTIFIED, NULL);
+ job->notified_renewed = md_json_getb(json, MD_KEY_NOTIFIED_RENEWED, NULL);
s = md_json_dups(p, json, MD_KEY_NEXT_RUN, NULL);
if (s && *s) job->next_run = apr_date_parse_rfc(s);
s = md_json_dups(p, json, MD_KEY_LAST_RUN, NULL);
@@ -257,6 +301,7 @@ static void job_to_json(md_json_t *json, const md_job_t *job,
md_json_sets(job->mdomain, json, MD_KEY_NAME, NULL);
md_json_setb(job->finished, json, MD_KEY_FINISHED, NULL);
md_json_setb(job->notified, json, MD_KEY_NOTIFIED, NULL);
+ md_json_setb(job->notified_renewed, json, MD_KEY_NOTIFIED_RENEWED, NULL);
if (job->next_run > 0) {
apr_rfc822_date(ts, job->next_run);
md_json_sets(ts, json, MD_KEY_NEXT_RUN, NULL);
@@ -447,6 +492,24 @@ static void job_result_update(md_result_t *result, void *data)
}
}
+static apr_status_t job_result_raise(md_result_t *result, void *data, const char *event, apr_pool_t *p)
+{
+ md_job_result_ctx *ctx = data;
+ (void)p;
+ if (result == ctx->job->observing) {
+ return md_job_notify(ctx->job, event, result);
+ }
+ return APR_SUCCESS;
+}
+
+static void job_result_holler(md_result_t *result, void *data, const char *event, apr_pool_t *p)
+{
+ md_job_result_ctx *ctx = data;
+ if (result == ctx->job->observing) {
+ md_event_holler(event, ctx->job->mdomain, ctx->job, result, p);
+ }
+}
+
static void job_observation_start(md_job_t *job, md_result_t *result, md_store_t *store)
{
md_job_result_ctx *ctx;
@@ -461,6 +524,8 @@ static void job_observation_start(md_job_t *job, md_result_t *result, md_store_t
ctx->last = md_result_md_make(result->p, APR_SUCCESS);
md_result_assign(ctx->last, result);
md_result_on_change(result, job_result_update, ctx);
+ md_result_on_raise(result, job_result_raise, ctx);
+ md_result_on_holler(result, job_result_holler, ctx);
}
static void job_observation_end(md_job_t *job)
@@ -477,17 +542,36 @@ void md_job_start_run(md_job_t *job, md_result_t *result, md_store_t *store)
md_job_log_append(job, "starting", NULL, NULL);
}
-apr_time_t md_job_delay_on_errors(int err_count)
+apr_time_t md_job_delay_on_errors(md_job_t *job, int err_count, const char *last_problem)
{
- apr_time_t delay = 0;
-
- if (err_count > 0) {
+ apr_time_t delay = 0, max_delay = apr_time_from_sec(24*60*60); /* daily */
+ unsigned char c;
+
+ if (last_problem && md_acme_problem_is_input_related(last_problem)) {
+ /* If ACME server reported a problem and that problem indicates that our
+ * input values, e.g. our configuration, has something wrong, we always
+ * go to max delay as frequent retries are unlikely to resolve the situation.
+ * However, we should nevertheless retry daily, bc. it might be that there
+ * is a bug in the server. Unlikely, but... */
+ delay = max_delay;
+ }
+ else if (err_count > 0) {
/* back off duration, depending on the errors we encounter in a row */
delay = apr_time_from_sec(5 << (err_count - 1));
- if (delay > apr_time_from_sec(60*60)) {
- delay = apr_time_from_sec(60*60);
+ if (delay > max_delay) {
+ delay = max_delay;
}
}
+ if (delay > 0) {
+ /* jitter the delay by +/- 0-50%.
+ * Background: we see retries of jobs being too regular (e.g. all at midnight),
+ * possibly cumulating from many installations that restart their Apache at a
+ * fixed hour. This can contribute to an overload at the CA and a continuation
+ * of failure.
+ */
+ md_rand_bytes(&c, sizeof(c), job->p);
+ delay += apr_time_from_sec((apr_time_sec(delay) * (c - 128)) / 256);
+ }
return delay;
}
@@ -503,7 +587,7 @@ void md_job_end_run(md_job_t *job, md_result_t *result)
else {
++job->error_runs;
job->dirty = 1;
- job->next_run = apr_time_now() + md_job_delay_on_errors(job->error_runs);
+ job->next_run = apr_time_now() + md_job_delay_on_errors(job, job->error_runs, result->problem);
}
job_observation_end(job);
}
@@ -516,31 +600,20 @@ void md_job_retry_at(md_job_t *job, apr_time_t later)
apr_status_t md_job_notify(md_job_t *job, const char *reason, md_result_t *result)
{
- if (job->notify) return job->notify(job, reason, result, job->p, job->notify_ctx);
+ apr_status_t rv;
+
+ md_result_set(result, APR_SUCCESS, NULL);
+ rv = md_event_raise(reason, job->mdomain, job, result, job->p);
job->dirty = 1;
- if (APR_SUCCESS == result->status) {
+ if (APR_SUCCESS == rv && APR_SUCCESS == result->status) {
job->notified = 1;
+ if (!strcmp("renewed", reason)) job->notified_renewed = 1;
job->error_runs = 0;
}
else {
++job->error_runs;
- job->next_run = apr_time_now() + md_job_delay_on_errors(job->error_runs);
+ job->next_run = apr_time_now() + md_job_delay_on_errors(job, job->error_runs, result->problem);
}
return result->status;
}
-void md_job_holler(md_job_t *job, const char *reason)
-{
- md_result_t *result;
-
- if (job->notify) {
- result = md_result_make(job->p, APR_SUCCESS);
- job->notify(job, reason, result, job->p, job->notify_ctx);
- }
-}
-
-void md_job_set_notify_cb(md_job_t *job, md_job_notify_cb *cb, void *baton)
-{
- job->notify = cb;
- job->notify_ctx = baton;
-}
diff --git a/modules/md/md_status.h b/modules/md/md_status.h
index 3b25dbbff9..cd358b0e8e 100644
--- a/modules/md/md_status.h
+++ b/modules/md/md_status.h
@@ -59,6 +59,7 @@ struct md_job_t {
struct md_result_t *last_result; /* Result from last run */
int finished; /* true iff the job finished successfully */
int notified; /* true iff notifications were handled successfully */
+ int notified_renewed; /* true iff a 'renewed' notification was handled successfully */
apr_time_t valid_from; /* at which time the finished job results become valid, 0 if immediate */
int error_runs; /* Number of errored runs of an unfinished job */
int fatal_error; /* a fatal error is remedied by retrying */
@@ -67,9 +68,6 @@ struct md_job_t {
apr_size_t max_log; /* max number of log entries, new ones replace oldest */
int dirty;
struct md_result_t *observing;
-
- md_job_notify_cb *notify;
- void *notify_ctx;
};
/**
@@ -115,12 +113,12 @@ void md_job_start_run(md_job_t *job, struct md_result_t *result, md_store_t *sto
void md_job_end_run(md_job_t *job, struct md_result_t *result);
void md_job_retry_at(md_job_t *job, apr_time_t later);
-/* Given the number of errors encountered, recommend a delay for the next attempt */
-apr_time_t md_job_delay_on_errors(int err_count);
+/**
+ * Given the number of errors and the last problem encountered,
+ * recommend a delay for the next attempt of job
+ */
+apr_time_t md_job_delay_on_errors(md_job_t *job, int err_count, const char *last_problem);
-void md_job_set_notify_cb(md_job_t *job, md_job_notify_cb *cb, void *baton);
apr_status_t md_job_notify(md_job_t *job, const char *reason, struct md_result_t *result);
-/* Same as notify but without checks on success and no change to job */
-void md_job_holler(md_job_t *job, const char *reason);
#endif /* md_status_h */
diff --git a/modules/md/md_store.c b/modules/md/md_store.c
index ad5bb28cf2..29f3632d92 100644
--- a/modules/md/md_store.c
+++ b/modules/md/md_store.c
@@ -254,29 +254,89 @@ typedef struct {
apr_array_header_t *mds;
} md_load_ctx;
+static const char *pk_filename(const char *keyname, const char *base, apr_pool_t *p)
+{
+ char *s, *t;
+ /* We also run on various filesystems with difference upper/lower preserve matching
+ * rules. Normalize the names we use, since private key specifications are basically
+ * user input. */
+ s = (keyname && apr_strnatcasecmp("rsa", keyname))?
+ apr_pstrcat(p, base, ".", keyname, ".pem", NULL)
+ : apr_pstrcat(p, base, ".pem", NULL);
+ for (t = s; *t; t++ )
+ *t = (char)apr_tolower(*t);
+ return s;
+}
+
+const char *md_pkey_filename(md_pkey_spec_t *spec, apr_pool_t *p)
+{
+ return pk_filename(md_pkey_spec_name(spec), "privkey", p);
+}
+
+const char *md_chain_filename(md_pkey_spec_t *spec, apr_pool_t *p)
+{
+ return pk_filename(md_pkey_spec_name(spec), "pubcert", p);
+}
+
apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group, const char *name,
- md_pkey_t **ppkey, apr_pool_t *p)
+ md_pkey_spec_t *spec, md_pkey_t **ppkey, apr_pool_t *p)
{
- return md_store_load(store, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, (void**)ppkey, p);
+ const char *fname = md_pkey_filename(spec, p);
+ return md_store_load(store, group, name, fname, MD_SV_PKEY, (void**)ppkey, p);
}
apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name,
- struct md_pkey_t *pkey, int create)
+ md_pkey_spec_t *spec, struct md_pkey_t *pkey, int create)
{
- return md_store_save(store, p, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, pkey, create);
+ const char *fname = md_pkey_filename(spec, p);
+ return md_store_save(store, p, group, name, fname, MD_SV_PKEY, pkey, create);
}
apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name,
- struct apr_array_header_t **ppubcert, apr_pool_t *p)
+ md_pkey_spec_t *spec, struct apr_array_header_t **ppubcert,
+ apr_pool_t *p)
{
- return md_store_load(store, group, name, MD_FN_PUBCERT, MD_SV_CHAIN, (void**)ppubcert, p);
+ const char *fname = md_chain_filename(spec, p);
+ return md_store_load(store, group, name, fname, MD_SV_CHAIN, (void**)ppubcert, p);
}
apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p,
md_store_group_t group, const char *name,
- struct apr_array_header_t *pubcert, int create)
+ md_pkey_spec_t *spec, struct apr_array_header_t *pubcert, int create)
+{
+ const char *fname = md_chain_filename(spec, p);
+ return md_store_save(store, p, group, name, fname, MD_SV_CHAIN, pubcert, create);
+}
+
+apr_status_t md_creds_load(md_store_t *store, md_store_group_t group, const char *name,
+ md_pkey_spec_t *spec, md_credentials_t **pcreds, apr_pool_t *p)
{
- return md_store_save(store, p, group, name, MD_FN_PUBCERT, MD_SV_CHAIN, pubcert, create);
+ md_credentials_t *creds = apr_pcalloc(p, sizeof(*creds));
+ apr_status_t rv;
+
+ creds->spec = spec;
+ if (APR_SUCCESS != (rv = md_pkey_load(store, group, name, spec, &creds->pkey, p))) {
+ goto leave;
+ }
+ /* chain is optional */
+ rv = md_pubcert_load(store, group, name, spec, &creds->chain, p);
+ if (APR_STATUS_IS_ENOENT(rv)) rv = APR_SUCCESS;
+leave:
+ *pcreds = (APR_SUCCESS == rv)? creds : NULL;
+ return rv;
+}
+
+apr_status_t md_creds_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, md_credentials_t *creds, int create)
+{
+ apr_status_t rv;
+
+ if (APR_SUCCESS != (rv = md_pkey_save(store, p, group, name, creds->spec, creds->pkey, create))) {
+ goto leave;
+ }
+ rv = md_pubcert_save(store, p, group, name, creds->spec, creds->chain, create);
+leave:
+ return rv;
}
typedef struct {
diff --git a/modules/md/md_store.h b/modules/md/md_store.h
index dfe9f32d6d..e252c27909 100644
--- a/modules/md/md_store.h
+++ b/modules/md/md_store.h
@@ -20,6 +20,7 @@
struct apr_array_header_t;
struct md_cert_t;
struct md_pkey_t;
+struct md_pkey_spec_t;
const char *md_store_group_name(unsigned int group);
@@ -72,13 +73,19 @@ typedef enum {
#define MD_FN_MD "md.json"
#define MD_FN_JOB "job.json"
+#define MD_FN_HTTPD_JSON "httpd.json"
+
+/* The corresponding names for current cert & key files are constructed
+ * in md_store and md_crypt.
+ */
+
+/* These three legacy filenames are only used in md_store_fs to
+ * upgrade 1.0 directories. They should not be used for any other
+ * purpose.
+ */
#define MD_FN_PRIVKEY "privkey.pem"
#define MD_FN_PUBCERT "pubcert.pem"
#define MD_FN_CERT "cert.pem"
-#define MD_FN_HTTPD_JSON "httpd.json"
-
-#define MD_FN_FALLBACK_PKEY "fallback-privkey.pem"
-#define MD_FN_FALLBACK_CERT "fallback-cert.pem"
/**
* Load the JSON value at key "group/name/aspect", allocated from pool p.
@@ -218,18 +225,40 @@ apr_status_t md_store_md_iter(md_store_md_inspect *inspect, void *baton, md_stor
apr_pool_t *p, md_store_group_t group, const char *pattern);
+const char *md_pkey_filename(struct md_pkey_spec_t *spec, apr_pool_t *p);
+const char *md_chain_filename(struct md_pkey_spec_t *spec, apr_pool_t *p);
+
apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group,
- const char *name, struct md_pkey_t **ppkey, apr_pool_t *p);
+ const char *name, struct md_pkey_spec_t *spec,
+ struct md_pkey_t **ppkey, apr_pool_t *p);
apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
- const char *name, struct md_pkey_t *pkey, int create);
+ const char *name, struct md_pkey_spec_t *spec,
+ struct md_pkey_t *pkey, int create);
apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name,
- struct apr_array_header_t **ppubcert, apr_pool_t *p);
+ struct md_pkey_spec_t *spec, struct apr_array_header_t **ppubcert,
+ apr_pool_t *p);
apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p,
md_store_group_t group, const char *name,
+ struct md_pkey_spec_t *spec,
struct apr_array_header_t *pubcert, int create);
/**************************************************************************************************/
+/* X509 complete credentials */
+
+typedef struct md_credentials_t md_credentials_t;
+struct md_credentials_t {
+ struct md_pkey_spec_t *spec;
+ struct md_pkey_t *pkey;
+ struct apr_array_header_t *chain;
+};
+
+apr_status_t md_creds_load(md_store_t *store, md_store_group_t group, const char *name,
+ struct md_pkey_spec_t *spec, md_credentials_t **pcreds, apr_pool_t *p);
+apr_status_t md_creds_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+ const char *name, md_credentials_t *creds, int create);
+
+/**************************************************************************************************/
/* implementation interface */
typedef apr_status_t md_store_load_cb(md_store_t *store, md_store_group_t group,
diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c
index 6553fdd706..7ba822a4e2 100644
--- a/modules/md/md_store_fs.c
+++ b/modules/md/md_store_fs.c
@@ -256,6 +256,11 @@ static apr_status_t setup_store_file(void *baton, apr_pool_t *p, apr_pool_t *pte
(void)ap;
s_fs->plain_pkey[MD_SG_DOMAINS] = 1;
+ /* Added: the encryption of tls-alpn-01 certificate keys is not a security issue
+ * for these self-signed, short-lived certificates. Having them unencrypted let's
+ * use pass around the files insteak of an *SSL implementation dependent PKEY_something.
+ */
+ s_fs->plain_pkey[MD_SG_CHALLENGES] = 1;
s_fs->plain_pkey[MD_SG_TMP] = 1;
if (!MD_OK(md_util_path_merge(&fname, ptemp, s_fs->base, FS_STORE_JSON, NULL))) {
diff --git a/modules/md/md_time.c b/modules/md/md_time.c
index 8076d5be46..268ca83c1b 100644
--- a/modules/md/md_time.c
+++ b/modules/md/md_time.c
@@ -311,3 +311,15 @@ int md_timeslice_eq(const md_timeslice_t *ts1, const md_timeslice_t *ts2)
if (!ts1 || !ts2) return 0;
return (ts1->norm == ts2->norm) && (ts1->len == ts2->len);
}
+
+md_timeperiod_t md_timeperiod_common(const md_timeperiod_t *a, const md_timeperiod_t *b)
+{
+ md_timeperiod_t c;
+
+ c.start = (a->start > b->start)? a->start : b->start;
+ c.end = (a->end < b->end)? a->end : b->end;
+ if (c.start > c.end) {
+ c.start = c.end = 0;
+ }
+ return c;
+}
diff --git a/modules/md/md_time.h b/modules/md/md_time.h
index 88e28b20cb..92bd9d8aa9 100644
--- a/modules/md/md_time.h
+++ b/modules/md/md_time.h
@@ -36,6 +36,11 @@ int md_timeperiod_has_started(const md_timeperiod_t *period, apr_time_t time);
int md_timeperiod_has_ended(const md_timeperiod_t *period, apr_time_t time);
apr_interval_time_t md_timeperiod_remaining(const md_timeperiod_t *period, apr_time_t time);
+/**
+ * Return the timeperiod common between a and b. If both do not overlap, return {0,0}.
+ */
+md_timeperiod_t md_timeperiod_common(const md_timeperiod_t *a, const md_timeperiod_t *b);
+
char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period);
/**
diff --git a/modules/md/md_util.c b/modules/md/md_util.c
index 25830f2b92..d0c23f9678 100644
--- a/modules/md/md_util.c
+++ b/modules/md/md_util.c
@@ -25,6 +25,10 @@
#include <apr_tables.h>
#include <apr_uri.h>
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
#include "md.h"
#include "md_log.h"
#include "md_util.h"
@@ -153,7 +157,7 @@ int md_array_remove_at(struct apr_array_header_t *a, int idx)
else {
ps = (a->elts + (idx * a->elt_size));
pe = ps + a->elt_size;
- memmove(ps, pe, (a->nelts - (idx+1)) * a->elt_size);
+ memmove(ps, pe, (size_t)((a->nelts - (idx+1)) * a->elt_size));
--a->nelts;
}
return 1;
@@ -223,7 +227,7 @@ int md_array_str_eq(const struct apr_array_header_t *a1,
const char *s1, *s2;
if (a1 == a2) return 1;
- if (!a1) return 0;
+ if (!a1 || !a2) return 0;
if (a1->nelts != a2->nelts) return 0;
for (i = 0; i < a1->nelts; ++i) {
s1 = APR_ARRAY_IDX(a1, i, const char *);
@@ -1010,23 +1014,31 @@ apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs,
/* execute process ********************************************************************************/
apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv,
- int *exit_code)
+ apr_array_header_t *env, int *exit_code)
{
apr_status_t rv;
apr_procattr_t *procattr;
apr_proc_t *proc;
apr_exit_why_e ewhy;
+ const char * const *envp = NULL;
char buffer[1024];
*exit_code = 0;
if (!(proc = apr_pcalloc(p, sizeof(*proc)))) {
return APR_ENOMEM;
}
+ if (env && env->nelts > 0) {
+ apr_array_header_t *nenv;
+
+ nenv = apr_array_copy(p, env);
+ APR_ARRAY_PUSH(nenv, const char *) = NULL;
+ envp = (const char * const *)nenv->elts;
+ }
if ( APR_SUCCESS == (rv = apr_procattr_create(&procattr, p))
&& APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE,
APR_NO_PIPE, APR_FULL_BLOCK))
&& APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
- && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, NULL, procattr, p))) {
+ && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, envp, procattr, p))) {
/* read stderr and log on INFO for possible fault analysis. */
while(APR_SUCCESS == (rv = apr_file_gets(buffer, sizeof(buffer)-1, proc->err))) {
@@ -1472,3 +1484,23 @@ const char *md_link_find_relation(const apr_table_t *headers,
return ctx.url;
}
+const char *md_util_parse_ct(apr_pool_t *pool, const char *cth)
+{
+ char *type;
+ const char *p;
+ apr_size_t hlen;
+
+ if (!cth) return NULL;
+
+ for( p = cth; *p && *p != ' ' && *p != ';'; ++p)
+ ;
+ hlen = (apr_size_t)(p - cth);
+ type = apr_pcalloc( pool, hlen + 1 );
+ assert(type);
+ memcpy(type, cth, hlen);
+ type[hlen] = '\0';
+
+ return type;
+ /* Could parse and return parameters here, but we don't need any at present.
+ */
+}
diff --git a/modules/md/md_util.h b/modules/md/md_util.h
index 3d92756459..0fc00256cf 100644
--- a/modules/md/md_util.h
+++ b/modules/md/md_util.h
@@ -100,8 +100,9 @@ int md_array_str_add_missing(struct apr_array_header_t *dest,
/**************************************************************************************************/
/* process execution */
+
apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv,
- int *exit_code);
+ struct apr_array_header_t *env, int *exit_code);
/**************************************************************************************************/
/* dns name check */
@@ -207,6 +208,7 @@ apr_status_t md_util_abs_http_uri_check(apr_pool_t *p, const char *uri, const ch
const char *md_link_find_relation(const struct apr_table_t *headers,
apr_pool_t *pool, const char *relation);
+const char *md_util_parse_ct(apr_pool_t *pool, const char *cth);
/**************************************************************************************************/
/* retry logic */
diff --git a/modules/md/md_version.h b/modules/md/md_version.h
index 21f5de1c73..7e25c854a9 100644
--- a/modules/md/md_version.h
+++ b/modules/md/md_version.h
@@ -27,7 +27,7 @@
* @macro
* Version number of the md module as c string
*/
-#define MOD_MD_VERSION "2.2.7"
+#define MOD_MD_VERSION "2.4.0"
/**
* @macro
@@ -35,7 +35,7 @@
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_MD_VERSION_NUM 0x020207
+#define MOD_MD_VERSION_NUM 0x020400
#define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory"
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c
index dac239527f..f2e199bd92 100644
--- a/modules/md/mod_md.c
+++ b/modules/md/mod_md.c
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+
#include <assert.h>
#include <apr_optional.h>
#include <apr_strings.h>
@@ -32,6 +32,7 @@
#include "md.h"
#include "md_curl.h"
#include "md_crypt.h"
+#include "md_event.h"
#include "md_http.h"
#include "md_json.h"
#include "md_store.h"
@@ -86,12 +87,12 @@ static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level)
#define LOG_BUF_LEN 16*1024
-static void log_print(const char *file, int line, md_log_level_t level,
+static void log_print(const char *file, int line, md_log_level_t level,
apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap)
{
if (log_is_level(baton, p, level)) {
char buffer[LOG_BUF_LEN];
-
+
memset(buffer, 0, sizeof(buffer));
apr_vsnprintf(buffer, LOG_BUF_LEN-1, fmt, ap);
buffer[LOG_BUF_LEN-1] = '\0';
@@ -108,11 +109,9 @@ static void log_print(const char *file, int line, md_log_level_t level,
/**************************************************************************************************/
/* mod_ssl interface */
-static APR_OPTIONAL_FN_TYPE(ssl_is_https) *opt_ssl_is_https;
-
static void init_ssl(void)
{
- opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+ /* nop */
}
/**************************************************************************************************/
@@ -125,7 +124,7 @@ static apr_status_t cleanup_setups(void *dummy)
return APR_SUCCESS;
}
-static void init_setups(apr_pool_t *p, server_rec *base_server)
+static void init_setups(apr_pool_t *p, server_rec *base_server)
{
log_server = base_server;
apr_pool_cleanup_register(p, NULL, cleanup_setups, apr_pool_cleanup_null);
@@ -140,7 +139,8 @@ typedef struct {
} notify_rate;
static notify_rate notify_rates[] = {
- { "renewed", apr_time_from_sec(28 * MD_SECS_PER_DAY) }, /* once per month */
+ { "renewing", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */
+ { "renewed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
{ "installed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
{ "expiring", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
{ "errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */
@@ -148,7 +148,7 @@ static notify_rate notify_rates[] = {
{ "ocsp-errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */
};
-static apr_status_t notify(md_job_t *job, const char *reason,
+static apr_status_t notify(md_job_t *job, const char *reason,
md_result_t *result, apr_pool_t *p, void *baton)
{
md_mod_conf_t *mc = baton;
@@ -160,7 +160,7 @@ static apr_status_t notify(md_job_t *job, const char *reason,
md_timeperiod_t since_last;
const char *log_msg_reason;
int i;
-
+
log_msg_reason = apr_psprintf(p, "message-%s", reason);
for (i = 0; i < (int)(sizeof(notify_rates)/sizeof(notify_rates[0])); ++i) {
if (!strcmp(reason, notify_rates[i].reason)) {
@@ -170,69 +170,79 @@ static apr_status_t notify(md_job_t *job, const char *reason,
if (min_interim > 0) {
since_last.start = md_job_log_get_time_of_latest(job, log_msg_reason);
since_last.end = apr_time_now();
- if (md_timeperiod_length(&since_last) < min_interim) {
+ if (since_last.start > 0 && md_timeperiod_length(&since_last) < min_interim) {
/* not enough time has passed since we sent the last notification
* for this reason. */
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, APLOGNO()
+ "%s: rate limiting notification about '%s'", job->mdomain, reason);
return APR_SUCCESS;
}
}
-
+
if (!strcmp("renewed", reason)) {
if (mc->notify_cmd) {
- cmdline = apr_psprintf(p, "%s %s", mc->notify_cmd, job->mdomain);
+ cmdline = apr_psprintf(p, "%s %s", mc->notify_cmd, job->mdomain);
apr_tokenize_to_argv(cmdline, (char***)&argv, p);
- rv = md_util_exec(p, argv[0], argv, &exit_code);
-
+ rv = md_util_exec(p, argv[0], argv, NULL, &exit_code);
+
if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
if (APR_SUCCESS != rv) {
- md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)),
- "MDNotifyCmd %s failed with exit code %d.",
+ md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)),
+ "MDNotifyCmd %s failed with exit code %d.",
mc->notify_cmd, exit_code);
md_result_log(result, MD_LOG_ERR);
md_job_log_append(job, "notify-error", result->problem, result->detail);
return rv;
}
}
- md_log_perror(MD_LOG_MARK, MD_LOG_NOTICE, 0, p, APLOGNO(10059)
+ md_log_perror(MD_LOG_MARK, MD_LOG_NOTICE, 0, p, APLOGNO(10059)
"The Managed Domain %s has been setup and changes "
"will be activated on next (graceful) server restart.", job->mdomain);
}
if (mc->message_cmd) {
- cmdline = apr_psprintf(p, "%s %s %s", mc->message_cmd, reason, job->mdomain);
+ cmdline = apr_psprintf(p, "%s %s %s", mc->message_cmd, reason, job->mdomain);
apr_tokenize_to_argv(cmdline, (char***)&argv, p);
- rv = md_util_exec(p, argv[0], argv, &exit_code);
-
+ rv = md_util_exec(p, argv[0], argv, NULL, &exit_code);
+
if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
if (APR_SUCCESS != rv) {
- md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)),
- "MDMessageCmd %s failed with exit code %d.",
+ md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)),
+ "MDMessageCmd %s failed with exit code %d.",
mc->message_cmd, exit_code);
md_result_log(result, MD_LOG_ERR);
md_job_log_append(job, "message-error", reason, result->detail);
return rv;
}
}
+
md_job_log_append(job, log_msg_reason, NULL, NULL);
return APR_SUCCESS;
}
+static apr_status_t on_event(const char *event, const char *mdomain, void *baton,
+ md_job_t *job, md_result_t *result, apr_pool_t *p)
+{
+ (void)mdomain;
+ return notify(job, event, result, p, baton);
+}
+
/**************************************************************************************************/
/* store setup */
static apr_status_t store_file_ev(void *baton, struct md_store_t *store,
- md_store_fs_ev_t ev, unsigned int group,
- const char *fname, apr_filetype_e ftype,
+ md_store_fs_ev_t ev, unsigned int group,
+ const char *fname, apr_filetype_e ftype,
apr_pool_t *p)
{
server_rec *s = baton;
apr_status_t rv;
-
+
(void)store;
- ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)",
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)",
ev, (ftype == APR_DIR)? "dir" : "file", fname, group);
-
- /* Directories in group CHALLENGES, STAGING and OCSP are written to
- * under a different user. Give her ownership.
+
+ /* Directories in group CHALLENGES, STAGING and OCSP are written to
+ * under a different user. Give her ownership.
*/
if (ftype == APR_DIR) {
switch (group) {
@@ -244,19 +254,19 @@ static apr_status_t store_file_ev(void *baton, struct md_store_t *store,
return rv;
}
break;
- default:
+ default:
break;
}
}
return APR_SUCCESS;
}
-static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group,
+static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group,
apr_pool_t *p, server_rec *s)
{
const char *dir;
apr_status_t rv;
-
+
if (APR_SUCCESS == (rv = md_store_get_fname(&dir, store, group, NULL, NULL, p))
&& APR_SUCCESS == (rv = apr_dir_make_recursive(dir, MD_FPROT_D_UALL_GREAD, p))) {
rv = store_file_ev(s, store, MD_S_FS_EV_CREATED, group, dir, APR_DIR, p);
@@ -264,14 +274,14 @@ static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group,
return rv;
}
-static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc,
+static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc,
apr_pool_t *p, server_rec *s)
{
const char *base_dir;
apr_status_t rv;
-
+
base_dir = ap_server_root_relative(p, mc->base_dir);
-
+
if (APR_SUCCESS != (rv = md_store_fs_init(pstore, p, base_dir))) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10046)"setup store for %s", base_dir);
goto leave;
@@ -283,11 +293,11 @@ static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc,
|| APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_ACCOUNTS, p, s))
|| APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_OCSP, p, s))
) {
- ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047)
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047)
"setup challenges directory");
goto leave;
}
-
+
leave:
return rv;
}
@@ -332,10 +342,9 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
}
if (!md->ca_challenges && md->sc->ca_challenges) {
md->ca_challenges = apr_array_copy(p, md->sc->ca_challenges);
- }
- if (!md->pkey_spec) {
- md->pkey_spec = md->sc->pkey_spec;
-
+ }
+ if (md_pkeys_spec_is_empty(md->pks)) {
+ md->pks = md->sc->pks;
}
if (md->require_https < 0) {
md->require_https = md_config_geti(md->sc, MD_CONFIG_REQUIRE_HTTPS);
@@ -348,7 +357,7 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
}
}
-static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s,
+static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s,
int *pupdates, apr_pool_t *p)
{
if (md_contains(md, domain, 0)) {
@@ -374,16 +383,16 @@ static apr_status_t md_cover_server(md_t *md, server_rec *s, int *pupdates, apr_
apr_status_t rv;
const char *name;
int i;
-
+
if (APR_SUCCESS == (rv = check_coverage(md, s->server_hostname, s, pupdates, p))) {
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
"md[%s]: auto add, covers name %s", md->name, s->server_hostname);
for (i = 0; s->names && i < s->names->nelts; ++i) {
name = APR_ARRAY_IDX(s->names, i, const char*);
if (APR_SUCCESS != (rv = check_coverage(md, name, s, pupdates, p))) {
break;
}
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
"md[%s]: auto add, covers alias %s", md->name, name);
}
}
@@ -407,7 +416,7 @@ static int uses_port(server_rec *s, int port)
return match;
}
-static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s,
+static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s,
apr_pool_t *p, int log_level)
{
ap_listen_rec *lr;
@@ -415,11 +424,11 @@ static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s,
int can_http, can_https;
if (mc->can_http >= 0 && mc->can_https >= 0) goto set_and_leave;
-
+
can_http = can_https = 0;
for (lr = ap_listeners; lr; lr = lr->next) {
for (sa = lr->bind_addr; sa; sa = sa->next) {
- if (sa->port == mc->local_80
+ if (sa->port == mc->local_80
&& (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
can_http = 1;
}
@@ -429,13 +438,13 @@ static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s,
}
}
}
- if (mc->can_http < 0) mc->can_http = can_http;
+ if (mc->can_http < 0) mc->can_http = can_http;
if (mc->can_https < 0) mc->can_https = can_https;
ap_log_error(APLOG_MARK, log_level, 0, s, APLOGNO(10037)
"server seems%s reachable via http: and%s reachable via https:",
mc->can_http? "" : " not", mc->can_https? "" : " not");
set_and_leave:
- return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https);
+ return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https);
}
static server_rec *get_public_https_server(md_t *md, const char *domain, server_rec *base_server)
@@ -493,9 +502,9 @@ static apr_status_t auto_add_domains(md_t *md, server_rec *base_server, apr_pool
server_rec *s;
apr_status_t rv = APR_SUCCESS;
int updates;
-
+
/* Ad all domain names used in SSL VirtualHosts, if not already there */
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, base_server,
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, base_server,
"md[%s]: auto add domains", md->name);
updates = 0;
for (s = base_server; s; s = s->next) {
@@ -516,12 +525,12 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server)
server_rec *s;
int i;
const char *domain;
-
+
/* Collect those domains that support the "acme-tls/1" protocol. This
* is part of the MD (and not tested dynamically), since challenge selection
* may be done outside the server, e.g. in the a2md command. */
sc = md_config_get(base_server);
- mc = sc->mc;
+ mc = sc->mc;
apr_array_clear(md->acme_tls_1_domains);
for (i = 0; i < md->domains->nelts; ++i) {
domain = APR_ARRAY_IDX(md->domains, i, const char*);
@@ -536,7 +545,7 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server)
}
if (!ap_is_allowed_protocol(NULL, NULL, s, PROTO_ACME_TLS_1)) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10169)
- "%s: https server_rec for %s does not have protocol %s enabled",
+ "%s: https server_rec for %s does not have protocol %s enabled",
md->name, domain, PROTO_ACME_TLS_1);
continue;
}
@@ -544,7 +553,7 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server)
}
}
-static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *base_server,
+static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *base_server,
apr_pool_t *p)
{
server_rec *s;
@@ -552,7 +561,7 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *
md_srv_conf_t *sc;
int i;
const char *domain, *uri;
-
+
sc = md_config_get(base_server);
/* Assign the MD to all server_rec configs that it matches. If there already
@@ -564,23 +573,24 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *
/* we shall not assign ourselves to the base server */
continue;
}
-
+
r.server = s;
for (i = 0; i < md->domains->nelts; ++i) {
domain = APR_ARRAY_IDX(md->domains, i, const char*);
-
- if (ap_matches_request_vhost(&r, domain, s->port)) {
+
+ if (ap_matches_request_vhost(&r, domain, s->port)
+ || (md_dns_is_wildcard(p, domain) && md_dns_matches(domain, s->server_hostname))) {
/* Create a unique md_srv_conf_t record for this server, if there is none yet */
sc = md_config_get_unique(s, p);
if (!sc->assigned) sc->assigned = apr_array_make(p, 2, sizeof(md_t*));
-
+
APR_ARRAY_PUSH(sc->assigned, md_t*) = md;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10041)
"Server %s:%d matches md %s (config %s) for domain %s, "
- "has now %d MDs",
+ "has now %d MDs",
s->server_hostname, s->port, md->name, sc->name,
domain, (int)sc->assigned->nelts);
-
+
if (sc->ca_contact && sc->ca_contact[0]) {
uri = md_util_schemify(p, sc->ca_contact, "mailto");
if (md_array_str_index(md->contacts, uri, 0, 0) < 0) {
@@ -608,7 +618,7 @@ static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, apr_po
int i;
md_t *md;
apr_status_t rv = APR_SUCCESS;
-
+
apr_array_clear(mc->unused_names);
for (i = 0; i < mc->mds->nelts; ++i) {
md = APR_ARRAY_IDX(mc->mds, i, md_t*);
@@ -620,7 +630,7 @@ leave:
return rv;
}
-static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
+static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
server_rec *base_server, int log_level)
{
md_srv_conf_t *base_conf;
@@ -634,13 +644,13 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
* in the server. This list is collected during configuration processing and,
* in the post config phase, get updated from all merged server configurations
* before the server starts processing.
- */
+ */
base_conf = md_config_get(base_server);
md_config_get_timespan(&ts, base_conf, MD_CONFIG_RENEW_WINDOW);
if (ts) md_reg_set_renew_window_default(mc->reg, ts);
md_config_get_timespan(&ts, base_conf, MD_CONFIG_WARN_WINDOW);
if (ts) md_reg_set_warn_window_default(mc->reg, ts);
-
+
/* Complete the properties of the MDs, now that we have the complete, merged
* server configurations.
*/
@@ -660,7 +670,7 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
return APR_EINVAL;
}
}
-
+
if (md->cert_file && !md->pkey_file) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10170)
"The Managed Domain '%s', defined in %s(line %d), "
@@ -694,12 +704,12 @@ static void load_staged_data(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
md_t *md;
md_result_t *result;
int i;
-
+
for (i = 0; i < mc->mds->nelts; ++i) {
md = APR_ARRAY_IDX(mc->mds, i, md_t *);
result = md_result_md_make(p, md->name);
if (APR_SUCCESS == (rv = md_reg_load_staging(mc->reg, md, mc->env, result, p))) {
- ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068)
+ ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068)
"%s: staged set activated", md->name);
}
else if (!APR_STATUS_IS_ENOENT(rv)) {
@@ -713,13 +723,13 @@ static apr_status_t check_invalid_duplicates(server_rec *base_server)
{
server_rec *s;
md_srv_conf_t *sc;
-
- ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server,
+
+ ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server,
"checking duplicate ssl assignments");
for (s = base_server; s; s = s->next) {
sc = md_config_get(s);
if (!sc || !sc->assigned) continue;
-
+
if (sc->assigned->nelts > 1 && sc->is_ssl) {
/* duplicate assignment to SSL VirtualHost, not allowed */
ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10042)
@@ -731,7 +741,7 @@ static apr_status_t check_invalid_duplicates(server_rec *base_server)
return APR_SUCCESS;
}
-static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server,
+static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server,
apr_pool_t *p, apr_pool_t *ptemp)
{
server_rec *s;
@@ -776,7 +786,7 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *
md_t *md;
md_result_t *result;
int i, count;
-
+
/* Calculate the list of MD names which we need to watch:
* - all MDs that are used somewhere
* - all MDs in drive mode 'AUTO' that are not in 'unused_names'
@@ -788,7 +798,7 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *
md_result_set(result, APR_SUCCESS, NULL);
md->watched = 0;
if (md->state == MD_S_ERROR) {
- md_result_set(result, APR_EGENERAL,
+ md_result_set(result, APR_EGENERAL,
"in error state, unable to drive forward. This "
"indicates an incomplete or inconsistent configuration. "
"Please check the log for warnings in this regard.");
@@ -800,22 +810,22 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *
/* This MD is not used in any virtualhost, do not watch */
continue;
}
-
+
if (md_will_renew_cert(md)) {
/* make a test init to detect early errors. */
md_reg_test_init(mc->reg, md, mc->env, result, p);
if (APR_SUCCESS != result->status && result->detail) {
apr_hash_set(mc->init_errors, md->name, APR_HASH_KEY_STRING, apr_pstrdup(p, result->detail));
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10173)
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10173)
"md[%s]: %s", md->name, result->detail);
}
}
-
+
md->watched = 1;
++count;
}
return count;
-}
+}
static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
@@ -833,7 +843,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
/* At the first start, httpd makes a config check dry run. It
* runs all config hooks to check if it can. If so, it does
* this all again and starts serving requests.
- *
+ *
* On a dry run, we therefore do all the cheap config things we
* need to do to find out if the settings are ok. More expensive
* things we delay to the real run.
@@ -859,12 +869,14 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
mc = sc->mc;
mc->dry_run = dry_run;
+ md_event_init(p);
+ md_event_subscribe(on_event, mc);
+
if (APR_SUCCESS != (rv = setup_store(&store, mc, p, s))
- || APR_SUCCESS != (rv = md_reg_create(&mc->reg, p, store, mc->proxy_url))) {
+ || APR_SUCCESS != (rv = md_reg_create(&mc->reg, p, store, mc->proxy_url, mc->ca_certs))) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) "setup md registry");
goto leave;
}
- md_reg_set_notify_cb(mc->reg, notify, mc);
/* renew on 30% remaining /*/
rv = md_ocsp_reg_make(&mc->ocsp, p, store, mc->ocsp_renew_window,
@@ -873,8 +885,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10196) "setup ocsp registry");
goto leave;
}
- md_ocsp_set_notify_cb(mc->ocsp, notify, mc);
-
+
init_ssl();
/* How to bootstrap this module:
@@ -884,17 +895,17 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
* 4. Update the store with the MDs. Change domain names, create new MDs, etc.
* Basically all MD properties that are configured directly.
* WARNING: this may change the name of an MD. If an MD loses the first
- * of its domain names, it first gets the new first one as name. The
+ * of its domain names, it first gets the new first one as name. The
* store will find the old settings and "recover" the previous name.
* 5. Load any staged data from previous driving.
* 6. on a dry run, this is all we do
* 7. Read back the MD properties that reflect the existence and aspect of
- * credentials that are in the store (or missing there).
+ * credentials that are in the store (or missing there).
* Expiry times, MD state, etc.
* 8. Determine the list of MDs that need driving/supervision.
* 9. Cleanup any left-overs in registry/store that are no longer needed for
* the list of MDs as we know it now.
- * 10. If this list is non-empty, setup a watchdog to run.
+ * 10. If this list is non-empty, setup a watchdog to run.
*/
/*1*/
if (APR_SUCCESS != (rv = detect_supported_protocols(mc, s, p, log_level))) goto leave;
@@ -930,7 +941,7 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog,
/*6*/
if (!sc || !sc->mc || sc->mc->dry_run) goto leave;
mc = sc->mc;
-
+
/*7*/
if (APR_SUCCESS != (rv = check_invalid_duplicates(s))) {
goto leave;
@@ -956,16 +967,16 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog,
watched = init_cert_watch_status(mc, p, ptemp, s);
/*9*/
md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds);
-
- /* From here on, the domains in the registry are readonly
+
+ /* From here on, the domains in the registry are readonly
* and only staging/challenges may be manipulated */
md_reg_freeze_domains(mc->reg, mc->mds);
-
+
if (watched) {
/*10*/
ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10074)
"%d out of %d mds need watching", watched, mc->mds->nelts);
-
+
md_http_use_implementation(md_curl_get_impl(p));
rv = md_renew_start_watching(mc, s, p);
}
@@ -974,10 +985,10 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog,
}
if (!mc->ocsp || md_ocsp_count(mc->ocsp) == 0) goto leave;
-
+
md_http_use_implementation(md_curl_get_impl(p));
rv = md_ocsp_start_watching(mc, s, p);
-
+
leave:
return rv;
}
@@ -1006,7 +1017,7 @@ static int md_protocol_propose(conn_rec *c, request_rec *r,
apr_array_header_t *proposals)
{
(void)s;
- if (!r && offers && opt_ssl_is_https && opt_ssl_is_https(c)
+ if (!r && offers && ap_ssl_conn_is_ssl(c)
&& ap_array_str_contains(offers, PROTO_ACME_TLS_1)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"proposing protocol '%s'", PROTO_ACME_TLS_1);
@@ -1020,9 +1031,9 @@ static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
const char *protocol)
{
md_conn_ctx *ctx;
-
+
(void)s;
- if (!r && opt_ssl_is_https && opt_ssl_is_https(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) {
+ if (!r && ap_ssl_conn_is_ssl(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"switching protocol '%s'", PROTO_ACME_TLS_1);
ctx = apr_pcalloc(c->pool, sizeof(*ctx));
@@ -1035,57 +1046,65 @@ static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
return DECLINED;
}
-
+
/**************************************************************************************************/
/* Access API to other httpd components */
-static apr_status_t setup_fallback_cert(md_store_t *store, const md_t *md,
- server_rec *s, apr_pool_t *p)
+static void fallback_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn )
+{
+ *keyfn = apr_pstrcat(p, "fallback-", md_pkey_filename(kspec, p), NULL);
+ *certfn = apr_pstrcat(p, "fallback-", md_chain_filename(kspec, p), NULL);
+}
+
+static apr_status_t make_fallback_cert(md_store_t *store, const md_t *md, md_pkey_spec_t *kspec,
+ server_rec *s, apr_pool_t *p, char *keyfn, char *crtfn)
{
md_pkey_t *pkey;
md_cert_t *cert;
- md_pkey_spec_t spec;
apr_status_t rv;
-
- spec.type = MD_PKEY_TYPE_RSA;
- spec.params.rsa.bits = MD_PKEY_RSA_BITS_DEF;
-
- if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, &spec))
- || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name,
- MD_FN_FALLBACK_PKEY, MD_SV_PKEY, (void*)pkey, 0))
- || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback",
+
+ if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, kspec))
+ || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name,
+ keyfn, MD_SV_PKEY, (void*)pkey, 0))
+ || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback",
md->domains, pkey, apr_time_from_sec(14 * MD_SECS_PER_DAY), p))
- || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name,
- MD_FN_FALLBACK_CERT, MD_SV_CERT, (void*)cert, 0))) {
+ || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name,
+ crtfn, MD_SV_CERT, (void*)cert, 0))) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10174)
- "%s: setup fallback certificate", md->name);
+ "%s: make fallback %s certificate", md->name, md_pkey_spec_name(kspec));
}
return rv;
}
-static apr_status_t get_certificate(server_rec *s, apr_pool_t *p, int fallback,
- const char **pcertfile, const char **pkeyfile)
+static apr_status_t get_certificates(server_rec *s, apr_pool_t *p, int fallback,
+ apr_array_header_t **pcert_files,
+ apr_array_header_t **pkey_files)
{
- apr_status_t rv = APR_ENOENT;
+ apr_status_t rv = APR_ENOENT;
md_srv_conf_t *sc;
md_reg_t *reg;
md_store_t *store;
const md_t *md;
-
- *pkeyfile = NULL;
- *pcertfile = NULL;
+ apr_array_header_t *key_files, *chain_files;
+ const char *keyfile, *chainfile;
+ md_pkey_spec_t *spec;
+ int i;
+
+ *pkey_files = *pcert_files = NULL;
+ key_files = apr_array_make(p, 5, sizeof(const char*));
+ chain_files = apr_array_make(p, 5, sizeof(const char*));
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10113)
- "get_certificate called for vhost %s.", s->server_hostname);
+ "get_certificates called for vhost %s.", s->server_hostname);
sc = md_config_get(s);
if (!sc) {
- ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
- "asked for certificate of server %s which has no md config",
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
+ "asked for certificate of server %s which has no md config",
s->server_hostname);
return APR_ENOENT;
}
-
+
assert(sc->mc);
reg = sc->mc->reg;
assert(reg);
@@ -1107,141 +1126,173 @@ static apr_status_t get_certificate(server_rec *s, apr_pool_t *p, int fallback,
return APR_EINVAL;
}
md = APR_ARRAY_IDX(sc->assigned, 0, const md_t*);
-
- rv = md_reg_get_cred_files(pkeyfile, pcertfile, reg, MD_SG_DOMAINS, md, p);
- if (APR_STATUS_IS_ENOENT(rv)) {
+
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ rv = md_reg_get_cred_files(&keyfile, &chainfile, reg, MD_SG_DOMAINS, md, spec, p);
+ if (APR_SUCCESS == rv) {
+ APR_ARRAY_PUSH(key_files, const char*) = keyfile;
+ APR_ARRAY_PUSH(chain_files, const char*) = chainfile;
+ }
+ else if (!APR_STATUS_IS_ENOENT(rv)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110)
+ "retrieving credentials for MD %s (%s)",
+ md->name, md_pkey_spec_name(spec));
+ return rv;
+ }
+ }
+
+ if (md_array_is_empty(key_files)) {
if (fallback) {
/* Provide temporary, self-signed certificate as fallback, so that
* clients do not get obscure TLS handshake errors or will see a fallback
* virtual host that is not intended to be served here. */
+ char *kfn, *cfn;
+
store = md_reg_store_get(reg);
- assert(store);
-
- md_store_get_fname(pkeyfile, store, MD_SG_DOMAINS, md->name, MD_FN_FALLBACK_PKEY, p);
- md_store_get_fname(pcertfile, store, MD_SG_DOMAINS, md->name, MD_FN_FALLBACK_CERT, p);
- if (!md_file_exists(*pkeyfile, p) || !md_file_exists(*pcertfile, p)) {
- if (APR_SUCCESS != (rv = setup_fallback_cert(store, md, s, p))) {
- return rv;
+ assert(store);
+
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ fallback_fnames(p, spec, &kfn, &cfn);
+
+ md_store_get_fname(&keyfile, store, MD_SG_DOMAINS, md->name, kfn, p);
+ md_store_get_fname(&chainfile, store, MD_SG_DOMAINS, md->name, cfn, p);
+ if (!md_file_exists(keyfile, p) || !md_file_exists(chainfile, p)) {
+ if (APR_SUCCESS != (rv = make_fallback_cert(store, md, spec, s, p, kfn, cfn))) {
+ return rv;
+ }
}
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116)
+ "%s: providing %s fallback certificate for server %s",
+ md->name, md_pkey_spec_name(spec), s->server_hostname);
+ APR_ARRAY_PUSH(key_files, const char*) = keyfile;
+ APR_ARRAY_PUSH(chain_files, const char*) = chainfile;
}
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116)
- "%s: providing fallback certificate for server %s",
- md->name, s->server_hostname);
- return APR_EAGAIN;
+ rv = APR_EAGAIN;
+ goto leave;
}
}
- else if (APR_SUCCESS != rv) {
- ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110)
- "retrieving credentials for MD %s", md->name);
- return rv;
- }
-
- ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077)
- "%s[state=%d]: providing certificate for server %s",
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077)
+ "%s[state=%d]: providing certificates for server %s",
md->name, md->state, s->server_hostname);
+leave:
+ if (!md_array_is_empty(key_files) && !md_array_is_empty(chain_files)) {
+ *pkey_files = key_files;
+ *pcert_files = chain_files;
+ }
return rv;
}
static int md_add_cert_files(server_rec *s, apr_pool_t *p,
- apr_array_header_t *cert_files,
+ apr_array_header_t *cert_files,
apr_array_header_t *key_files)
{
- const char *certfile, *keyfile;
+ apr_array_header_t *md_cert_files;
+ apr_array_header_t *md_key_files;
apr_status_t rv;
-
+
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_cert_files for %s",
s->server_hostname);
- rv = get_certificate(s, p, 0, &certfile, &keyfile);
+ rv = get_certificates(s, p, 0, &md_cert_files, &md_key_files);
if (APR_SUCCESS == rv) {
if (!apr_is_empty_array(cert_files)) {
- ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10084)
+ /* downgraded fromm WARNING to DEBUG, since installing separate certificates
+ * may be a valid use case. */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10084)
"host '%s' is covered by a Managed Domain, but "
"certificate/key files are already configured "
- "for it (most likely via SSLCertificateFile).",
+ "for it (most likely via SSLCertificateFile).",
s->server_hostname);
- }
- APR_ARRAY_PUSH(cert_files, const char*) = certfile;
- APR_ARRAY_PUSH(key_files, const char*) = keyfile;
+ }
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
+ "host '%s' is covered by a Managed Domaina and "
+ "is being provided with %d key/certificate files.",
+ s->server_hostname, md_cert_files->nelts);
+ apr_array_cat(cert_files, md_cert_files);
+ apr_array_cat(key_files, md_key_files);
return DONE;
}
return DECLINED;
}
static int md_add_fallback_cert_files(server_rec *s, apr_pool_t *p,
- apr_array_header_t *cert_files,
+ apr_array_header_t *cert_files,
apr_array_header_t *key_files)
{
- const char *certfile, *keyfile;
+ apr_array_header_t *md_cert_files;
+ apr_array_header_t *md_key_files;
apr_status_t rv;
-
+
ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_fallback_cert_files for %s",
s->server_hostname);
- rv = get_certificate(s, p, 1, &certfile, &keyfile);
+ rv = get_certificates(s, p, 1, &md_cert_files, &md_key_files);
if (APR_EAGAIN == rv) {
- APR_ARRAY_PUSH(cert_files, const char*) = certfile;
- APR_ARRAY_PUSH(key_files, const char*) = keyfile;
+ apr_array_cat(cert_files, md_cert_files);
+ apr_array_cat(key_files, md_key_files);
return DONE;
}
return DECLINED;
}
-static int md_is_challenge(conn_rec *c, const char *servername,
- X509 **pcert, EVP_PKEY **pkey)
+static int md_answer_challenge(conn_rec *c, const char *servername,
+ const char **pcert_pem, const char **pkey_pem)
{
+ const char *protocol;
+ int hook_rv = DECLINED;
+ apr_status_t rv = APR_ENOENT;
md_srv_conf_t *sc;
- const char *protocol, *challenge, *cert_name, *pkey_name;
- apr_status_t rv;
+ md_store_t *store;
+ char *cert_name, *pkey_name;
+ const char *cert_pem, *key_pem;
+ int i;
- if (!servername) goto out;
-
- challenge = NULL;
- if ((protocol = md_protocol_get(c)) && !strcmp(PROTO_ACME_TLS_1, protocol)) {
- challenge = "tls-alpn-01";
- cert_name = MD_FN_TLSALPN01_CERT;
- pkey_name = MD_FN_TLSALPN01_PKEY;
-
- sc = md_config_get(c->base_server);
- if (sc && sc->mc->reg) {
- md_store_t *store = md_reg_store_get(sc->mc->reg);
- md_cert_t *mdcert;
- md_pkey_t *mdpkey;
-
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "%s: load certs/keys %s/%s",
- servername, cert_name, pkey_name);
- rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name,
- MD_SV_CERT, (void**)&mdcert, c->pool);
- if (APR_SUCCESS == rv && (*pcert = md_cert_get_X509(mdcert))) {
- rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name,
- MD_SV_PKEY, (void**)&mdpkey, c->pool);
- if (APR_SUCCESS == rv && (*pkey = md_pkey_get_EVP_PKEY(mdpkey))) {
- ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10078)
- "%s: is a %s challenge host", servername, challenge);
- return 1;
- }
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, rv, c, APLOGNO(10079)
- "%s: challenge data not complete, key unavailable", servername);
- }
- else {
- ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080)
- "%s: unknown %s challenge host", servername, challenge);
- }
- }
+ if (!servername
+ || !(protocol = md_protocol_get(c))
+ || strcmp(PROTO_ACME_TLS_1, protocol)) {
+ goto cleanup;
}
-out:
- *pcert = NULL;
- *pkey = NULL;
- return 0;
-}
+ sc = md_config_get(c->base_server);
+ if (!sc || !sc->mc->reg) goto cleanup;
-static int md_answer_challenge(conn_rec *c, const char *servername,
- X509 **pcert, EVP_PKEY **pkey)
-{
- if (md_is_challenge(c, servername, pcert, pkey)) {
- return OK;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c,
+ "Answer challenge[tls-alpn-01] for %s", servername);
+ store = md_reg_store_get(sc->mc->reg);
+
+ for (i = 0; i < md_pkeys_spec_count( sc->pks ); i++) {
+ tls_alpn01_fnames(c->pool, md_pkeys_spec_get(sc->pks,i),
+ &pkey_name, &cert_name);
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, MD_SV_TEXT,
+ (void**)&cert_pem, c->pool);
+ if (APR_STATUS_IS_ENOENT(rv)) continue;
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, MD_SV_TEXT,
+ (void**)&key_pem, c->pool);
+ if (APR_STATUS_IS_ENOENT(rv)) continue;
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "Found challenge cert %s, key %s for %s",
+ cert_name, pkey_name, servername);
+ *pcert_pem = cert_pem;
+ *pkey_pem = key_pem;
+ hook_rv = OK;
+ break;
}
- return DECLINED;
+
+ if (DECLINED == hook_rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080)
+ "%s: unknown tls-alpn-01 challenge host", servername);
+ }
+
+cleanup:
+ return hook_rv;
}
+
/**************************************************************************************************/
/* ACME 'http-01' challenge responses */
@@ -1256,28 +1307,28 @@ static int md_http_challenge_pr(request_rec *r)
md_reg_t *reg;
const md_t *md;
apr_status_t rv;
-
- if (r->parsed_uri.path
+
+ if (r->parsed_uri.path
&& !strncmp(ACME_CHALLENGE_PREFIX, r->parsed_uri.path, sizeof(ACME_CHALLENGE_PREFIX)-1)) {
sc = ap_get_module_config(r->server->module_config, &md_module);
if (sc && sc->mc) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "access inside /.well-known/acme-challenge for %s%s",
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "access inside /.well-known/acme-challenge for %s%s",
r->hostname, r->parsed_uri.path);
md = md_get_by_domain(sc->mc->mds, r->hostname);
name = r->parsed_uri.path + sizeof(ACME_CHALLENGE_PREFIX)-1;
reg = sc && sc->mc? sc->mc->reg : NULL;
-
+
if (strlen(name) && !ap_strchr_c(name, '/') && reg) {
md_store_t *store = md_reg_store_get(reg);
-
- rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname,
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname,
MD_FN_HTTP01, MD_SV_TEXT, (void**)&data, r->pool);
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
"loading challenge for %s (%s)", r->hostname, r->uri);
if (APR_SUCCESS == rv) {
apr_size_t len = strlen(data);
-
+
if (r->method_number != M_GET) {
return HTTP_NOT_IMPLEMENTED;
}
@@ -1285,12 +1336,12 @@ static int md_http_challenge_pr(request_rec *r)
* configured for. Let's send the content back */
r->status = HTTP_OK;
apr_table_setn(r->headers_out, "Content-Length", apr_ltoa(r->pool, (long)len));
-
+
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
apr_brigade_write(bb, NULL, NULL, data, len);
ap_pass_brigade(r->output_filters, bb);
apr_brigade_cleanup(bb);
-
+
return DONE;
}
else if (!md || md->renew_mode == MD_RENEW_MANUAL
@@ -1325,25 +1376,24 @@ static int md_require_https_maybe(request_rec *r)
const char *s, *host;
const md_t *md;
int status;
-
+
/* Requests outside the /.well-known path are subject to possible
* https: redirects or HSTS header additions.
*/
sc = ap_get_module_config(r->server->module_config, &md_module);
- if (!sc || !sc->assigned || !sc->assigned->nelts
- || !opt_ssl_is_https || !r->parsed_uri.path
+ if (!sc || !sc->assigned || !sc->assigned->nelts || !r->parsed_uri.path
|| !strncmp(WELL_KNOWN_PREFIX, r->parsed_uri.path, sizeof(WELL_KNOWN_PREFIX)-1)) {
goto declined;
}
-
+
host = ap_get_server_name_for_url(r);
md = md_get_for_domain(r->server, host);
if (!md) goto declined;
-
- if (opt_ssl_is_https(r->connection)) {
+
+ if (ap_ssl_conn_is_ssl(r->connection)) {
/* Using https:
* if 'permanent' and no one else set a HSTS header already, do it */
- if (md->require_https == MD_REQUIRE_PERMANENT
+ if (md->require_https == MD_REQUIRE_PERMANENT
&& sc->mc->hsts_header && !apr_table_get(r->headers_out, MD_HSTS_HEADER)) {
apr_table_setn(r->headers_out, MD_HSTS_HEADER, sc->mc->hsts_header);
}
@@ -1353,15 +1403,15 @@ static int md_require_https_maybe(request_rec *r)
/* Not using https:, but require it. Redirect. */
if (r->method_number == M_GET) {
/* safe to use the old-fashioned codes */
- status = ((MD_REQUIRE_PERMANENT == md->require_https)?
+ status = ((MD_REQUIRE_PERMANENT == md->require_https)?
HTTP_MOVED_PERMANENTLY : HTTP_MOVED_TEMPORARILY);
}
else {
/* these should keep the method unchanged on retry */
- status = ((MD_REQUIRE_PERMANENT == md->require_https)?
+ status = ((MD_REQUIRE_PERMANENT == md->require_https)?
HTTP_PERMANENT_REDIRECT : HTTP_TEMPORARY_REDIRECT);
}
-
+
s = ap_construct_url(r->pool, r->uri, r);
if (APR_SUCCESS == apr_uri_parse(r->pool, s, &uri)) {
uri.scheme = (char*)"https";
@@ -1381,7 +1431,7 @@ declined:
return DECLINED;
}
-/* Runs once per created child process. Perform any process
+/* Runs once per created child process. Perform any process
* related initialization here.
*/
static void md_child_init(apr_pool_t *pool, server_rec *s)
@@ -1398,15 +1448,15 @@ static void md_hooks(apr_pool_t *pool)
/* Leave the ssl initialization to mod_ssl or friends. */
md_acme_init(pool, AP_SERVER_BASEVERSION, 0);
-
+
ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
-
+
/* Run once after configuration is set, before mod_ssl.
* Run again after mod_ssl is done.
*/
ap_hook_post_config(md_post_config_before_ssl, NULL, mod_ssl, APR_HOOK_MIDDLE);
ap_hook_post_config(md_post_config_after_ssl, mod_ssl, NULL, APR_HOOK_MIDDLE);
-
+
/* Run once after a child process has been created.
*/
ap_hook_child_init(md_child_init, NULL, mod_ssl, APR_HOOK_MIDDLE);
@@ -1425,13 +1475,13 @@ static void md_hooks(apr_pool_t *pool)
APR_OPTIONAL_HOOK(ap, status_hook, md_ocsp_status_hook, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(md_status_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ssl_answer_challenge(md_answer_challenge, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ssl_add_cert_files(md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ssl_add_fallback_cert_files(md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
#ifndef SSL_CERT_HOOKS
#error "This version of mod_md requires Apache httpd 2.4.41 or newer."
#endif
- APR_OPTIONAL_HOOK(ssl, add_cert_files, md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
- APR_OPTIONAL_HOOK(ssl, add_fallback_cert_files, md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
- APR_OPTIONAL_HOOK(ssl, answer_challenge, md_answer_challenge, NULL, NULL, APR_HOOK_MIDDLE);
APR_OPTIONAL_HOOK(ssl, init_stapling_status, md_ocsp_init_stapling_status, NULL, NULL, APR_HOOK_MIDDLE);
APR_OPTIONAL_HOOK(ssl, get_stapling_status, md_ocsp_get_stapling_status, NULL, NULL, APR_HOOK_MIDDLE);
}
diff --git a/modules/md/mod_md.dsp b/modules/md/mod_md.dsp
index 1bc34b2377..7b247a5bc0 100644
--- a/modules/md/mod_md.dsp
+++ b/modules/md/mod_md.dsp
@@ -145,10 +145,6 @@ SOURCE=./md_acme_order.c
# End Source File
# Begin Source File
-SOURCE=./md_acmev1_drive.c
-# End Source File
-# Begin Source File
-
SOURCE=./md_acmev2_drive.c
# End Source File
# Begin Source File
@@ -169,6 +165,10 @@ SOURCE=./md_http.c
# End Source File
# Begin Source File
+SOURCE=./md_event.c
+# End Source File
+# Begin Source File
+
SOURCE=./md_json.c
# End Source File
# Begin Source File
diff --git a/modules/md/mod_md.h b/modules/md/mod_md.h
index cf043c4530..39b62f853f 100644
--- a/modules/md/mod_md.h
+++ b/modules/md/mod_md.h
@@ -39,4 +39,9 @@ APR_DECLARE_OPTIONAL_FN(int,
md_is_challenge, (struct conn_rec *, const char *,
X509 **pcert, EVP_PKEY **pkey));
+APR_DECLARE_OPTIONAL_FN(apr_status_t,
+ md_answer_challenges, (conn_rec *c, const char *servername,
+ apr_array_header_t *certs,
+ apr_array_header_t *pkeys));
+
#endif /* mod_md_mod_md_h */
diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c
index 66684213aa..0f31b919c5 100644
--- a/modules/md/mod_md_config.c
+++ b/modules/md/mod_md_config.c
@@ -81,6 +81,7 @@ static md_mod_conf_t defmc = {
&def_ocsp_renew_window, /* default time to renew ocsp responses */
"crt.sh", /* default cert checker site name */
"https://crt.sh?q=", /* default cert checker site url */
+ NULL, /* CA cert file to use */
};
static md_timeslice_t def_renew_window = {
@@ -153,7 +154,7 @@ static void srv_conf_props_clear(md_srv_conf_t *sc)
sc->require_https = MD_REQUIRE_UNSET;
sc->renew_mode = DEF_VAL;
sc->must_staple = DEF_VAL;
- sc->pkey_spec = NULL;
+ sc->pks = NULL;
sc->renew_window = NULL;
sc->warn_window = NULL;
sc->ca_url = NULL;
@@ -171,7 +172,7 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
to->require_https = from->require_https;
to->renew_mode = from->renew_mode;
to->must_staple = from->must_staple;
- to->pkey_spec = from->pkey_spec;
+ to->pks = from->pks;
to->warn_window = from->warn_window;
to->renew_window = from->renew_window;
to->ca_url = from->ca_url;
@@ -189,7 +190,7 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t
if (from->transitive != DEF_VAL) md->transitive = from->transitive;
if (from->renew_mode != DEF_VAL) md->renew_mode = from->renew_mode;
if (from->must_staple != DEF_VAL) md->must_staple = from->must_staple;
- if (from->pkey_spec) md->pkey_spec = from->pkey_spec;
+ if (from->pks) md->pks = md_pkeys_spec_clone(p, from->pks);
if (from->renew_window) md->renew_window = from->renew_window;
if (from->warn_window) md->warn_window = from->warn_window;
if (from->ca_url) md->ca_url = from->ca_url;
@@ -227,7 +228,7 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
nsc->require_https = (add->require_https != MD_REQUIRE_UNSET)? add->require_https : base->require_https;
nsc->renew_mode = (add->renew_mode != DEF_VAL)? add->renew_mode : base->renew_mode;
nsc->must_staple = (add->must_staple != DEF_VAL)? add->must_staple : base->must_staple;
- nsc->pkey_spec = add->pkey_spec? add->pkey_spec : base->pkey_spec;
+ nsc->pks = (!md_pkeys_spec_is_empty(add->pks))? add->pks : base->pks;
nsc->renew_window = add->renew_window? add->renew_window : base->renew_window;
nsc->warn_window = add->warn_window? add->warn_window : base->warn_window;
@@ -769,6 +770,7 @@ static const char *md_config_set_pkeys(cmd_parms *cmd, void *dc,
md_srv_conf_t *config = md_config_get(cmd->server);
const char *err, *ptype;
apr_int64_t bits;
+ int i;
(void)dc;
if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
@@ -778,42 +780,63 @@ static const char *md_config_set_pkeys(cmd_parms *cmd, void *dc,
return "needs to specify the private key type";
}
- ptype = argv[0];
- if (!apr_strnatcasecmp("Default", ptype)) {
- if (argc > 1) {
- return "type 'Default' takes no parameter";
- }
- if (!config->pkey_spec) {
- config->pkey_spec = apr_pcalloc(cmd->pool, sizeof(*config->pkey_spec));
+ config->pks = md_pkeys_spec_make(cmd->pool);
+ for (i = 0; i < argc; ++i) {
+ ptype = argv[i];
+ if (!apr_strnatcasecmp("Default", ptype)) {
+ if (argc > 1) {
+ return "'Default' allows no other parameter";
+ }
+ md_pkeys_spec_add_default(config->pks);
}
- config->pkey_spec->type = MD_PKEY_TYPE_DEFAULT;
- return NULL;
- }
- else if (!apr_strnatcasecmp("RSA", ptype)) {
- if (argc == 1) {
- bits = MD_PKEY_RSA_BITS_DEF;
+ else if (strlen(ptype) > 3
+ && (ptype[0] == 'R' || ptype[0] == 'r')
+ && (ptype[1] == 'S' || ptype[1] == 's')
+ && (ptype[2] == 'A' || ptype[2] == 'a')
+ && isdigit(ptype[3])) {
+ bits = (int)apr_atoi64(ptype+3);
+ if (bits < MD_PKEY_RSA_BITS_MIN) {
+ return apr_psprintf(cmd->pool,
+ "must be %d or higher in order to be considered safe.",
+ MD_PKEY_RSA_BITS_MIN);
+ }
+ if (bits >= INT_MAX) {
+ return apr_psprintf(cmd->pool, "is too large for an RSA key length.");
+ }
+ if (md_pkeys_spec_contains_rsa(config->pks)) {
+ return "two keys of type 'RSA' are not possible.";
+ }
+ md_pkeys_spec_add_rsa(config->pks, (unsigned int)bits);
}
- else if (argc == 2) {
- bits = (int)apr_atoi64(argv[1]);
- if (bits < MD_PKEY_RSA_BITS_MIN || bits >= INT_MAX) {
- return apr_psprintf(cmd->pool, "must be %d or higher in order to be considered "
- "safe. Too large a value will slow down everything. Larger then 4096 probably does "
- "not make sense unless quantum cryptography really changes spin.",
- MD_PKEY_RSA_BITS_MIN);
+ else if (!apr_strnatcasecmp("RSA", ptype)) {
+ if (i+1 >= argc || !isdigit(argv[i+1][0])) {
+ bits = MD_PKEY_RSA_BITS_DEF;
+ }
+ else {
+ ++i;
+ bits = (int)apr_atoi64(argv[i]);
+ if (bits < MD_PKEY_RSA_BITS_MIN) {
+ return apr_psprintf(cmd->pool,
+ "must be %d or higher in order to be considered safe.",
+ MD_PKEY_RSA_BITS_MIN);
+ }
+ if (bits >= INT_MAX) {
+ return apr_psprintf(cmd->pool, "is too large for an RSA key length.");
+ }
+ }
+ if (md_pkeys_spec_contains_rsa(config->pks)) {
+ return "two keys of type 'RSA' are not possible.";
}
+ md_pkeys_spec_add_rsa(config->pks, (unsigned int)bits);
}
else {
- return "key type 'RSA' has only one optional parameter, the number of bits";
- }
-
- if (!config->pkey_spec) {
- config->pkey_spec = apr_pcalloc(cmd->pool, sizeof(*config->pkey_spec));
+ if (md_pkeys_spec_contains_ec(config->pks, argv[i])) {
+ return apr_psprintf(cmd->pool, "two keys of type '%s' are not possible.", argv[i]);
+ }
+ md_pkeys_spec_add_ec(config->pks, argv[i]);
}
- config->pkey_spec->type = MD_PKEY_TYPE_RSA;
- config->pkey_spec->params.rsa.bits = (unsigned int)bits;
- return NULL;
}
- return apr_pstrcat(cmd->pool, "unsupported private key type \"", ptype, "\"", NULL);
+ return NULL;
}
static const char *md_config_set_notify_cmd(cmd_parms *cmd, void *mconfig, const char *arg)
@@ -967,6 +990,15 @@ static const char *md_config_set_activation_delay(cmd_parms *cmd, void *mconfig,
return NULL;
}
+static const char *md_config_set_ca_certs(cmd_parms *cmd, void *dc, const char *path)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+
+ (void)dc;
+ sc->mc->ca_certs = path;
+ return NULL;
+}
+
const command_rec md_cmds[] = {
AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF,
"URL of CA issuing the certificates"),
@@ -1041,6 +1073,8 @@ const command_rec md_cmds[] = {
"Set name and URL pattern for a certificate monitoring site."),
AP_INIT_TAKE1("MDActivationDelay", md_config_set_activation_delay, NULL, RSRC_CONF,
"How long to delay activation of new certificates"),
+ AP_INIT_TAKE1("MDCACertificateFile", md_config_set_ca_certs, NULL, RSRC_CONF,
+ "Set the CA file to use for connections"),
AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
};
diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h
index 2be0f68d30..76423bd602 100644
--- a/modules/md/mod_md_config.h
+++ b/modules/md/mod_md_config.h
@@ -21,7 +21,7 @@ struct apr_hash_t;
struct md_store_t;
struct md_reg_t;
struct md_ocsp_reg_t;
-struct md_pkey_spec_t;
+struct md_pkeys_spec_t;
typedef enum {
MD_CONFIG_CA_URL,
@@ -70,6 +70,7 @@ struct md_mod_conf_t {
md_timeslice_t *ocsp_renew_window; /* time before exp. that we start renewing ocsp resp. */
const char *cert_check_name; /* name of the linked certificate check site */
const char *cert_check_url; /* url "template for" checking a certificate */
+ const char *ca_certs; /* root certificates to use for connections */
};
typedef struct md_srv_conf_t {
@@ -81,9 +82,9 @@ typedef struct md_srv_conf_t {
md_require_t require_https; /* If MDs require https: access */
int renew_mode; /* mode of obtaining credentials */
int must_staple; /* certificates should set the OCSP Must Staple extension */
- struct md_pkey_spec_t *pkey_spec; /* specification for generating private keys */
- md_timeslice_t *renew_window; /* time before expiration that starts renewal */
- md_timeslice_t *warn_window; /* time before expiration that warning are sent out */
+ struct md_pkeys_spec_t *pks; /* specification for private keys */
+ md_timeslice_t *renew_window; /* time before expiration that starts renewal */
+ md_timeslice_t *warn_window; /* time before expiration that warning are sent out */
const char *ca_url; /* url of CA certificate service */
const char *ca_contact; /* contact email registered to account */
diff --git a/modules/md/mod_md_drive.c b/modules/md/mod_md_drive.c
index 1191a5fc3d..87b06f6b51 100644
--- a/modules/md/mod_md_drive.c
+++ b/modules/md/mod_md_drive.c
@@ -31,6 +31,7 @@
#include "md.h"
#include "md_curl.h"
#include "md_crypt.h"
+#include "md_event.h"
#include "md_http.h"
#include "md_json.h"
#include "md_status.h"
@@ -79,7 +80,7 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p
if (apr_time_now() < job->next_run) return;
job->next_run = 0;
- if (job->finished && job->notified) {
+ if (job->finished && job->notified_renewed) {
/* finished and notification handled, nothing to do. */
goto leave;
}
@@ -101,7 +102,7 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p
if (md_will_renew_cert(md)) {
/* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly
* without discarding previous/intermediate results.
- * Only returns SUCCESS when the renewal is complete, e.g. STAGING as a
+ * Only returns SUCCESS when the renewal is complete, e.g. STAGING has a
* complete set of new credentials.
*/
ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052)
@@ -113,7 +114,17 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p
goto expiry;
}
- md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
+ /* The (possibly configured) event handler may veto renewals. This
+ * is used in cluster installtations, see #233. */
+ rv = md_event_raise("renewing", md->name, job, result, ptemp);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10060)
+ "%s: event-handler for 'renewing' returned %d, preventing renewal to proceed.",
+ job->mdomain, rv);
+ goto leave;
+ }
+
+ md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, result, ptemp);
md_job_end_run(job, result);
@@ -125,13 +136,15 @@ static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *p
goto leave;
}
- if (!job->notified) md_job_notify(job, "renewed", result);
+ if (!job->notified_renewed) {
+ md_job_notify(job, "renewed", result);
+ }
}
else {
ap_log_error( APLOG_MARK, APLOG_ERR, result->status, dctx->s, APLOGNO(10056)
"processing %s: %s", job->mdomain, result->detail);
md_job_log_append(job, "renewal-error", result->problem, result->detail);
- md_job_holler(job, "errored");
+ md_event_holler("errored", job->mdomain, job, result, ptemp);
ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10057)
"%s: encountered error for the %d. time, next run in %s",
job->mdomain, job->error_runs,
@@ -144,9 +157,7 @@ expiry:
ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s,
"md(%s): warn about expiration", md->name);
md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
- if (APR_SUCCESS == md_job_notify(job, "expiring", result)) {
- md_result_set(result, APR_SUCCESS, NULL);
- }
+ md_job_notify(job, "expiring", result);
md_job_end_run(job, result);
}
diff --git a/modules/md/mod_md_status.c b/modules/md/mod_md_status.c
index cde13325c7..af0eed5c24 100644
--- a/modules/md/mod_md_status.c
+++ b/modules/md/mod_md_status.c
@@ -58,9 +58,12 @@
int md_http_cert_status(request_rec *r)
{
- md_json_t *resp, *j, *mdj, *certj;
+ int i;
+ md_json_t *resp, *mdj, *cj;
const md_srv_conf_t *sc;
const md_t *md;
+ md_pkey_spec_t *spec;
+ const char *keyname;
apr_bucket_brigade *bb;
apr_status_t rv;
@@ -96,32 +99,40 @@ int md_http_cert_status(request_rec *r)
"status for MD: %s is %s", md->name, md_json_writep(mdj, r->pool, MD_JSON_FMT_INDENT));
resp = md_json_create(r->pool);
-
- if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL)) {
- md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL),
- resp, MD_KEY_VALID, MD_KEY_UNTIL, NULL);
- }
- if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_FROM, NULL)) {
- md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_FROM, NULL),
- resp, MD_KEY_VALID, MD_KEY_FROM, NULL);
- }
- if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_SERIAL, NULL)) {
- md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SERIAL, NULL),
- resp, MD_KEY_SERIAL, NULL);
- }
- if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL)) {
- md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL),
- resp, MD_KEY_SHA256_FINGERPRINT, NULL);
+
+ if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID, NULL)) {
+ md_json_setj(md_json_getj(mdj, MD_KEY_CERT, MD_KEY_VALID, NULL), resp, MD_KEY_VALID, NULL);
+ }
+
+ for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ keyname = md_pkey_spec_name(spec);
+ cj = md_json_create(r->pool);
+
+ if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_VALID, NULL)) {
+ md_json_setj(md_json_getj(mdj, MD_KEY_CERT, keyname, MD_KEY_VALID, NULL),
+ cj, MD_KEY_VALID, NULL);
+ }
+
+ if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL)) {
+ md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL),
+ cj, MD_KEY_SERIAL, NULL);
+ }
+ if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL)) {
+ md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL),
+ cj, MD_KEY_SHA256_FINGERPRINT, NULL);
+ }
+ md_json_setj(cj, resp, keyname, NULL );
}
if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) {
- /* copy over the information we want to make public about this:
- * - when not finished, add an empty object to indicate something is going on
- * - when a certificate is staged, add the information from that */
- certj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL);
- j = certj? certj : md_json_create(r->pool);;
- md_json_setj(j, resp, MD_KEY_RENEWAL, NULL);
- }
+ /* copy over the information we want to make public about this:
+ * - when not finished, add an empty object to indicate something is going on
+ * - when a certificate is staged, add the information from that */
+ cj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL);
+ cj = cj? cj : md_json_create(r->pool);;
+ md_json_setj(cj, resp, MD_KEY_RENEWAL, MD_KEY_CERT, NULL);
+ }
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md[%s]: sending status", md->name);
apr_table_set(r->headers_out, "Content-Type", "application/json");
@@ -329,7 +340,7 @@ static void print_job_summary(apr_bucket_brigade *bb, md_json_t *mdj, const char
return;
}
- finished = (int)md_json_getl(mdj, key, MD_KEY_FINISHED, NULL);
+ finished = md_json_getb(mdj, key, MD_KEY_FINISHED, NULL);
errors = (int)md_json_getl(mdj, key, MD_KEY_ERRORS, NULL);
rv = (apr_status_t)md_json_getl(mdj, key, MD_KEY_LAST, MD_KEY_STATUS, NULL);
@@ -388,16 +399,26 @@ static void si_val_activity(status_ctx *ctx, md_json_t *mdj, const status_info *
}
}
-static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+static int cert_check_iter(void *baton, const char *key, md_json_t *json)
{
+ status_ctx *ctx = baton;
const char *fingerprint;
+ fingerprint = md_json_gets(json, MD_KEY_SHA256_FINGERPRINT, NULL);
+ if (fingerprint) {
+ apr_brigade_printf(ctx->bb, NULL, NULL,
+ "<a href=\"%s%s\">%s[%s]</a><br>",
+ ctx->mc->cert_check_url, fingerprint,
+ ctx->mc->cert_check_name, key);
+ }
+ return 1;
+}
+
+static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
(void)info;
if (ctx->mc->cert_check_name && ctx->mc->cert_check_url) {
- fingerprint = md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL);
- apr_brigade_printf(ctx->bb, NULL, NULL,
- "<a href=\"%s%s\">%s</a> ",
- ctx->mc->cert_check_url, fingerprint, ctx->mc->cert_check_name);
+ md_json_iterkey(cert_check_iter, ctx, mdj, MD_KEY_CERT, NULL);
}
}
@@ -436,6 +457,13 @@ static void add_json_val(status_ctx *ctx, md_json_t *j)
}
}
+static void si_val_names(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+ apr_brigade_puts(ctx->bb, NULL, NULL, "<div style=\"max-width:400px;\">");
+ add_json_val(ctx, md_json_getj(mdj, info->key, NULL));
+ apr_brigade_puts(ctx->bb, NULL, NULL, "</div>");
+}
+
static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *info)
{
if (info->fn) {
@@ -448,7 +476,7 @@ static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *
static const status_info status_infos[] = {
{ "Domain", MD_KEY_NAME, NULL },
- { "Names", MD_KEY_DOMAINS, NULL },
+ { "Names", MD_KEY_DOMAINS, si_val_names },
{ "Status", MD_KEY_STATE, si_val_status },
{ "Valid", MD_KEY_CERT, si_val_cert_valid_time },
{ "CA", MD_KEY_CA, si_val_ca_url },