summaryrefslogtreecommitdiff
path: root/modules/md
diff options
context:
space:
mode:
authorStefan Eissing <icing@apache.org>2021-03-08 18:05:50 +0000
committerStefan Eissing <icing@apache.org>2021-03-08 18:05:50 +0000
commite3928f2b27cc17676389e68f6fb33cb5394edda3 (patch)
treede4a0451b21070bfec875b293edde9cdd59b9c1f /modules/md
parentf86ff6565df3e335d772e065fb820b0a6d4d5ad9 (diff)
downloadhttpd-e3928f2b27cc17676389e68f6fb33cb5394edda3.tar.gz
*) 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. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1887337 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules/md')
-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
46 files changed, 2191 insertions, 1273 deletions
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 },