From: Stefan Eissing Date: Tue, 5 Sep 2017 13:10:11 +0000 (+0000) Subject: On the trunk: X-Git-Tag: 2.5.0-alpha~163 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e001bc80f14a836b320b415abdd34882a9aea0cb;p=apache On the trunk: *) mod_md: v0.9.1: - various fixes in MDRenewWindow handling when specifying percent. Serialization changed. If someone already used percent configurations, it is advised to change these to a new value, reload and change back to the wanted ones. - various fixes in handling of MDPrivateKeys when specifying 2048 bits (the default) explicitly. - mod_md version removed from top level md_store.json file. The store has its own format version to facilitate upgrades. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1807347 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 10ae87660a..4ca0e4cc82 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,15 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) mod_md: v0.9.1: + - various fixes in MDRenewWindow handling when specifying percent. Serialization changed. If + someone already used percent configurations, it is advised to change these to a new value, + reload and change back to the wanted ones. + - various fixes in handling of MDPrivateKeys when specifying 2048 bits (the default) explicitly. + - mod_md version removed from top level md_store.json file. The store has its own format version + to facilitate upgrades. + [Stefan Eissing] + *) mod_http2: DoS flow control protection is less agressive as long as active tasks stay below worker capacity. Intended to fix problems with media streaming. [Stefan Eissing] diff --git a/modules/md/md.h b/modules/md/md.h index fb33460831..174363a41f 100644 --- a/modules/md/md.h +++ b/modules/md/md.h @@ -28,7 +28,9 @@ struct md_srv_conf_t; struct md_pkey_spec_t; #define MD_TLSSNI01_DNS_SUFFIX ".acme.invalid" -#define MD_PKEY_RSA_BITS_DEF 2048U + +#define MD_PKEY_RSA_BITS_MIN 2048 +#define MD_PKEY_RSA_BITS_DEF 2048 typedef enum { MD_S_UNKNOWN, /* MD has not been analysed yet */ @@ -121,7 +123,7 @@ struct md_t { #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 "renew" #define MD_KEY_RENEW_WINDOW "renew-window" #define MD_KEY_RESOURCE "resource" #define MD_KEY_STATE "state" @@ -237,6 +239,11 @@ md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base); struct md_json_t *md_to_json (const md_t *md, apr_pool_t *p); md_t *md_from_json(struct md_json_t *json, apr_pool_t *p); +/** + * Determine if MD should renew its cert (if it has one) + */ +int md_should_renew(const md_t *md); + /**************************************************************************************************/ /* domain credentials */ diff --git a/modules/md/md_core.c b/modules/md/md_core.c index 8294ba505c..5582ae6b9b 100644 --- a/modules/md/md_core.c +++ b/modules/md/md_core.c @@ -201,6 +201,33 @@ md_t *md_create(apr_pool_t *p, apr_array_header_t *domains) return md; } +int md_should_renew(const md_t *md) +{ + apr_time_t now = apr_time_now(); + + if (md->expires <= now) { + return 1; + } + else if (md->expires > 0) { + 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 = (apr_time_t)(life * ((double)renew_win / md->renew_norm)); + } + + left = md->expires - now; + if (left <= renew_win) { + return 1; + } + } + return 0; +} + /**************************************************************************************************/ /* lifetime */ @@ -304,12 +331,13 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p) md_json_sets(ts, json, MD_KEY_CERT, MD_KEY_VALID_FROM, 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); + md_json_sets(apr_psprintf(p, "%ld%%", (long)(md->renew_window * 100L / md->renew_norm)), + json, MD_KEY_RENEW_WINDOW, NULL); } else { md_json_setl((long)apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL); } + md_json_setb(md_should_renew(md), json, MD_KEY_RENEW, 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); @@ -348,8 +376,18 @@ 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_norm = 0; md->renew_window = apr_time_from_sec(md_json_getl(json, MD_KEY_RENEW_WINDOW, NULL)); + if (md->renew_window <= 0) { + const char *s = md_json_gets(json, MD_KEY_RENEW_WINDOW, NULL); + if (s && strchr(s, '%')) { + int percent = atoi(s); + if (0 < percent && percent < 100) { + md->renew_norm = apr_time_from_sec(100 * MD_SECS_PER_DAY); + md->renew_window = apr_time_from_sec(percent * MD_SECS_PER_DAY); + } + } + } if (md_json_has_key(json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL)) { md->ca_challenges = apr_array_make(p, 5, sizeof(const char*)); md_json_dupsa(md->ca_challenges, p, json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL); diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c index 10ddf51ed9..7b27075a8f 100644 --- a/modules/md/md_crypt.c +++ b/modules/md/md_crypt.c @@ -191,7 +191,7 @@ md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p) break; case MD_PKEY_TYPE_RSA: md_json_sets("RSA", json, MD_KEY_TYPE, NULL); - if (spec->params.rsa.bits > 2048) { + if (spec->params.rsa.bits >= MD_PKEY_RSA_BITS_MIN) { md_json_setl(spec->params.rsa.bits, json, MD_KEY_BITS, NULL); } break; @@ -217,14 +217,36 @@ md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p) else if (!apr_strnatcasecmp("RSA", s)) { spec->type = MD_PKEY_TYPE_RSA; l = md_json_getl(json, MD_KEY_BITS, NULL); - if (l > 2048) { + if (l >= MD_PKEY_RSA_BITS_MIN) { spec->params.rsa.bits = (unsigned int)l; } + else { + spec->params.rsa.bits = MD_PKEY_RSA_BITS_DEF; + } } } return spec; } +int md_pkey_spec_eq(md_pkey_spec_t *spec1, md_pkey_spec_t *spec2) +{ + if (spec1 == spec2) { + return 1; + } + if (spec1 && spec2 && spec1->type == spec2->type) { + switch (spec1->type) { + case MD_PKEY_TYPE_DEFAULT: + return 1; + case MD_PKEY_TYPE_RSA: + if (spec1->params.rsa.bits == spec2->params.rsa.bits) { + return 1; + } + break; + } + } + return 0; +} + static md_pkey_t *make_pkey(apr_pool_t *p) { md_pkey_t *pkey = apr_pcalloc(p, sizeof(*pkey)); @@ -363,7 +385,7 @@ static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits) rv = APR_SUCCESS; } else { - md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "unable to generate new key"); + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "error generate pkey RSA %d", bits); *ppkey = NULL; rv = APR_EGENERAL; } diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h index 5a53655926..401e6403a3 100644 --- a/modules/md/md_crypt.h +++ b/modules/md/md_crypt.h @@ -80,6 +80,7 @@ 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 */ diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c index 96a294be7c..6a32508f97 100644 --- a/modules/md/md_reg.c +++ b/modules/md/md_reg.c @@ -151,7 +151,7 @@ static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, i /**************************************************************************************************/ /* state assessment */ -static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md) +static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md, int save_changes) { md_state_t state = MD_S_UNKNOWN; const md_creds_t *creds; @@ -166,7 +166,7 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md) md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: incomplete, without private key", md->name); } - else if (!creds->pubcert) { + else if (!creds->cert) { md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: incomplete, has key but no certificate", md->name); } @@ -216,41 +216,24 @@ out: state = MD_S_ERROR; md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name); } + + if (save_changes && md->state == state + && md->valid_from == valid_from && md->expires == expires) { + save_changes = 0; + } md->state = state; md->valid_from = valid_from; md->expires = expires; - return rv; -} - -static apr_status_t state_vinit(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap) -{ - md_reg_t *reg = baton; - md_t *md = va_arg(ap, md_t *); - - return state_init(reg, p, md); -} - -static apr_status_t md_state_init(md_reg_t *reg, md_t *md, apr_pool_t *p) -{ - return md_util_pool_vdo(state_vinit, reg, p, md, NULL); -} - -static md_t *state_check(md_reg_t *reg, md_t *md, apr_pool_t *p) -{ - if (md) { - int ostate = md->state; - if (APR_SUCCESS == state_init(reg, p, md) && md->state != ostate) { - md_save(reg->store, p, MD_SG_DOMAINS, md, 0); - } + if (save_changes && APR_SUCCESS == rv) { + return md_save(reg->store, p, MD_SG_DOMAINS, md, 0); } - return md; + return rv; } apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, apr_pool_t *p) { int renew = 0; int errored = 0; - apr_time_t now = apr_time_now(); switch (md->state) { case MD_S_UNKNOWN: @@ -271,31 +254,13 @@ apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, "md(%s): looks complete, but has unknown expiration date.", md->name); errored = 1; } - else if (md->expires <= now) { + else if (md->expires <= apr_time_now()) { /* Maybe we hibernated in the meantime? */ md->state = MD_S_EXPIRED; 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; - } + renew = md_should_renew(md); } break; case MD_S_INCOMPLETE: @@ -326,7 +291,7 @@ static int reg_md_iter(void *baton, md_store_t *store, md_t *md, apr_pool_t *pte reg_do_ctx *ctx = baton; if (!ctx->exclude || strcmp(ctx->exclude, md->name)) { - md = state_check(ctx->reg, (md_t*)md, ptemp); + state_init(ctx->reg, ptemp, (md_t*)md, 1); return ctx->cb(ctx->baton, ctx->reg, md); } return 1; @@ -357,7 +322,8 @@ md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p) md_t *md; if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, name, &md, p)) { - return state_check(reg, md, p); + state_init(reg, p, md, 1); + return md; } return NULL; } @@ -386,12 +352,15 @@ md_t *md_reg_find(md_reg_t *reg, const char *domain, apr_pool_t *p) ctx.md = NULL; md_reg_do(find_domain, &ctx, reg, p); - return state_check(reg, (md_t*)ctx.md, p); + if (ctx.md) { + state_init(reg, p, ctx.md, 1); + } + return ctx.md; } typedef struct { const md_t *md_checked; - const md_t *md; + md_t *md; const char *s; } find_overlap_ctx; @@ -420,7 +389,10 @@ md_t *md_reg_find_overlap(md_reg_t *reg, const md_t *md, const char **pdomain, a if (pdomain && ctx.s) { *pdomain = ctx.s; } - return state_check(reg, (md_t*)ctx.md, p); + if (ctx.md) { + state_init(reg, p, ctx.md, 1); + } + return ctx.md; } apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p, @@ -447,7 +419,7 @@ static apr_status_t p_md_add(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l md = va_arg(ap, md_t *); mine = md_clone(ptemp, md); if (APR_SUCCESS == (rv = check_values(reg, ptemp, md, MD_UPD_ALL)) - && APR_SUCCESS == (rv = md_state_init(reg, mine, ptemp)) + && APR_SUCCESS == (rv = state_init(reg, ptemp, mine, 0)) && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, mine, 1))) { } return rv; @@ -525,9 +497,16 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v nmd->ca_challenges = (updates->ca_challenges? apr_array_copy(p, updates->ca_challenges) : NULL); } + 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)); + } + } if (fields && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, nmd, 0))) { - rv = md_state_init(reg, nmd, ptemp); + rv = state_init(reg, ptemp, nmd, 0); } return rv; } @@ -792,6 +771,13 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, smd->ca_challenges = NULL; fields |= MD_UPD_CA_CHALLENGES; } + if (!md_pkey_spec_eq(md->pkey_spec, smd->pkey_spec)) { + fields |= MD_UPD_PKEY_SPEC; + smd->pkey_spec = NULL; + if (md->pkey_spec) { + smd->pkey_spec = apr_pmemdup(p, md->pkey_spec, sizeof(md_pkey_spec_t)); + } + } if (fields) { rv = md_reg_update(reg, ptemp, smd->name, smd, fields); diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h index 1ab0dc0fd5..d889b8abd0 100644 --- a/modules/md/md_reg.h +++ b/modules/md/md_reg.h @@ -91,7 +91,8 @@ int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p); #define MD_UPD_DRIVE_MODE 0x0080 #define MD_UPD_RENEW_WINDOW 0x0100 #define MD_UPD_CA_CHALLENGES 0x0200 -#define MD_UPD_ALL 0x7FFF +#define MD_UPD_PKEY_SPEC 0x0400 +#define MD_UPD_ALL 0x7FFFFFFF /** * Update the given fields for the managed domain. Take the new diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c index 49f7394585..0ba017c256 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 2 +#define MD_STORE_VERSION 3 typedef struct { apr_fileperms_t dir; @@ -99,7 +99,6 @@ static apr_status_t init_store_file(md_store_fs_t *s_fs, const char *fname, unsigned char *key; apr_status_t rv; - 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); s_fs->key_len = FS_STORE_KLEN; @@ -217,9 +216,13 @@ static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname, /* 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"); + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "migrating store v1 -> v2"); rv = upgrade_from_1_0(s_fs, p, ptemp); } + if (store_version <= 2.0) { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "migrating store v2 -> v3"); + md_json_del(json, MD_KEY_VERSION, NULL); + } if (APR_SUCCESS == rv) { md_json_setn(MD_STORE_VERSION, json, MD_KEY_STORE, MD_KEY_VERSION, NULL); diff --git a/modules/md/md_version.h b/modules/md/md_version.h index 5ef47d426b..7c03dc7548 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.9.0" +#define MOD_MD_VERSION "0.9.1-git" /** * @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 0x000900 +#define MOD_MD_VERSION_NUM 0x000901 #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 4d8721651b..ac31aa9848 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -78,10 +78,8 @@ 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) { + if (md->renew_norm <= 0 && md->renew_window <= 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); } if (md->transitive < 0) { diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index 17fbbe3f2c..85e30d61e9 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -496,8 +496,8 @@ static const char *md_config_set_renew_window(cmd_parms *cmd, void *dc, const ch else { switch (percentage_parse(value, &percent)) { case APR_SUCCESS: - config->renew_norm = 100; - config->renew_window = percent; + config->renew_norm = apr_time_from_sec(100 * MD_SECS_PER_DAY); + config->renew_window = apr_time_from_sec(percent * MD_SECS_PER_DAY); return NULL; case APR_BADARG: return "MDRenewWindow as percent must be less than 100"; @@ -630,10 +630,11 @@ static const char *md_config_set_pkeys(cmd_parms *cmd, void *arg, } 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."; + 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 {