From c9f60810cf026f000acd77a97eaff31821d018a3 Mon Sep 17 00:00:00 2001 From: Stefan Eissing <icing@apache.org> Date: Fri, 1 Sep 2017 12:11:38 +0000 Subject: [PATCH] On the trunk: mod_md: v0.8.1 from github, new feats in CHANGES git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1806939 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 24 +++++- docs/manual/mod/mod_md.xml | 34 ++++++++ modules/md/md.h | 15 +++- modules/md/md_acme.c | 15 ++-- modules/md/md_acme.h | 7 +- modules/md/md_acme_acct.c | 16 ++-- modules/md/md_acme_acct.h | 4 + modules/md/md_acme_authz.c | 26 ++++--- modules/md/md_acme_authz.h | 5 +- modules/md/md_acme_drive.c | 106 +++++++++++++++++-------- modules/md/md_cmd_reg.c | 6 +- modules/md/md_core.c | 27 +++++-- modules/md/md_crypt.c | 122 ++++++++++++++++++++++------- modules/md/md_crypt.h | 23 +++++- modules/md/md_curl.c | 9 ++- modules/md/md_reg.c | 88 +++++++++++---------- modules/md/md_store.c | 17 +++- modules/md/md_store.h | 6 ++ modules/md/md_store_fs.c | 141 +++++++++++++++++++++++++--------- modules/md/md_util.c | 115 +++++++++++++-------------- modules/md/md_version.h | 4 +- modules/md/mod_md.c | 100 ++++++++++++++---------- modules/md/mod_md_config.c | 120 ++++++++++++++++++++++++++--- modules/md/mod_md_config.h | 6 ++ modules/md/mod_md_os.c | 2 +- modules/md/mod_md_os.h | 2 +- modules/ssl/ssl_engine_init.c | 5 +- 27 files changed, 748 insertions(+), 297 deletions(-) diff --git a/CHANGES b/CHANGES index c018a9cb3b..e2133fab55 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,29 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 - *) mod_md: v0.7.0: + *) mod_md: v0.8.1: + - New directive ```MDPrivateKeys``` to specify the type and parameter to private key generation. + Currently only 'RSA' is supported as type with an option number of bits >= 2048 as parameter. + Simple test cases for config handling added. + - Private RSA keys are now generated with 2048 bits by default. Use ```MDPrivateKeys``` for + higher security. + - IMPORTANT: store format change. The following changes will be made to an existing md store on + first start with a new version (be it by mod_md in the server or a run by a new 'a2md'): + - pkey.pem will be renamed to privkey.pem + - cert.pem and chain.pem will be concatenated to pubcert.pem. The former files will remain, + but no longer be used. They will disappear on next renewal. + ADVICE: If the current store data is vital to you, please make a backup first! + - Fixed test case clearing of store to keep key alive, enabling true random store key again. + - Removed pun "Something, like certbot" from the User-Agent request header. Refs issue #34 + - Cleaned up reporting of missing/mismatched MDCertificateAgreement in the logs. This will + no longer trigger early retries. + - badNonce encounters are no longer reported as errors. Retries are attempted now silently. + Refs github issue #35 + - new default MDRenewWindow. Instead of 14 days, the default is now a third before the end of + the certificates lifetime. For the usual 90 days of Let's Encrypt certificates, this makes + an effective renewal window of 30 days - as recommended by LE. Refs issue #30 + - Enabled conversion warnings if supported by compiler, eliminated several signed/unsigned + warnings. - LIVE: the real Let's Encrypt CA is now live by default! If you need to experiment, configure MDCertificateAuthority https://acme-staging.api.letsencrypt.org/directory - When existing, complete certificates are renewed, the activation of the new ones is diff --git a/docs/manual/mod/mod_md.xml b/docs/manual/mod/mod_md.xml index 7b084ce3a6..e78d9b03a0 100644 --- a/docs/manual/mod/mod_md.xml +++ b/docs/manual/mod/mod_md.xml @@ -341,6 +341,40 @@ MDPortMap 80:- 443:5002 </usage> </directivesynopsis> + <directivesynopsis> + <name>MDPrivateKeys</name> + <description></description> + <syntax>MDPrivateKeys type [ params... ]</syntax> + <default>MDPrivateKeys RSA 2048</default> + <contextlist> + <context>server config</context> + </contextlist> + <usage> + <p> + Defines what kind of private keys are generated for a managed domain and with + what parameters. The only supported type right now is 'RSA' and the only parameter + it takes is the number of bits used for the key. + </p><p> + The current (2017) recommendation is at least 2048 bits and a smaller number is + not accepted here. Higher numbers offer longer security, but are computationally more + expensive, e.g. increase the load on your server. That might or might not be an + issue for you. + </p><p> + Other key types will be defined in the future. + </p> + <example><title>Example</title> + <highlight language="config"> +MDPrivateKeys RSA 3072 + </highlight> + </example> + <p> + Please note that this setting only has an effect on new keys. Any existing + private key you have remains unaffected. Also, this only affects private keys + generated for certificates. ACME account keys are unaffected by this. + </p> + </usage> + </directivesynopsis> + <directivesynopsis> <name>MDRenewWindow</name> <description></description> diff --git a/modules/md/md.h b/modules/md/md.h index d1a7b518df..f723309ba5 100644 --- a/modules/md/md.h +++ b/modules/md/md.h @@ -25,8 +25,10 @@ struct md_cert_t; struct md_pkey_t; struct md_store_t; struct md_srv_conf_t; +struct md_pkey_spec_t; #define MD_TLSSNI01_DNS_SUFFIX ".acme.invalid" +#define MD_PKEY_RSA_BITS_DEF 2048U typedef enum { MD_S_UNKNOWN, /* MD has not been analysed yet */ @@ -34,6 +36,7 @@ typedef enum { MD_S_COMPLETE, /* MD has all necessary information, can go live */ MD_S_EXPIRED, /* MD is complete, but credentials have expired */ MD_S_ERROR, /* MD data is flawed, unable to be processed as is */ + MD_S_MISSING, /* MD is missing config information, cannot proceed */ } md_state_t; typedef enum { @@ -70,7 +73,9 @@ struct md_t { int transitive; /* != 0 iff VirtualHost names/aliases are auto-added */ int drive_mode; /* mode of obtaining credentials */ + struct md_pkey_spec_t *pkey_spec;/* specification for generating new private keys */ int must_staple; /* certificates should set the OCSP Must Staple extension */ + apr_interval_time_t renew_norm; /* if > 0, normalized cert lifetime */ apr_interval_time_t renew_window;/* time before expiration that starts renewal */ const char *ca_url; /* url of CA certificate service */ @@ -91,6 +96,7 @@ struct md_t { #define MD_KEY_ACCOUNT "account" #define MD_KEY_AGREEMENT "agreement" +#define MD_KEY_BITS "bits" #define MD_KEY_CA "ca" #define MD_KEY_CA_URL "ca-url" #define MD_KEY_CERT "cert" @@ -112,8 +118,10 @@ struct md_t { #define MD_KEY_KEYAUTHZ "keyAuthorization" #define MD_KEY_LOCATION "location" #define MD_KEY_NAME "name" +#define MD_KEY_PKEY "privkey" #define MD_KEY_PROTO "proto" #define MD_KEY_REGISTRATION "registration" +#define MD_KEY_RENEW_NORM "renew-norm" #define MD_KEY_RENEW_WINDOW "renew-window" #define MD_KEY_RESOURCE "resource" #define MD_KEY_STATE "state" @@ -129,7 +137,8 @@ struct md_t { #define MD_KEY_VERSION "version" #define MD_FN_MD "md.json" -#define MD_FN_PKEY "pkey.pem" +#define MD_FN_PRIVKEY "privkey.pem" +#define MD_FN_PUBCERT "pubcert.pem" #define MD_FN_CERT "cert.pem" #define MD_FN_CHAIN "chain.pem" #define MD_FN_HTTPD_JSON "httpd.json" @@ -230,9 +239,9 @@ md_t *md_from_json(struct md_json_t *json, apr_pool_t *p); typedef struct md_creds_t md_creds_t; struct md_creds_t { + struct md_pkey_t *privkey; + struct apr_array_header_t *pubcert; /* complete md_cert* chain */ struct md_cert_t *cert; - struct md_pkey_t *pkey; - struct apr_array_header_t *chain; /* list of md_cert* */ int expired; }; diff --git a/modules/md/md_acme.c b/modules/md/md_acme.c index 6eedb55d2a..d4b5c8a57a 100644 --- a/modules/md/md_acme.c +++ b/modules/md/md_acme.c @@ -111,9 +111,8 @@ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url) acme = apr_pcalloc(p, sizeof(*acme)); acme->url = url; acme->p = p; - acme->user_agent = apr_psprintf(p, "%s mod_md/%s (Something, like certbot)", + acme->user_agent = apr_psprintf(p, "%s mod_md/%s", base_product, MOD_MD_VERSION); - acme->pkey_bits = 4096; acme->max_retries = 3; if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) { @@ -260,9 +259,15 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t ptype = md_json_gets(problem, "type", NULL); pdetail = md_json_gets(problem, "detail", NULL); req->rv = problem_status_get(ptype); - - md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, req->rv, req->p, - "acme problem %s: %s", ptype, pdetail); + + if (APR_STATUS_IS_EAGAIN(req->rv)) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, req->rv, req->p, + "acme reports %s: %s", ptype, pdetail); + } + else { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, req->rv, req->p, + "acme problem %s: %s", ptype, pdetail); + } return req->rv; } } diff --git a/modules/md/md_acme.h b/modules/md/md_acme.h index a5a57aab88..25af1da058 100644 --- a/modules/md/md_acme.h +++ b/modules/md/md_acme.h @@ -62,7 +62,6 @@ struct md_acme_t { const char *nonce; int max_retries; - unsigned int pkey_bits; }; /** @@ -126,8 +125,12 @@ apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *tos); * accounces the Tos URL it wants. If this is equal to the agreement specified, * the server is notified of this. If the server requires a ToS that the account * thinks it has already given, it is resend. + * + * If an agreement is required, different from the current one, APR_INCOMPLETE is + * returned and the agreement url is returned in the parameter. */ -apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, const char *agreement); +apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, + const char *agreement, const char **prequired); /** * Get the ToS agreement for current account. diff --git a/modules/md/md_acme_acct.c b/modules/md/md_acme_acct.c index 71246c3f86..5ad419ff63 100644 --- a/modules/md/md_acme_acct.c +++ b/modules/md/md_acme_acct.c @@ -327,6 +327,7 @@ static apr_status_t acct_register(md_acme_t *acme, apr_pool_t *p, apr_status_t rv; md_pkey_t *pkey; const char *err = NULL, *uri; + md_pkey_spec_t spec; int i; md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account"); @@ -347,7 +348,10 @@ static apr_status_t acct_register(md_acme_t *acme, apr_pool_t *p, } } - if (APR_SUCCESS == (rv = md_pkey_gen_rsa(&pkey, acme->p, acme->pkey_bits)) + spec.type = MD_PKEY_TYPE_RSA; + spec.params.rsa.bits = MD_ACME_ACCT_PKEY_BITS; + + if (APR_SUCCESS == (rv = md_pkey_gen(&pkey, acme->p, &spec)) && APR_SUCCESS == (rv = acct_make(&acme->acct, p, acme->url, NULL, contacts))) { acct_ctx_t ctx; @@ -614,11 +618,13 @@ static int agreement_required(md_acme_acct_t *acct) || (acct->tos_required && strcmp(acct->tos_required, acct->agreement))); } -apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, const char *agreement) +apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, + const char *agreement, const char **prequired) { apr_status_t rv = APR_SUCCESS; /* Check if (correct) Terms-of-Service for account were accepted */ + *prequired = NULL; if (agreement_required(acme->acct)) { const char *tos = acme->acct->tos_required; if (!tos) { @@ -642,10 +648,8 @@ apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, const char rv = md_acme_agree(acme, p, tos); } else { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, acme->p, - "need to accept terms-of-service <%s> for account %s", - tos, acme->acct->id); - rv = APR_EACCES; + *prequired = apr_pstrdup(p, tos); + rv = APR_INCOMPLETE; } } return rv; diff --git a/modules/md/md_acme_acct.h b/modules/md/md_acme_acct.h index 5abd9b5031..06ac9532b8 100644 --- a/modules/md/md_acme_acct.h +++ b/modules/md/md_acme_acct.h @@ -41,4 +41,8 @@ struct md_acme_acct_t { #define MD_FN_ACCOUNT "account.json" #define MD_FN_ACCT_KEY "account.pem" +/* ACME account private keys are always RSA and have that many bits. Since accounts + * are expected to live long, better err on the safe side. */ +#define MD_ACME_ACCT_PKEY_BITS 3072 + #endif /* md_acme_acct_h */ diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c index 83c6ca9609..e07a3c06a3 100644 --- a/modules/md/md_acme_authz.c +++ b/modules/md/md_acme_authz.c @@ -91,10 +91,10 @@ apr_status_t md_acme_authz_set_remove(md_acme_authz_set_t *set, const char *doma for (i = 0; i < set->authzs->nelts; ++i) { authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *); if (!apr_strnatcasecmp(domain, authz->domain)) { - int n = i +1; + int n = i + 1; if (n < set->authzs->nelts) { void **elems = (void **)set->authzs->elts; - memmove(elems + i, elems + n, set->authzs->nelts - n); + memmove(elems + i, elems + n, (size_t)(set->authzs->nelts - n)); } --set->authzs->nelts; return APR_SUCCESS; @@ -292,7 +292,8 @@ static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *a } 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, apr_pool_t *p) + md_acme_t *acme, md_store_t *store, + md_pkey_spec_t *key_spec, apr_pool_t *p) { const char *data; apr_status_t rv; @@ -347,7 +348,8 @@ static apr_status_t setup_cha_dns(const char **pdns, md_acme_authz_cha_t *cha, a } static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, - md_acme_t *acme, md_store_t *store, apr_pool_t *p) + md_acme_t *acme, md_store_t *store, + md_pkey_spec_t *key_spec, apr_pool_t *p) { md_cert_t *cha_cert; md_pkey_t *cha_key; @@ -365,7 +367,7 @@ static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, cha_dns)) || APR_STATUS_IS_ENOENT(rv)) { - if (APR_SUCCESS != (rv = md_pkey_gen_rsa(&cha_key, p, acme->pkey_bits))) { + if (APR_SUCCESS != (rv = md_pkey_gen(&cha_key, p, key_spec))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-sni-01 challgenge key", authz->domain); goto out; @@ -405,7 +407,8 @@ out: } typedef apr_status_t cha_starter(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, - md_acme_t *acme, md_store_t *store, apr_pool_t *p); + md_acme_t *acme, md_store_t *store, + md_pkey_spec_t *key_spec, apr_pool_t *p); typedef struct { const char *name; @@ -449,7 +452,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, apr_pool_t *p) + apr_array_header_t *challenges, + md_pkey_spec_t *key_spec, apr_pool_t *p) { apr_status_t rv; int i; @@ -485,7 +489,7 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s for (i = 0; i < CHA_TYPES_LEN; ++i) { if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) { - return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, p); + return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, key_spec, p); } } @@ -560,7 +564,7 @@ md_acme_authz_t *md_acme_authz_from_json(struct md_json_t *json, apr_pool_t *p) authz->domain = md_json_dups(p, json, MD_KEY_DOMAIN, NULL); authz->location = md_json_dups(p, json, MD_KEY_LOCATION, NULL); authz->dir = md_json_dups(p, json, MD_KEY_DIR, NULL); - authz->state = (int)md_json_getl(json, MD_KEY_STATE, NULL); + authz->state = (md_acme_authz_state_t)md_json_getl(json, MD_KEY_STATE, NULL); return authz; } return NULL; @@ -631,7 +635,7 @@ static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_lis const char *md_name; int create; - group = va_arg(ap, int); + group = (md_store_group_t)va_arg(ap, int); md_name = va_arg(ap, const char *); set = va_arg(ap, md_acme_authz_set_t *); create = va_arg(ap, int); @@ -657,7 +661,7 @@ static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_li const char *md_name; int i; - group = va_arg(ap, int); + group = (md_store_group_t)va_arg(ap, int); md_name = va_arg(ap, const char *); if (APR_SUCCESS == md_acme_authz_set_load(store, group, md_name, &authz_set, p)) { diff --git a/modules/md/md_acme_authz.h b/modules/md/md_acme_authz.h index 0996deb56b..817d6fe830 100644 --- a/modules/md/md_acme_authz.h +++ b/modules/md/md_acme_authz.h @@ -21,6 +21,7 @@ struct md_acme_t; struct md_acme_acct_t; struct md_json_t; struct md_store_t; +struct md_pkey_spec_t; typedef struct md_acme_challenge_t md_acme_challenge_t; @@ -67,8 +68,8 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, struct md_acme_t *acme struct md_store_t *store, 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, apr_pool_t *p); + struct md_store_t *store, apr_array_header_t *challenges, + struct md_pkey_spec_t *key_spec, apr_pool_t *p); apr_status_t md_acme_authz_del(md_acme_authz_t *authz, struct md_acme_t *acme, struct md_store_t *store, apr_pool_t *p); diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c index fe8359be3d..068c0d3349 100644 --- a/modules/md/md_acme_drive.c +++ b/modules/md/md_acme_drive.c @@ -42,9 +42,11 @@ typedef struct { const char *phase; int complete; - md_pkey_t *pkey; - md_cert_t *cert; - apr_array_header_t *chain; + md_pkey_t *privkey; /* the new private key */ + apr_array_header_t *pubcert; /* the new certificate + chain certs */ + + md_cert_t *cert; /* the new certificate */ + apr_array_header_t *chain; /* the chain certificates */ md_acme_t *acme; md_t *md; @@ -122,8 +124,8 @@ static apr_status_t ad_set_acct(md_proto_driver_t *d) goto out; } - if (APR_SUCCESS == (rv = md_acme_create_acct(ad->acme, d->p, - md->contacts, md->ca_agreement)) + if (APR_SUCCESS == (rv = md_acme_create_acct(ad->acme, d->p, md->contacts, + md->ca_agreement)) && APR_SUCCESS == (rv = md_acme_acct_save_staged(ad->acme, d->store, md, d->p))) { md->ca_account = MD_ACME_ACCT_STAGED; update = 1; @@ -267,11 +269,13 @@ static apr_status_t ad_start_challenges(md_proto_driver_t *d) switch (authz->state) { case MD_ACME_AUTHZ_S_VALID: break; - case MD_ACME_AUTHZ_S_PENDING: - rv = md_acme_authz_respond(authz, ad->acme, d->store, ad->ca_challenges, d->p); + case MD_ACME_AUTHZ_S_PENDING: + rv = md_acme_authz_respond(authz, ad->acme, d->store, ad->ca_challenges, + d->md->pkey_spec, d->p); changed = 1; break; + default: rv = APR_EINVAL; md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, @@ -464,22 +468,22 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void static apr_status_t ad_setup_certificate(md_proto_driver_t *d) { md_acme_driver_t *ad = d->baton; - md_pkey_t *pkey; + md_pkey_t *privkey; apr_status_t rv; - ad->phase = "setup cert pkey"; + ad->phase = "setup cert privkey"; - rv = md_pkey_load(d->store, MD_SG_STAGING, ad->md->name, &pkey, d->p); + rv = md_pkey_load(d->store, MD_SG_STAGING, ad->md->name, &privkey, d->p); if (APR_STATUS_IS_ENOENT(rv)) { - if (APR_SUCCESS == (rv = md_pkey_gen_rsa(&pkey, d->p, ad->acme->pkey_bits))) { - rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, ad->md->name, pkey, 1); + 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, ad->md->name, privkey, 1); } - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate pkey", ad->md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate privkey", ad->md->name); } if (APR_SUCCESS == rv) { ad->phase = "setup csr"; - rv = md_cert_req_create(&ad->csr_der_64, ad->md, pkey, d->p); + rv = md_cert_req_create(&ad->csr_der_64, ad->md, privkey, d->p); md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create CSR", ad->md->name); } @@ -674,6 +678,12 @@ static apr_status_t acme_stage(md_proto_driver_t *d) ad->md = NULL; } + if (ad->md && ad->md->state == MD_S_MISSING) { + /* There is config information missing. It makes no sense to drive this MD further */ + rv = APR_INCOMPLETE; + goto out; + } + if (ad->md) { /* staging in progress. look for new ACME account information collected there */ rv = md_reg_creds_get(&ad->ncreds, d->reg, MD_SG_STAGING, d->md, d->p); @@ -684,7 +694,7 @@ static apr_status_t acme_stage(md_proto_driver_t *d) } /* Find out where we're at with this managed domain */ - if (ad->ncreds && ad->ncreds->pkey && ad->ncreds->cert && ad->ncreds->chain) { + if (ad->ncreds && ad->ncreds->privkey && ad->ncreds->pubcert) { /* There is a full set staged, to be loaded */ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: all data staged", d->md->name); renew = 0; @@ -724,11 +734,32 @@ static apr_status_t acme_stage(md_proto_driver_t *d) * requests for new authorizations are denied. ToS may change during the * lifetime of an account */ if (APR_SUCCESS == rv) { + const char *required; + ad->phase = "check agreement"; md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: check Terms-of-Service agreement", d->md->name); - rv = md_acme_check_agreement(ad->acme, d->p, ad->md->ca_agreement); + 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; + md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0); + + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, + "%s: 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 url\" " + "with exactly that URL in your Apache. " + "Then (graceful) restart the server to activate.", + ad->md->name, required); + goto out; + } } /* If we know a cert's location, try to get it. Previous download might @@ -779,6 +810,10 @@ static apr_status_t acme_stage(md_proto_driver_t *d) } + if (APR_SUCCESS == rv && !ad->chain) { + /* have we created this already? */ + md_chain_load(d->store, MD_SG_STAGING, ad->md->name, &ad->chain, d->p); + } if (APR_SUCCESS == rv && !ad->chain) { ad->phase = "install chain"; md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, @@ -786,6 +821,18 @@ static apr_status_t acme_stage(md_proto_driver_t *d) rv = ad_chain_install(d); } + if (APR_SUCCESS == rv && !ad->pubcert) { + /* have we created this already? */ + md_pubcert_load(d->store, MD_SG_STAGING, ad->md->name, &ad->pubcert, d->p); + } + if (APR_SUCCESS == rv && !ad->pubcert) { + /* combine cert + chain into the pubcert */ + ad->pubcert = apr_array_make(d->p, ad->chain->nelts + 1, sizeof(md_cert_t*)); + APR_ARRAY_PUSH(ad->pubcert, md_cert_t *) = ad->cert; + apr_array_cat(ad->pubcert, ad->chain); + rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, ad->md->name, ad->pubcert, 0); + } + if (APR_SUCCESS == rv && ad->cert) { apr_time_t now = apr_time_now(); apr_interval_time_t max_delay, delay_activation; @@ -832,10 +879,9 @@ static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group, const char *name, apr_pool_t *p) { apr_status_t rv; - md_pkey_t *pkey, *acct_key; + md_pkey_t *privkey, *acct_key; md_t *md; - md_cert_t *cert; - apr_array_header_t *chain; + apr_array_header_t *pubcert; struct md_acme_acct_t *acct; md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: preload start", name); @@ -852,16 +898,12 @@ static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group, md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading md json", name); return rv; } - if (APR_SUCCESS != (rv = md_cert_load(store, MD_SG_STAGING, name, &cert, p))) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading certificate", name); - return rv; - } - if (APR_SUCCESS != (rv = md_chain_load(store, MD_SG_STAGING, name, &chain, p))) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading cert chain", name); + if (APR_SUCCESS != (rv = md_pkey_load(store, MD_SG_STAGING, name, &privkey, p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading staging private key", name); return rv; } - if (APR_SUCCESS != (rv = md_pkey_load(store, MD_SG_STAGING, name, &pkey, p))) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading staging private key", name); + if (APR_SUCCESS != (rv = md_pubcert_load(store, MD_SG_STAGING, name, &pubcert, p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading pubcert", name); return rv; } @@ -904,16 +946,12 @@ static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group, md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving md json", name); return rv; } - if (APR_SUCCESS != (rv = md_cert_save(store, p, load_group, name, cert, 1))) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving certificate", name); - return rv; - } - if (APR_SUCCESS != (rv = md_chain_save(store, p, load_group, name, chain, 1))) { + if (APR_SUCCESS != (rv = md_pubcert_save(store, p, load_group, name, pubcert, 1))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving cert chain", name); return rv; } - if (APR_SUCCESS != (rv = md_pkey_save(store, p, load_group, name, pkey, 1))) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving domain private key", name); + if (APR_SUCCESS != (rv = md_pkey_save(store, p, load_group, name, privkey, 1))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving private key", name); return rv; } diff --git a/modules/md/md_cmd_reg.c b/modules/md/md_cmd_reg.c index 8dac2a89b5..1b1278ac46 100644 --- a/modules/md/md_cmd_reg.c +++ b/modules/md/md_cmd_reg.c @@ -103,7 +103,7 @@ static apr_status_t cmd_reg_list(md_cmd_ctx *ctx, const md_cmd_t *cmd) else { md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ctx->p, "list do"); md_reg_do(list_add_md, mdlist, ctx->reg, ctx->p); - qsort(mdlist->elts, mdlist->nelts, sizeof(md_t *), md_name_cmp); + qsort(mdlist->elts, (size_t)mdlist->nelts, sizeof(md_t *), md_name_cmp); for (i = 0; i < mdlist->nelts; ++i) { md = APR_ARRAY_IDX(mdlist, i, const md_t*); @@ -274,7 +274,7 @@ static apr_status_t assess_and_drive(md_cmd_ctx *ctx, md_t *md) md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: %s", md->name, msg); if (APR_SUCCESS == (rv = md_reg_stage(ctx->reg, md, challenge, reset, NULL, ctx->p))) { - md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: loading", md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: loading", md->name); rv = md_reg_load(ctx->reg, md->name, ctx->p); @@ -317,7 +317,7 @@ static apr_status_t cmd_reg_drive(md_cmd_ctx *ctx, const md_cmd_t *cmd) } else { md_reg_do(list_add_md, mdlist, ctx->reg, ctx->p); - qsort(mdlist->elts, mdlist->nelts, sizeof(md_t *), md_name_cmp); + qsort(mdlist->elts, (size_t)mdlist->nelts, sizeof(md_t *), md_name_cmp); } rv = APR_SUCCESS; diff --git a/modules/md/md_core.c b/modules/md/md_core.c index f9bbd78033..8294ba505c 100644 --- a/modules/md/md_core.c +++ b/modules/md/md_core.c @@ -24,6 +24,7 @@ #include "md_json.h" #include "md.h" +#include "md_crypt.h" #include "md_log.h" #include "md_store.h" #include "md_util.h" @@ -229,6 +230,8 @@ md_t *md_clone(apr_pool_t *p, const md_t *src) md->name = apr_pstrdup(p, src->name); md->drive_mode = src->drive_mode; md->domains = md_array_str_compact(p, src->domains, 0); + md->pkey_spec = src->pkey_spec; + md->renew_norm = src->renew_norm; md->renew_window = src->renew_window; md->contacts = md_array_str_clone(p, src->contacts); if (src->ca_url) md->ca_url = apr_pstrdup(p, src->ca_url); @@ -253,8 +256,10 @@ md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base) n->ca_proto = add->ca_proto? add->ca_proto : base->ca_proto; n->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement; n->drive_mode = (add->drive_mode != MD_DRIVE_DEFAULT)? add->drive_mode : base->drive_mode; - n->renew_window = (add->renew_window <= 0)? add->renew_window : base->renew_window; - n->transitive = (add->transitive < 0)? add->transitive : base->transitive; + n->pkey_spec = add->pkey_spec? add->pkey_spec : base->pkey_spec; + n->renew_norm = (add->renew_norm > 0)? add->renew_norm : base->renew_norm; + n->renew_window = (add->renew_window > 0)? add->renew_window : base->renew_window; + n->transitive = (add->transitive >= 0)? add->transitive : base->transitive; if (add->ca_challenges) { n->ca_challenges = apr_array_copy(p, add->ca_challenges); } @@ -264,7 +269,6 @@ md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base) return n; } - /**************************************************************************************************/ /* format conversion */ @@ -284,6 +288,9 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p) if (md->cert_url) { md_json_sets(md->cert_url, json, MD_KEY_CERT, MD_KEY_URL, NULL); } + if (md->pkey_spec) { + md_json_setj(md_pkey_spec_to_json(md->pkey_spec, p), json, MD_KEY_PKEY, NULL); + } md_json_setl(md->state, json, MD_KEY_STATE, NULL); md_json_setl(md->drive_mode, json, MD_KEY_DRIVE_MODE, NULL); if (md->expires > 0) { @@ -296,7 +303,13 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p) apr_rfc822_date(ts, md->valid_from); md_json_sets(ts, json, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL); } - md_json_setl(apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL); + if (md->renew_norm > 0) { + md_json_setl((long)apr_time_sec(md->renew_norm), json, MD_KEY_RENEW_NORM, NULL); + md_json_setl((long)apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL); + } + else { + md_json_setl((long)apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL); + } if (md->ca_challenges && md->ca_challenges->nelts > 0) { apr_array_header_t *na; na = md_array_str_compact(p, md->ca_challenges, 0); @@ -320,7 +333,10 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p) 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); md->cert_url = md_json_dups(p, json, MD_KEY_CERT, MD_KEY_URL, NULL); - md->state = (int)md_json_getl(json, MD_KEY_STATE, 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); + } + md->state = (md_state_t)md_json_getl(json, MD_KEY_STATE, NULL); md->drive_mode = (int)md_json_getl(json, MD_KEY_DRIVE_MODE, NULL); md->domains = md_array_str_compact(p, md->domains, 0); md->transitive = (int)md_json_getl(json, MD_KEY_TRANSITIVE, NULL); @@ -332,6 +348,7 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p) if (s && *s) { md->valid_from = apr_date_parse_rfc(s); } + md->renew_norm = apr_time_from_sec(md_json_getl(json, MD_KEY_RENEW_NORM, NULL)); md->renew_window = apr_time_from_sec(md_json_getl(json, MD_KEY_RENEW_WINDOW, NULL)); if (md_json_has_key(json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL)) { md->ca_challenges = apr_array_make(p, 5, sizeof(const char*)); diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c index 4e0b4d521f..10ddf51ed9 100644 --- a/modules/md/md_crypt.c +++ b/modules/md/md_crypt.c @@ -31,6 +31,7 @@ #include "md.h" #include "md_crypt.h" +#include "md_json.h" #include "md_log.h" #include "md_http.h" #include "md_util.h" @@ -83,7 +84,7 @@ static void seed_RAND(int pid) { unsigned char stackdata[256]; /* stolen from mod_ssl/ssl_engine_rand.c */ - apr_size_t n, l; + int n; struct { time_t t; pid_t pid; @@ -99,8 +100,7 @@ static void seed_RAND(int pid) */ my_seed.pid = pid; - l = sizeof(my_seed); - RAND_seed((unsigned char *)&my_seed, l); + RAND_seed((unsigned char *)&my_seed, sizeof(my_seed)); /* * seed in some current state of the run-time stack (128 bytes) @@ -173,7 +173,7 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton) if (ctx->pass_len < size) { size = (int)ctx->pass_len; } - memcpy(buf, ctx->pass_phrase, size); + memcpy(buf, ctx->pass_phrase, (size_t)size); } return ctx->pass_len; } @@ -181,6 +181,50 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton) /**************************************************************************************************/ /* private keys */ +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); + if (json) { + switch (spec->type) { + case MD_PKEY_TYPE_DEFAULT: + md_json_sets("Default", json, MD_KEY_TYPE, NULL); + break; + case MD_PKEY_TYPE_RSA: + md_json_sets("RSA", json, MD_KEY_TYPE, NULL); + if (spec->params.rsa.bits > 2048) { + md_json_setl(spec->params.rsa.bits, json, MD_KEY_BITS, NULL); + } + break; + default: + md_json_sets("Unsupported", json, MD_KEY_TYPE, NULL); + break; + } + } + return json; +} + +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)); + const char *s; + long l; + + if (spec) { + s = md_json_gets(json, MD_KEY_TYPE, NULL); + if (!s || !apr_strnatcasecmp("Default", s)) { + spec->type = MD_PKEY_TYPE_DEFAULT; + } + else if (!apr_strnatcasecmp("RSA", s)) { + spec->type = MD_PKEY_TYPE_RSA; + l = md_json_getl(json, MD_KEY_BITS, NULL); + if (l > 2048) { + spec->params.rsa.bits = (unsigned int)l; + } + } + } + return spec; +} + static md_pkey_t *make_pkey(apr_pool_t *p) { md_pkey_t *pkey = apr_pcalloc(p, sizeof(*pkey)); @@ -231,7 +275,7 @@ apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p, apr_pool_cleanup_register(p, pkey, pkey_cleanup, apr_pool_cleanup_null); } else { - long err = ERR_get_error(); + unsigned long err = ERR_get_error(); rv = APR_EINVAL; md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "error loading pkey %s: %s (pass phrase was %snull)", fname, @@ -251,6 +295,7 @@ static apr_status_t pkey_to_buffer(buffer *buffer, md_pkey_t *pkey, apr_pool_t * void *cb_baton = NULL; passwd_ctx ctx; unsigned long err; + int i; if (!bio) { return APR_ENOMEM; @@ -278,11 +323,12 @@ static apr_status_t pkey_to_buffer(buffer *buffer, md_pkey_t *pkey, apr_pool_t * return APR_EINVAL; } - buffer->len = BIO_pending(bio); - if (buffer->len > 0) { - buffer->data = apr_palloc(p, buffer->len+1); - buffer->len = BIO_read(bio, buffer->data, (int)buffer->len); - buffer->data[buffer->len] = '\0'; + i = BIO_pending(bio); + if (i > 0) { + buffer->data = apr_palloc(p, (apr_size_t)i + 1); + i = BIO_read(bio, buffer->data, i); + buffer->data[i] = '\0'; + buffer->len = (apr_size_t)i; } BIO_free(bio); return APR_SUCCESS; @@ -303,7 +349,7 @@ apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p, return rv; } -apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits) +static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits) { EVP_PKEY_CTX *ctx = NULL; apr_status_t rv; @@ -312,7 +358,7 @@ apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits) ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); if (ctx && EVP_PKEY_keygen_init(ctx) >= 0 - && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) >= 0 + && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int)bits) >= 0 && EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) >= 0) { rv = APR_SUCCESS; } @@ -328,6 +374,19 @@ apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits) 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; + switch (ptype) { + case MD_PKEY_TYPE_DEFAULT: + return gen_rsa(ppkey, p, MD_PKEY_RSA_BITS_DEF); + case MD_PKEY_TYPE_RSA: + return gen_rsa(ppkey, p, spec->params.rsa.bits); + default: + return APR_ENOTIMPL; + } +} + #if OPENSSL_VERSION_NUMBER < 0x10100000L #ifndef NID_tlsfeature @@ -350,7 +409,7 @@ static void RSA_get0_key(const RSA *r, static const char *bn64(const BIGNUM *b, apr_pool_t *p) { if (b) { - int len = BN_num_bytes(b); + apr_size_t len = (apr_size_t)BN_num_bytes(b); char *buffer = apr_pcalloc(p, len); if (buffer) { BN_bn2bin(b, (unsigned char *)buffer); @@ -393,7 +452,7 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t * const char *sign64 = NULL; apr_status_t rv = APR_ENOMEM; - buffer = apr_pcalloc(p, EVP_PKEY_size(pkey->pkey)); + buffer = apr_pcalloc(p, (apr_size_t)EVP_PKEY_size(pkey->pkey)); if (buffer) { ctx = EVP_MD_CTX_create(); if (ctx) { @@ -728,6 +787,7 @@ apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname) static apr_status_t cert_to_buffer(buffer *buffer, md_cert_t *cert, apr_pool_t *p) { BIO *bio = BIO_new(BIO_s_mem()); + int i; if (!bio) { return APR_ENOMEM; @@ -740,11 +800,12 @@ static apr_status_t cert_to_buffer(buffer *buffer, md_cert_t *cert, apr_pool_t * return APR_EINVAL; } - buffer->len = BIO_pending(bio); - if (buffer->len > 0) { - buffer->data = apr_palloc(p, buffer->len+1); - buffer->len = BIO_read(bio, buffer->data, (int)buffer->len); - buffer->data[buffer->len] = '\0'; + i = BIO_pending(bio); + if (i > 0) { + buffer->data = apr_palloc(p, (apr_size_t)i + 1); + i = BIO_read(bio, buffer->data, i); + buffer->data[i] = '\0'; + buffer->len = (apr_size_t)i; } BIO_free(bio); return APR_SUCCESS; @@ -797,7 +858,7 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p, const unsigned char *bf = (const unsigned char*)der; X509 *x509; - if (NULL == (x509 = d2i_X509(NULL, &bf, der_len))) { + if (NULL == (x509 = d2i_X509(NULL, &bf, (long)der_len))) { rv = APR_EINVAL; } else { @@ -818,19 +879,16 @@ md_cert_state_t md_cert_state_get(md_cert_t *cert) return MD_CERT_UNKNOWN; } -apr_status_t md_chain_fload(apr_array_header_t **pcerts, apr_pool_t *p, const char *fname) +apr_status_t md_chain_fappend(struct apr_array_header_t *certs, apr_pool_t *p, const char *fname) { FILE *f; apr_status_t rv; - apr_array_header_t *certs = NULL; X509 *x509; md_cert_t *cert; unsigned long err; rv = md_util_fopen(&f, fname, "r"); if (rv == APR_SUCCESS) { - certs = apr_array_make(p, 5, sizeof(md_cert_t *)); - ERR_clear_error(); while (NULL != (x509 = PEM_read_X509(f, NULL, NULL, NULL))) { cert = make_cert(p, x509); @@ -862,6 +920,16 @@ apr_status_t md_chain_fload(apr_array_header_t **pcerts, apr_pool_t *p, const ch out: md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "read chain file %s, found %d certs", fname, certs? certs->nelts : 0); + return rv; +} + +apr_status_t md_chain_fload(apr_array_header_t **pcerts, apr_pool_t *p, const char *fname) +{ + apr_array_header_t *certs; + apr_status_t rv; + + certs = apr_array_make(p, 5, sizeof(md_cert_t *)); + rv = md_chain_fappend(certs, p, fname); *pcerts = (APR_SUCCESS == rv)? certs : NULL; return rv; } @@ -1027,12 +1095,12 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const md_t *md, md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: der length", md->name); rv = APR_EGENERAL; goto out; } - s = csr_der = apr_pcalloc(p, csr_der_len + 1); + s = csr_der = apr_pcalloc(p, (apr_size_t)csr_der_len + 1); if (i2d_X509_REQ(csr, (unsigned char**)&s) != csr_der_len) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: csr der enc", md->name); rv = APR_EGENERAL; goto out; } - csr_der_64 = md_util_base64url_encode(csr_der, csr_der_len, p); + csr_der_64 = md_util_base64url_encode(csr_der, (apr_size_t)csr_der_len, p); rv = APR_SUCCESS; out: @@ -1104,7 +1172,7 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, rv = APR_EGENERAL; goto out; } - days = ((apr_time_sec(valid_for) + MD_SECS_PER_DAY - 1)/ MD_SECS_PER_DAY); + days = (int)((apr_time_sec(valid_for) + MD_SECS_PER_DAY - 1)/ MD_SECS_PER_DAY); if (!X509_set_notBefore(x, ASN1_TIME_set(NULL, time(NULL)))) { rv = APR_EGENERAL; goto out; } diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h index 0437f897ea..5a53655926 100644 --- a/modules/md/md_crypt.h +++ b/modules/md/md_crypt.h @@ -41,9 +41,25 @@ apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p, typedef struct md_pkey_t md_pkey_t; +typedef enum { + MD_PKEY_TYPE_DEFAULT, + MD_PKEY_TYPE_RSA, +} md_pkey_type_t; + +typedef struct md_pkey_rsa_spec_t { + apr_uint32_t bits; +} md_pkey_rsa_spec_t; + +typedef struct md_pkey_spec_t { + md_pkey_type_t type; + union { + md_pkey_rsa_spec_t rsa; + } params; +} md_pkey_spec_t; + apr_status_t md_crypt_init(apr_pool_t *pool); -apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits); +apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec); void md_pkey_free(md_pkey_t *pkey); const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p); @@ -62,6 +78,9 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t * void *md_cert_get_X509(struct md_cert_t *cert); 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); + /**************************************************************************************************/ /* X509 certificates */ @@ -100,6 +119,8 @@ apr_status_t md_chain_fload(struct apr_array_header_t **pcerts, apr_pool_t *p, const char *fname); apr_status_t md_chain_fsave(struct apr_array_header_t *certs, apr_pool_t *p, const char *fname, apr_fileperms_t perms); +apr_status_t md_chain_fappend(struct apr_array_header_t *certs, + apr_pool_t *p, const char *fname); apr_status_t md_cert_req_create(const char **pcsr_der_64, const struct md_t *md, md_pkey_t *pkey, apr_pool_t *p); diff --git a/modules/md/md_curl.c b/modules/md/md_curl.c index 1eb5e554c0..f948efd8ec 100644 --- a/modules/md/md_curl.c +++ b/modules/md/md_curl.c @@ -99,7 +99,7 @@ static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton) if (res->req->resp_limit) { apr_off_t body_len = 0; apr_brigade_length(res->body, 0, &body_len); - if (body_len + len > res->req->resp_limit) { + if (body_len + (apr_off_t)len > res->req->resp_limit) { return 0; /* signal curl failure */ } } @@ -117,7 +117,7 @@ static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton) md_http_response_t *res = baton; size_t len, clen = elen * nmemb; const char *name = NULL, *value = "", *b = buffer; - int i; + apr_size_t i; len = (clen && b[clen-1] == '\n')? clen-1 : clen; len = (len && b[len-1] == '\r')? len-1 : len; @@ -183,7 +183,7 @@ static int curlify_headers(void *baton, const char *key, const char *value) static apr_status_t curl_perform(md_http_request_t *req) { apr_status_t rv = APR_SUCCESS; - int curle; + CURLcode curle; md_http_response_t *res; CURL *curl; struct curl_slist *req_hdrs = NULL; @@ -253,7 +253,8 @@ static apr_status_t curl_perform(md_http_request_t *req) } else { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, res->rv, req->pool, - "request %ld failed(%d): %s", req->id, curle, curl_easy_strerror(curle)); + "request %ld failed(%d): %s", req->id, curle, + curl_easy_strerror(curle)); } if (req->cb) { diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c index 04921c887f..25a841850f 100644 --- a/modules/md/md_reg.c +++ b/modules/md/md_reg.c @@ -162,19 +162,14 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md) if (APR_SUCCESS == (rv = md_reg_creds_get(&creds, reg, MD_SG_DOMAINS, md, p))) { state = MD_S_INCOMPLETE; - if (!creds->pkey) { + if (!creds->privkey) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: incomplete, without private key", md->name); } - else if (!creds->cert) { + else if (!creds->pubcert) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: incomplete, has key but no certificate", md->name); } - else if (!creds->chain) { - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, - "md{%s}: incomplete, has key and certificate, but no chain file.", - md->name); - } else { valid_from = md_cert_get_not_before(creds->cert); expires = md_cert_get_not_after(creds->cert); @@ -199,8 +194,8 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md) goto out; } - for (i = 0; i < creds->chain->nelts; ++i) { - cert = APR_ARRAY_IDX(creds->chain, i, const md_cert_t *); + for (i = 1; i < creds->pubcert->nelts; ++i) { + cert = APR_ARRAY_IDX(creds->pubcert, i, const md_cert_t *); if (!md_cert_is_valid_now(cert)) { state = MD_S_ERROR; md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, @@ -281,17 +276,34 @@ apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, md->state = MD_S_EXPIRED; renew = 1; } - else if ((md->expires - now) <= md->renew_window) { - int days = (int)(apr_time_sec(md->expires - now) / MD_SECS_PER_DAY); - md_log_perror( MD_LOG_MARK, MD_LOG_DEBUG, 0, p, - "md(%s): %d days to expiry, attempt renewal", md->name, days); - renew = 1; + else { + apr_interval_time_t renew_win, left, life; + + renew_win = md->renew_window; + if (md->renew_norm > 0 + && md->renew_norm > renew_win + && md->expires > md->valid_from) { + /* Calc renewal days as fraction of cert lifetime - if known */ + life = md->expires - md->valid_from; + renew_win = life * md->renew_norm / renew_win; + } + + left = md->expires - now; + if (left <= renew_win) { + int days_left = (int)(apr_time_sec(left) / MD_SECS_PER_DAY); + md_log_perror( MD_LOG_MARK, MD_LOG_DEBUG, 0, p, + "md(%s): %d days to expiry, attempt renewal", + md->name, days_left); + renew = 1; + } } break; case MD_S_INCOMPLETE: case MD_S_EXPIRED: renew = 1; break; + case MD_S_MISSING: + break; } *prenew = renew; *perrored = errored; @@ -417,16 +429,10 @@ apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p, { apr_status_t rv; - rv = md_store_get_fname(pkeyfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PKEY, p); + *pchainfile = NULL; + rv = md_store_get_fname(pkeyfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PRIVKEY, p); if (APR_SUCCESS == rv) { - rv = md_store_get_fname(pcertfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_CERT, p); - } - if (APR_SUCCESS == rv) { - rv = md_store_get_fname(pchainfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_CHAIN, p); - if (APR_STATUS_IS_ENOENT(rv)) { - *pchainfile = NULL; - rv = APR_SUCCESS; - } + rv = md_store_get_fname(pcertfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PUBCERT, p); } return rv; } @@ -513,6 +519,7 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v } if (MD_UPD_RENEW_WINDOW & fields) { md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update renew-window: %s", name); + nmd->renew_norm = updates->renew_norm; nmd->renew_window = updates->renew_window; } if (MD_UPD_CA_CHALLENGES & fields) { @@ -544,29 +551,28 @@ static int ok_or_noent(apr_status_t rv) static apr_status_t creds_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap) { md_reg_t *reg = baton; - apr_status_t rv; - md_cert_t *cert; - md_pkey_t *pkey; - apr_array_header_t *chain; + md_pkey_t *privkey; + apr_array_header_t *pubcert; md_creds_t *creds, **pcreds; const md_t *md; md_cert_state_t cert_state; md_store_group_t group; + apr_status_t rv; pcreds = va_arg(ap, md_creds_t **); - group = va_arg(ap, int); + group = (md_store_group_t)va_arg(ap, int); md = va_arg(ap, const md_t *); - if (ok_or_noent(rv = md_cert_load(reg->store, group, md->name, &cert, p)) - && ok_or_noent(rv = md_pkey_load(reg->store, group, md->name, &pkey, p)) - && ok_or_noent(rv = md_chain_load(reg->store, group, md->name, &chain, p))) { + if (ok_or_noent(rv = md_pkey_load(reg->store, group, md->name, &privkey, p)) + && ok_or_noent(rv = md_pubcert_load(reg->store, group, md->name, &pubcert, p))) { rv = APR_SUCCESS; creds = apr_pcalloc(p, sizeof(*creds)); - creds->cert = cert; - creds->pkey = pkey; - creds->chain = chain; - + creds->privkey = privkey; + if (pubcert && pubcert->nelts > 0) { + creds->pubcert = pubcert; + creds->cert = APR_ARRAY_IDX(pubcert, 0, md_cert_t *); + } if (creds->cert) { switch ((cert_state = md_cert_state_get(creds->cert))) { case MD_CERT_VALID: @@ -767,10 +773,12 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, smd->contacts = md->contacts; fields |= MD_UPD_CONTACTS; } - if (MD_VAL_UPDATE(md, smd, renew_window)) { + if (MD_VAL_UPDATE(md, smd, renew_window) + || MD_VAL_UPDATE(md, smd, renew_norm)) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, - "%s: update renew_window, old=%ld, new=%ld", - smd->name, (long)smd->renew_window, md->renew_window); + "%s: update renew norm=%ld, window=%ld", + smd->name, (long)md->renew_norm, (long)md->renew_window); + smd->renew_norm = md->renew_norm; smd->renew_window = md->renew_window; fields |= MD_UPD_RENEW_WINDOW; } @@ -880,7 +888,7 @@ apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, const char *challenge, return APR_SUCCESS; } - proto = apr_hash_get(reg->protos, md->ca_proto, strlen(md->ca_proto)); + proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto)); if (!proto) { md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md %s has unknown CA protocol: %s", md->name, md->ca_proto); @@ -918,7 +926,7 @@ static apr_status_t run_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l return APR_EINVAL; } - proto = apr_hash_get(reg->protos, md->ca_proto, strlen(md->ca_proto)); + proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto)); if (!proto) { md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md %s has unknown CA protocol: %s", md->name, md->ca_proto); diff --git a/modules/md/md_store.c b/modules/md/md_store.c index f94e959c5b..04e265cbea 100644 --- a/modules/md/md_store.c +++ b/modules/md/md_store.c @@ -232,13 +232,13 @@ typedef struct { 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) { - return md_store_load(store, group, name, MD_FN_PKEY, MD_SV_PKEY, (void**)ppkey, p); + return md_store_load(store, group, name, MD_FN_PRIVKEY, 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) { - return md_store_save(store, p, group, name, MD_FN_PKEY, MD_SV_PKEY, pkey, create); + return md_store_save(store, p, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, pkey, create); } apr_status_t md_cert_load(md_store_t *store, md_store_group_t group, const char *name, @@ -267,6 +267,19 @@ apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p, return md_store_save(store, p, group, name, MD_FN_CHAIN, MD_SV_CHAIN, chain, 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) +{ + return md_store_load(store, group, name, MD_FN_PUBCERT, 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) +{ + return md_store_save(store, p, group, name, MD_FN_PUBCERT, MD_SV_CHAIN, pubcert, create); +} + typedef struct { md_store_t *store; md_store_group_t group; diff --git a/modules/md/md_store.h b/modules/md/md_store.h index 86a8639603..8f968c76aa 100644 --- a/modules/md/md_store.h +++ b/modules/md/md_store.h @@ -146,5 +146,11 @@ apr_status_t md_chain_load(md_store_t *store, md_store_group_t group, apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name, struct apr_array_header_t *chain, 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); +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); + #endif /* mod_md_md_store_h */ diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c index 2626fde9f7..d37968a522 100644 --- a/modules/md/md_store_fs.c +++ b/modules/md/md_store_fs.c @@ -37,7 +37,7 @@ /**************************************************************************************************/ /* file system based implementation of md_store_t */ -#define MD_STORE_VERSION 1.0 +#define MD_STORE_VERSION 2 typedef struct { apr_fileperms_t dir; @@ -60,8 +60,6 @@ struct md_store_fs_t { int port_80; int port_443; - - const unsigned char *dupkey; }; #define FS_STORE(store) (md_store_fs_t*)(((char*)store)-offsetof(md_store_fs_t, s)) @@ -98,32 +96,88 @@ static apr_status_t init_store_file(md_store_fs_t *s_fs, const char *fname, { md_json_t *json = md_json_create(p); const char *key64; + unsigned char *key; apr_status_t rv; - unsigned char key[FS_STORE_KLEN]; - int i; md_json_sets(MOD_MD_VERSION, json, MD_KEY_VERSION, NULL); md_json_setn(MD_STORE_VERSION, json, MD_KEY_STORE, MD_KEY_VERSION, NULL); - /*if (APR_SUCCESS != (rv = md_rand_bytes(key, sizeof(key), p))) { + s_fs->key_len = FS_STORE_KLEN; + s_fs->key = key = apr_pcalloc(p, FS_STORE_KLEN); + if (APR_SUCCESS != (rv = md_rand_bytes(key, s_fs->key_len, p))) { return rv; - }*/ - for (i = 0; i < FS_STORE_KLEN; ++i) { - key[i] = 'a' + (i % 26); - } - - s_fs->key_len = sizeof(key); - s_fs->key = apr_pcalloc(p, sizeof(key) + 1); - memcpy((void*)s_fs->key, key, sizeof(key)); - s_fs->dupkey = apr_pmemdup(p, key, sizeof(key)); + } - key64 = md_util_base64url_encode((char *)key, sizeof(key), ptemp); + key64 = md_util_base64url_encode((char *)key, s_fs->key_len, ptemp); md_json_sets(key64, json, MD_KEY_KEY, NULL); - rv = md_json_fcreatex(json, ptemp, MD_JSON_FMT_INDENT, fname, MD_FPROT_F_UONLY); memset((char*)key64, 0, strlen(key64)); - assert(memcmp(s_fs->key, s_fs->dupkey, FS_STORE_KLEN) == 0); + return rv; +} + +static apr_status_t rename_pkey(void *baton, apr_pool_t *p, apr_pool_t *ptemp, + const char *dir, const char *name, + apr_filetype_e ftype) +{ + const char *from, *to; + apr_status_t rv = APR_SUCCESS; + + if (APR_SUCCESS == (rv = md_util_path_merge(&from, ptemp, dir, name, NULL)) + && APR_SUCCESS == (rv = md_util_path_merge(&to, ptemp, dir, MD_FN_PRIVKEY, NULL))) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "renaming %s/%s to %s", + dir, name, MD_FN_PRIVKEY); + return apr_file_rename(from, to, ptemp); + } + return rv; +} + +static apr_status_t mk_pubcert(void *baton, apr_pool_t *p, apr_pool_t *ptemp, + const char *dir, const char *name, + apr_filetype_e ftype) +{ + md_cert_t *cert; + apr_array_header_t *chain, *pubcert; + const char *fname, *fpubcert; + apr_status_t rv = APR_SUCCESS; + + if ( APR_SUCCESS == (rv = md_util_path_merge(&fpubcert, ptemp, dir, MD_FN_PUBCERT, NULL)) + && APR_STATUS_IS_ENOENT((rv = md_chain_fload(&pubcert, ptemp, fpubcert))) + && APR_SUCCESS == (rv = md_util_path_merge(&fname, ptemp, dir, name, NULL)) + && APR_SUCCESS == (rv = md_cert_fload(&cert, ptemp, fname)) + && APR_SUCCESS == (rv = md_util_path_merge(&fname, ptemp, dir, MD_FN_CHAIN, NULL))) { + + rv = md_chain_fload(&chain, ptemp, fname); + if (APR_STATUS_IS_ENOENT(rv)) { + chain = apr_array_make(ptemp, 1, sizeof(md_cert_t*)); + rv = APR_SUCCESS; + } + if (APR_SUCCESS == rv) { + pubcert = apr_array_make(ptemp, chain->nelts + 1, sizeof(md_cert_t*)); + APR_ARRAY_PUSH(pubcert, md_cert_t *) = cert; + apr_array_cat(pubcert, chain); + rv = md_chain_fsave(pubcert, ptemp, fpubcert, MD_FPROT_F_UONLY); + } + } + return rv; +} + +static apr_status_t upgrade_from_1_0(md_store_fs_t *s_fs, apr_pool_t *p, apr_pool_t *ptemp) +{ + md_store_group_t g; + apr_status_t rv = APR_SUCCESS; + + /* Migrate pkey.pem -> privkey.pem */ + for (g = MD_SG_NONE; g < MD_SG_COUNT && APR_SUCCESS == rv; ++g) { + rv = md_util_files_do(rename_pkey, s_fs, p, s_fs->base, + md_store_group_name(g), "*", "pkey.pem", NULL); + } + /* Generate fullcert.pem from cert.pem and chain.pem where missing */ + rv = md_util_files_do(mk_pubcert, s_fs, p, s_fs->base, + md_store_group_name(MD_SG_DOMAINS), "*", MD_FN_CERT, NULL); + rv = md_util_files_do(mk_pubcert, s_fs, p, s_fs->base, + md_store_group_name(MD_SG_ARCHIVE), "*", MD_FN_CERT, NULL); + return rv; } @@ -131,7 +185,7 @@ static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname, apr_pool_t *p, apr_pool_t *ptemp) { md_json_t *json; - const char *key64; + const char *key64, *key; apr_status_t rv; double store_version; @@ -145,22 +199,34 @@ static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname, md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "version too new: %s", store_version); return APR_EINVAL; } - else if (store_version > MD_STORE_VERSION) { - /* migrate future store version changes */ - } - + key64 = md_json_dups(p, json, MD_KEY_KEY, NULL); if (!key64) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "missing key: %s", MD_KEY_KEY); return APR_EINVAL; } - s_fs->key_len = md_util_base64url_decode((const char **)&s_fs->key, key64, p); - if (s_fs->key_len < FS_STORE_KLEN) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "key too short: %d", s_fs->key_len); + s_fs->key_len = md_util_base64url_decode(&key, key64, p); + s_fs->key = (const unsigned char*)key; + if (s_fs->key_len != FS_STORE_KLEN) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "key length unexpected: %d", + s_fs->key_len); return APR_EINVAL; } - s_fs->dupkey = apr_pmemdup(p, s_fs->key, FS_STORE_KLEN); + + /* Need to migrate format? */ + if (store_version < MD_STORE_VERSION) { + if (store_version <= 1.0) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "migrating store v1.0 -> v1.1"); + rv = upgrade_from_1_0(s_fs, p, ptemp); + } + + if (APR_SUCCESS == rv) { + md_json_setn(MD_STORE_VERSION, json, MD_KEY_STORE, MD_KEY_VERSION, NULL); + rv = md_json_freplace(json, ptemp, MD_JSON_FMT_INDENT, fname, MD_FPROT_F_UONLY); + } + md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, "migrated store"); + } } return rv; } @@ -320,7 +386,6 @@ static void get_pass(const char **ppass, apr_size_t *plen, *plen = 0; } else { - assert(memcmp(s_fs->key, s_fs->dupkey, FS_STORE_KLEN) == 0); *ppass = (const char *)s_fs->key; *plen = s_fs->key_len; } @@ -374,10 +439,10 @@ static apr_status_t pfs_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l void **pvalue; apr_status_t rv; - group = va_arg(ap, int); + group = (md_store_group_t)va_arg(ap, int); name = va_arg(ap, const char *); aspect = va_arg(ap, const char *); - vtype = va_arg(ap, int); + vtype = (md_store_vtype_t)va_arg(ap, int); pvalue= va_arg(ap, void **); rv = fs_get_fname(&fpath, &s_fs->s, group, name, aspect, ptemp); @@ -438,8 +503,8 @@ static apr_status_t pfs_is_newer(void *baton, apr_pool_t *p, apr_pool_t *ptemp, int *pnewer; apr_status_t rv; - group1 = va_arg(ap, int); - group2 = va_arg(ap, int); + group1 = (md_store_group_t)va_arg(ap, int); + group2 = (md_store_group_t)va_arg(ap, int); name = va_arg(ap, const char*); aspect = va_arg(ap, const char*); pnewer = va_arg(ap, int*); @@ -483,10 +548,10 @@ static apr_status_t pfs_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l const char *pass; apr_size_t pass_len; - group = va_arg(ap, int); + group = (md_store_group_t)va_arg(ap, int); name = va_arg(ap, const char*); aspect = va_arg(ap, const char*); - vtype = va_arg(ap, int); + vtype = (md_store_vtype_t)va_arg(ap, int); value = va_arg(ap, void *); create = va_arg(ap, int); @@ -540,7 +605,7 @@ static apr_status_t pfs_remove(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va apr_finfo_t info; md_store_group_t group; - group = va_arg(ap, int); + group = (md_store_group_t)va_arg(ap, int); name = va_arg(ap, const char*); aspect = va_arg(ap, const char *); force = va_arg(ap, int); @@ -599,7 +664,7 @@ static apr_status_t pfs_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_ md_store_group_t group; apr_status_t rv; - group = va_arg(ap, int); + group = (md_store_group_t)va_arg(ap, int); name = va_arg(ap, const char*); groupname = md_store_group_name(group); @@ -684,8 +749,8 @@ static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l int archive; apr_status_t rv; - from = va_arg(ap, int); - to = va_arg(ap, int); + from = (md_store_group_t)va_arg(ap, int); + to = (md_store_group_t)va_arg(ap, int); name = va_arg(ap, const char*); archive = va_arg(ap, int); diff --git a/modules/md/md_util.c b/modules/md/md_util.c index 01a0dfe945..8958a5d637 100644 --- a/modules/md/md_util.c +++ b/modules/md/md_util.c @@ -73,7 +73,7 @@ char *md_util_str_tolower(char *s) { char *orig = s; while (*s) { - *s = apr_tolower(*s); + *s = (char)apr_tolower(*s); ++s; } return orig; @@ -759,26 +759,28 @@ const char *md_print_duration(apr_pool_t *p, apr_interval_time_t duration) /* base64 url encoding ****************************************************************************/ -static const int BASE64URL_UINT6[] = { +#define N6 (unsigned int)-1 + +static const unsigned int BASE64URL_UINT6[] = { /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 1 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, /* 2 */ - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 3 */ - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 4 */ - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, /* 5 */ - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 6 */ - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 7 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 8 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 9 */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e */ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* f */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 0 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 1 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, 62, N6, N6, /* 2 */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, N6, N6, N6, N6, N6, N6, /* 3 */ + N6, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 4 */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, N6, N6, N6, N6, 63, /* 5 */ + N6, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 6 */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, N6, N6, N6, N6, N6, /* 7 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 8 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* 9 */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* a */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* b */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* c */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* d */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, /* e */ + N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6, N6 /* f */ }; -static const char BASE64URL_CHARS[] = { +static const unsigned char BASE64URL_CHARS[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 0 - 9 */ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 10 - 19 */ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', /* 20 - 29 */ @@ -788,21 +790,23 @@ static const char BASE64URL_CHARS[] = { '8', '9', '-', '_', ' ', ' ', ' ', ' ', ' ', ' ', /* 60 - 69 */ }; +#define BASE64URL_CHAR(x) BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ] + apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded, apr_pool_t *pool) { const unsigned char *e = (const unsigned char *)encoded; const unsigned char *p = e; unsigned char *d; - int n; - apr_size_t len, mlen, remain, i; + unsigned int n; + long len, mlen, remain, i; - while (*p && BASE64URL_UINT6[ *p ] != -1) { + while (*p && BASE64URL_UINT6[ *p ] != N6) { ++p; } len = p - e; mlen = (len/4)*4; - *decoded = apr_pcalloc(pool, len+1); + *decoded = apr_pcalloc(pool, (apr_size_t)len + 1); i = 0; d = (unsigned char*)*decoded; @@ -811,60 +815,59 @@ apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded, (BASE64URL_UINT6[ e[i+1] ] << 12) + (BASE64URL_UINT6[ e[i+2] ] << 6) + (BASE64URL_UINT6[ e[i+3] ])); - *d++ = n >> 16; - *d++ = n >> 8 & 0xffu; - *d++ = n & 0xffu; + *d++ = (unsigned char)(n >> 16); + *d++ = (unsigned char)(n >> 8 & 0xffu); + *d++ = (unsigned char)(n & 0xffu); } remain = len - mlen; switch (remain) { case 2: n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) + (BASE64URL_UINT6[ e[mlen+1] ] << 12)); - *d++ = n >> 16; + *d++ = (unsigned char)(n >> 16); remain = 1; break; case 3: n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) + (BASE64URL_UINT6[ e[mlen+1] ] << 12) + (BASE64URL_UINT6[ e[mlen+2] ] << 6)); - *d++ = n >> 16; - *d++ = n >> 8 & 0xffu; + *d++ = (unsigned char)(n >> 16); + *d++ = (unsigned char)(n >> 8 & 0xffu); remain = 2; break; default: /* do nothing */ break; } - return mlen/4*3 + remain; + return (apr_size_t)(mlen/4*3 + remain); } -const char *md_util_base64url_encode(const char *data, - apr_size_t dlen, apr_pool_t *pool) +const char *md_util_base64url_encode(const char *data, apr_size_t dlen, apr_pool_t *pool) { - long i, len = (int)dlen; + int i, len = (int)dlen; apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */ const unsigned char *udata = (const unsigned char*)data; - char *enc, *p = apr_pcalloc(pool, slen); + unsigned char *enc, *p = apr_pcalloc(pool, slen); enc = p; for (i = 0; i < len-2; i+= 3) { - *p++ = BASE64URL_CHARS[ (udata[i] >> 2) & 0x3fu ]; - *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + (udata[i+1] >> 4)) & 0x3fu ]; - *p++ = BASE64URL_CHARS[ ((udata[i+1] << 2) + (udata[i+2] >> 6)) & 0x3fu ]; - *p++ = BASE64URL_CHARS[ udata[i+2] & 0x3fu ]; + *p++ = BASE64URL_CHAR( (udata[i] >> 2) ); + *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) ); + *p++ = BASE64URL_CHAR( (udata[i+1] << 2) + (udata[i+2] >> 6) ); + *p++ = BASE64URL_CHAR( (udata[i+2]) ); } if (i < len) { - *p++ = BASE64URL_CHARS[ (udata[i] >> 2) & 0x3fu ]; + *p++ = BASE64URL_CHAR( (udata[i] >> 2) ); if (i == (len - 1)) { - *p++ = BASE64URL_CHARS[ (udata[i] << 4) & 0x3fu ]; + *p++ = BASE64URL_CHARS[ ((unsigned int)udata[i] << 4) & 0x3fu ]; } else { - *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + (udata[i+1] >> 4)) & 0x3fu ]; - *p++ = BASE64URL_CHARS[ (udata[i+1] << 2) & 0x3fu ]; + *p++ = BASE64URL_CHAR( (udata[i] << 4) + (udata[i+1] >> 4) ); + *p++ = BASE64URL_CHAR( (udata[i+1] << 2) ); } } *p++ = '\0'; - return enc; + return (char *)enc; } /******************************************************************************* @@ -874,12 +877,12 @@ const char *md_util_base64url_encode(const char *data, typedef struct { const char *s; apr_size_t slen; - int i; - int link_start; + apr_size_t i; + apr_size_t link_start; apr_size_t link_len; - int pn_start; + apr_size_t pn_start; apr_size_t pn_len; - int pv_start; + apr_size_t pv_start; apr_size_t pv_len; } link_ctx; @@ -960,9 +963,9 @@ static int skip_nonws(link_ctx *ctx) return (ctx->i < ctx->slen); } -static int find_chr(link_ctx *ctx, char c, int *pidx) +static unsigned int find_chr(link_ctx *ctx, char c, apr_size_t *pidx) { - int j; + apr_size_t j; for (j = ctx->i; j < ctx->slen; ++j) { if (ctx->s[j] == c) { *pidx = j; @@ -984,7 +987,7 @@ static int read_chr(link_ctx *ctx, char c) static int skip_qstring(link_ctx *ctx) { if (skip_ws(ctx) && read_chr(ctx, '\"')) { - int end; + apr_size_t end; if (find_chr(ctx, '\"', &end)) { ctx->i = end + 1; return 1; @@ -996,7 +999,7 @@ static int skip_qstring(link_ctx *ctx) static int skip_ptoken(link_ctx *ctx) { if (skip_ws(ctx)) { - int i; + apr_size_t i; for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) { /* nop */ } @@ -1013,7 +1016,7 @@ static int read_link(link_ctx *ctx) { ctx->link_start = ctx->link_len = 0; if (skip_ws(ctx) && read_chr(ctx, '<')) { - int end; + apr_size_t end; if (find_chr(ctx, '>', &end)) { ctx->link_start = ctx->i; ctx->link_len = end - ctx->link_start; @@ -1027,7 +1030,7 @@ static int read_link(link_ctx *ctx) static int skip_pname(link_ctx *ctx) { if (skip_ws(ctx)) { - int i; + apr_size_t i; for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) { /* nop */ } @@ -1068,7 +1071,7 @@ static int skip_param(link_ctx *ctx) static int pv_contains(link_ctx *ctx, const char *s) { - int pvstart = ctx->pv_start; + apr_size_t pvstart = ctx->pv_start; apr_size_t pvlen = ctx->pv_len; if (ctx->s[pvstart] == '\"' && pvlen > 1) { @@ -1078,7 +1081,7 @@ static int pv_contains(link_ctx *ctx, const char *s) if (pvlen > 0) { apr_size_t slen = strlen(s); link_ctx pvctx; - int i; + apr_size_t i; memset(&pvctx, 0, sizeof(pvctx)); pvctx.s = ctx->s + pvstart; @@ -1148,7 +1151,7 @@ static int find_url(void *baton, const char *key, const char *value) memset(&ctx, 0, sizeof(ctx)); ctx.s = value; - ctx.slen = (int)strlen(value); + ctx.slen = strlen(value); while (read_link(&ctx)) { while (skip_param(&ctx)) { diff --git a/modules/md/md_version.h b/modules/md/md_version.h index 41474e853e..306f40fec7 100644 --- a/modules/md/md_version.h +++ b/modules/md/md_version.h @@ -26,7 +26,7 @@ * @macro * Version number of the md module as c string */ -#define MOD_MD_VERSION "0.7.0-git" +#define MOD_MD_VERSION "0.8.1" /** * @macro @@ -34,7 +34,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 0x000700 +#define MOD_MD_VERSION_NUM 0x000801 #define MD_EXPERIMENTAL 0 #define MD_ACME_DEF_URL "https://acme-v01.api.letsencrypt.org/directory" diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c index 075dbb35c4..44025e4c8c 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -78,6 +78,9 @@ static void md_merge_srv(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) if (md->drive_mode == MD_DRIVE_DEFAULT) { md->drive_mode = md_config_geti(md->sc, MD_CONFIG_DRIVE_MODE); } + if (md->renew_norm <= 0) { + md->renew_norm = md_config_get_interval(md->sc, MD_CONFIG_RENEW_NORM); + } if (md->renew_window <= 0) { md->renew_window = md_config_get_interval(md->sc, MD_CONFIG_RENEW_WINDOW); } @@ -87,6 +90,10 @@ static void md_merge_srv(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; + + } } static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, apr_pool_t *p) @@ -460,6 +467,9 @@ static apr_status_t drive_md(md_watchdog *wd, md_t *md, apr_pool_t *ptemp) int errored, renew; char ts[APR_RFC822_DATE_LEN]; + if (md->state == MD_S_MISSING) { + rv = APR_INCOMPLETE; + } if (md->state == MD_S_COMPLETE && !md->expires) { /* This is our indicator that we did already renewed this managed domain * successfully and only wait on the next restart for it to activate */ @@ -508,7 +518,7 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) md_watchdog *wd = baton; apr_status_t rv = APR_SUCCESS; md_t *md; - apr_interval_time_t interval, now; + apr_time_t next_run, now; int i; switch (state) { @@ -519,9 +529,6 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) case AP_WATCHDOG_STATE_RUNNING: assert(wd->reg); - /* normally, we'd like to run at least twice a day */ - interval = apr_time_from_sec(MD_SECS_PER_DAY / 2); - wd->all_valid = 1; wd->valid_not_before = 0; wd->processed_count = 0; @@ -536,7 +543,13 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) for (i = 0; i < wd->mds->nelts; ++i) { md = APR_ARRAY_IDX(wd->mds, i, md_t *); - if (APR_SUCCESS != (rv = drive_md(wd, md, ptemp))) { + rv = drive_md(wd, md, ptemp); + + if (APR_STATUS_IS_INCOMPLETE(rv)) { + /* configuration not complete, this MD cannot be driven further */ + wd->all_valid = 0; + } + else if (APR_SUCCESS != rv) { wd->all_valid = 0; ++wd->error_count; ap_log_error( APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO(10056) @@ -546,50 +559,57 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) /* Determine when we want to run next */ wd->error_runs = wd->error_count? (wd->error_runs + 1) : 0; + if (wd->all_valid) { - now = apr_time_now(); - if (wd->next_valid > now && (wd->next_valid - now < interval)) { - interval = wd->next_valid - now; - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, - "Delaying activation of %d Managed Domain%s by %s", - wd->processed_count, (wd->processed_count > 1)? "s have" : " has", - md_print_duration(ptemp, interval)); - } - else { - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, - "all managed domains are valid"); - } + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, "all managed domains are valid"); } - else { - /* back off duration, depending on the errors we encounter in a row */ - interval = apr_time_from_sec(5 << (wd->error_runs - 1)); - if (interval > apr_time_from_sec(60*60)) { - interval = apr_time_from_sec(60*60); - } - ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10057) - "encountered errors for the %d. time, next run in %d seconds", - wd->error_runs, (int)apr_time_sec(interval)); + else if (wd->error_count == 0) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO() + "all managed domains driven as far as possible"); + } + + now = apr_time_now(); + /* normally, we'd like to run at least twice a day */ + next_run = now + apr_time_from_sec(MD_SECS_PER_DAY / 2); + + /* Unless we know of an MD change before that */ + if (wd->next_change > 0 && wd->next_change < next_run) { + next_run = wd->next_change; } - /* We follow the chosen min_interval for re-evaluation, unless we - * know of a change (renewal) that happens before that. */ - if (wd->next_change) { - apr_interval_time_t until_next = wd->next_change - apr_time_now(); - if (until_next < interval) { - interval = until_next; + /* Or have to activate a new cert even before that */ + if (wd->next_valid > now && wd->next_valid < next_run) { + next_run = wd->next_valid; + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, + "Delaying activation of %d Managed Domain%s by %s", + wd->processed_count, (wd->processed_count > 1)? "s have" : " has", + md_print_duration(ptemp, next_run - now)); + } + + /* Or encountered errors and like to retry even before that */ + if (wd->error_count > 0) { + apr_interval_time_t delay; + + /* back off duration, depending on the errors we encounter in a row */ + delay = apr_time_from_sec(5 << (wd->error_runs - 1)); + if (delay > apr_time_from_sec(60*60)) { + delay = apr_time_from_sec(60*60); + } + if (now + delay < next_run) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10057) + "encountered errors for the %d. time, next try by %s", + wd->error_runs, md_print_duration(ptemp, delay)); + next_run = now + delay; } } - /* Set when we'd like to be run next time. - * TODO: it seems that this is really only ticking down when the server - * runs. When you wake up a hibernated machine, the watchdog will not run right away - */ if (APLOGdebug(wd->s)) { - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, "next run in %s", - md_print_duration(ptemp, interval)); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO() + "next run in %s", md_print_duration(ptemp, next_run - now)); } - wd_set_interval(wd->watchdog, interval, wd, run_watchdog); + wd_set_interval(wd->watchdog, next_run - now, wd, run_watchdog); break; + case AP_WATCHDOG_STATE_STOPPING: ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10058) "md watchdog stopping"); @@ -684,7 +704,7 @@ static apr_status_t start_watchdog(apr_array_header_t *names, apr_pool_t *p, if (!wd->mds->nelts) { ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10065) "no managed domain in state to drive, no watchdog needed, " - "will check again on next server restart"); + "will check again on next server (graceful) restart"); apr_pool_destroy(wd->p); return APR_SUCCESS; } diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index 1b39d81d70..17fbbe3f2c 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -25,6 +25,7 @@ #include <http_vhost.h> #include "md.h" +#include "md_crypt.h" #include "md_util.h" #include "mod_md_private.h" #include "mod_md_config.h" @@ -39,6 +40,7 @@ #define MD_CMD_MEMBER "MDMember" #define MD_CMD_MEMBERS "MDMembers" #define MD_CMD_PORTMAP "MDPortMap" +#define MD_CMD_PKEYS "MDPrivateKeys" #define MD_CMD_RENEWWINDOW "MDRenewWindow" #define MD_CMD_STOREDIR "MDStoreDir" @@ -65,8 +67,9 @@ static md_srv_conf_t defconf = { 1, MD_DRIVE_AUTO, 0, - apr_time_from_sec(14 * MD_SECS_PER_DAY), - + NULL, + apr_time_from_sec(90 * MD_SECS_PER_DAY), /* If the cert lifetime were 90 days, renew */ + apr_time_from_sec(30 * MD_SECS_PER_DAY), /* 30 days before. Adjust to actual lifetime */ MD_ACME_DEF_URL, "ACME", NULL, @@ -109,6 +112,8 @@ static void srv_conf_props_clear(md_srv_conf_t *sc) sc->transitive = DEF_VAL; sc->drive_mode = DEF_VAL; sc->must_staple = DEF_VAL; + sc->pkey_spec = NULL; + sc->renew_norm = DEF_VAL; sc->renew_window = DEF_VAL; sc->ca_url = NULL; sc->ca_proto = NULL; @@ -121,6 +126,8 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from) to->transitive = from->transitive; to->drive_mode = from->drive_mode; to->must_staple = from->must_staple; + to->pkey_spec = from->pkey_spec; + to->renew_norm = from->renew_norm; to->renew_window = from->renew_window; to->ca_url = from->ca_url; to->ca_proto = from->ca_proto; @@ -133,6 +140,8 @@ 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->drive_mode != DEF_VAL) md->drive_mode = from->drive_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->renew_norm != DEF_VAL) md->renew_norm = from->renew_norm; if (from->renew_window != DEF_VAL) md->renew_window = from->renew_window; if (from->ca_url) md->ca_url = from->ca_url; @@ -166,6 +175,9 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv) nsc->transitive = (add->transitive != DEF_VAL)? add->transitive : base->transitive; nsc->drive_mode = (add->drive_mode != DEF_VAL)? add->drive_mode : base->drive_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->renew_window = (add->renew_norm != DEF_VAL)? add->renew_norm : base->renew_norm; nsc->renew_window = (add->renew_window != DEF_VAL)? add->renew_window : base->renew_window; nsc->ca_url = add->ca_url? add->ca_url : base->ca_url; @@ -242,7 +254,7 @@ static const char *md_config_sec_start(cmd_parms *cmd, void *mconfig, const char return MD_CMD_MD_SECTION "> directive missing closing '>'"; } - arg = apr_pstrndup(cmd->pool, arg, endp-arg); + arg = apr_pstrndup(cmd->pool, arg, (apr_size_t)(endp-arg)); if (!arg || !*arg) { return MD_CMD_MD_SECTION " > section must specify a unique domain name"; } @@ -444,23 +456,54 @@ static apr_status_t duration_parse(const char *value, apr_interval_time_t *ptime return rv; } +static apr_status_t percentage_parse(const char *value, int *ppercent) +{ + char *endp; + apr_int64_t n; + + n = apr_strtoi64(value, &endp, 10); + if (errno) { + return errno; + } + if (*endp == '%') { + if (n < 0 || n >= 100) { + return APR_BADARG; + } + *ppercent = (int)n; + return APR_SUCCESS; + } + return APR_EINVAL; +} + static const char *md_config_set_renew_window(cmd_parms *cmd, void *dc, const char *value) { md_srv_conf_t *config = md_config_get(cmd->server); const char *err; apr_interval_time_t timeout; - - /* Inspired by http_core.c */ - if (duration_parse(value, &timeout, "d") != APR_SUCCESS) { - return "MDRenewWindow has wrong format"; - } - + int percent; + if (!inside_section(cmd, MD_CMD_MD_SECTION) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) { return err; } - config->renew_window = timeout; - return NULL; + + /* Inspired by http_core.c */ + if (duration_parse(value, &timeout, "d") == APR_SUCCESS) { + config->renew_norm = 0; + config->renew_window = timeout; + return NULL; + } + else { + switch (percentage_parse(value, &percent)) { + case APR_SUCCESS: + config->renew_norm = 100; + config->renew_window = percent; + return NULL; + case APR_BADARG: + return "MDRenewWindow as percent must be less than 100"; + } + } + return "MDRenewWindow has unrecognized format"; } static const char *md_config_set_store_dir(cmd_parms *cmd, void *arg, const char *value) @@ -555,6 +598,57 @@ static const char *md_config_set_cha_tyes(cmd_parms *cmd, void *dc, return NULL; } +static const char *md_config_set_pkeys(cmd_parms *cmd, void *arg, + int argc, char *const argv[]) +{ + md_srv_conf_t *config = md_config_get(cmd->server); + const char *err, *ptype; + apr_int64_t bits; + + if (!inside_section(cmd, MD_CMD_MD_SECTION) + && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) { + return err; + } + if (argc <= 0) { + 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->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 (argc == 2) { + bits = (int)apr_atoi64(argv[1]); + if (bits < 2048 || bits >= INT_MAX) { + return "must be a 2048 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."; + } + } + else { + return "key type 'RSA' has only one optinal parameter, the number of bits"; + } + + if (!config->pkey_spec) { + config->pkey_spec = apr_pcalloc(cmd->pool, sizeof(*config->pkey_spec)); + } + 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); +} const command_rec md_cmds[] = { AP_INIT_TAKE1( MD_CMD_CA, md_config_set_ca, NULL, RSRC_CONF, @@ -582,6 +676,8 @@ const command_rec md_cmds[] = { "to indicate that the server port 8000 is reachable as port 80 from the " "internet. Use 80:- to indicate that port 80 is not reachable from " "the outside."), + AP_INIT_TAKE_ARGV( MD_CMD_PKEYS, md_config_set_pkeys, NULL, RSRC_CONF, + "set the type and parameters for private key generation"), AP_INIT_TAKE1( MD_CMD_STOREDIR, md_config_set_store_dir, NULL, RSRC_CONF, "the directory for file system storage of managed domain data."), AP_INIT_TAKE1( MD_CMD_RENEWWINDOW, md_config_set_renew_window, NULL, RSRC_CONF, @@ -654,6 +750,8 @@ int md_config_geti(const md_srv_conf_t *sc, md_config_var_t var) apr_interval_time_t md_config_get_interval(const md_srv_conf_t *sc, md_config_var_t var) { switch (var) { + case MD_CONFIG_RENEW_NORM: + return (sc->renew_norm != DEF_VAL)? sc->renew_norm : defconf.renew_norm; case MD_CONFIG_RENEW_WINDOW: return (sc->renew_window != DEF_VAL)? sc->renew_window : defconf.renew_window; default: diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h index 4996b5295f..e5dc4aa6fc 100644 --- a/modules/md/mod_md_config.h +++ b/modules/md/mod_md_config.h @@ -17,6 +17,7 @@ #define mod_md_md_config_h struct md_store_t; +struct md_pkey_spec_t; typedef enum { MD_CONFIG_CA_URL, @@ -26,6 +27,7 @@ typedef enum { MD_CONFIG_DRIVE_MODE, MD_CONFIG_LOCAL_80, MD_CONFIG_LOCAL_443, + MD_CONFIG_RENEW_NORM, MD_CONFIG_RENEW_WINDOW, MD_CONFIG_TRANSITIVE, } md_config_var_t; @@ -51,6 +53,10 @@ typedef struct md_srv_conf_t { int transitive; /* != 0 iff VirtualHost names/aliases are auto-added */ int drive_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 */ + apr_interval_time_t renew_norm; /* If > 0, use as normalizing value for cert lifetime + * Example: renew_norm=90d renew_win=30d, cert lives + * for 12 days => renewal 4 days before */ apr_interval_time_t renew_window; /* time before expiration that starts renewal */ const char *ca_url; /* url of CA certificate service */ diff --git a/modules/md/mod_md_os.c b/modules/md/mod_md_os.c index 3c599e17da..4b7cae7bab 100644 --- a/modules/md/mod_md_os.c +++ b/modules/md/mod_md_os.c @@ -34,7 +34,7 @@ #include "md_util.h" #include "mod_md_os.h" -apr_status_t md_try_chown(const char *fname, int uid, int gid, apr_pool_t *p) +apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p) { #if AP_NEED_SET_MUTEX_PERMS if (-1 == chown(fname, (uid_t)uid, (gid_t)gid)) { diff --git a/modules/md/mod_md_os.h b/modules/md/mod_md_os.h index 9f5c2b6e8f..b4b59d8c0f 100644 --- a/modules/md/mod_md_os.h +++ b/modules/md/mod_md_os.h @@ -20,7 +20,7 @@ * Try chown'ing the file/directory. Give id -1 to not change uid/gid. * Will return APR_ENOTIMPL on platforms not supporting this operation. */ -apr_status_t md_try_chown(const char *fname, int uid, int gid, apr_pool_t *p); +apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p); /** * Make a file or directory read/write(/searchable) by httpd workers. diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index 79e9a58a30..9b9e451ef7 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -1723,8 +1723,9 @@ static apr_status_t ssl_init_server_ctx(server_rec *s, else if (APR_STATUS_IS_EAGAIN(rv)) { /* Managed Domain not ready yet. This is not a reason to fail the config */ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10085) - "Init: (%s) disabling this host for now as certificate/key data " - "for the Managed Domain is incomplete.", ssl_util_vhostid(p, s)); + "Init: %s will respond with '503 Service Unavailable' for now. This " + "host is part of a Managed Domain, but no SSL certificate is " + "available (yet).", ssl_util_vhostid(p, s)); pks->service_unavailable = 1; return APR_SUCCESS; } -- 2.40.0