-*- 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
</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>
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 */
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 {
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 */
#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"
#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"
#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"
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;
};
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))) {
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;
}
}
const char *nonce;
int max_retries;
- unsigned int pkey_bits;
};
/**
* 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.
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");
}
}
- 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;
|| (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) {
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;
#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 */
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;
}
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;
}
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;
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;
}
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;
}
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;
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);
}
}
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;
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);
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)) {
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;
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);
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;
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;
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,
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);
}
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);
}
/* 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;
* 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
}
+ 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,
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;
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);
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;
}
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;
}
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*);
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);
}
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;
#include "md_json.h"
#include "md.h"
+#include "md_crypt.h"
#include "md_log.h"
#include "md_store.h"
#include "md_util.h"
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);
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);
}
return n;
}
-
/**************************************************************************************************/
/* format conversion */
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) {
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);
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);
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*));
#include "md.h"
#include "md_crypt.h"
+#include "md_json.h"
#include "md_log.h"
#include "md_http.h"
#include "md_util.h"
{
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;
*/
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)
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;
}
/**************************************************************************************************/
/* 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));
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,
void *cb_baton = NULL;
passwd_ctx ctx;
unsigned long err;
+ int i;
if (!bio) {
return APR_ENOMEM;
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;
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;
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;
}
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
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);
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) {
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;
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;
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 {
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);
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;
}
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:
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;
}
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);
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 */
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);
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 */
}
}
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;
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;
}
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) {
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);
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,
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;
{
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;
}
}
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) {
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:
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;
}
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);
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);
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,
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;
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 */
/**************************************************************************************************/
/* 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;
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))
{
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;
}
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;
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;
}
*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;
}
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);
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*);
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);
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);
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);
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);
{
char *orig = s;
while (*s) {
- *s = apr_tolower(*s);
+ *s = (char)apr_tolower(*s);
++s;
}
return orig;
/* 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 */
'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;
(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;
}
/*******************************************************************************
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;
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;
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;
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 */
}
{
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;
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 */
}
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) {
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;
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)) {
* @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
* 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"
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);
}
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)
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 */
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) {
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;
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)
/* 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");
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;
}
#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"
#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"
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,
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;
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;
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;
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;
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";
}
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)
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,
"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,
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:
#define mod_md_md_config_h
struct md_store_t;
+struct md_pkey_spec_t;
typedef enum {
MD_CONFIG_CA_URL,
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;
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 */
#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)) {
* 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.
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;
}