in modules/md/md_acme_drive.c [652:927]
static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
{
md_acme_driver_t *ad = d->baton;
int reset_staging = d->reset;
apr_status_t rv = APR_SUCCESS;
apr_time_t now, t, t2;
md_credentials_t *cred;
const char *ca_effective = NULL;
char ts[APR_RFC822_DATE_LEN];
int i, first = 0;
if (!d->md->ca_urls || d->md->ca_urls->nelts <= 0) {
/* No CA defined? This is checked in several other places, but lets be sure */
md_result_printf(result, APR_INCOMPLETE,
"The managed domain %s is missing MDCertificateAuthority", d->md->name);
goto out;
}
/* When not explicitly told to reset, we check the existing data. If
* it is incomplete or old, we trigger the reset for a clean start. */
if (!reset_staging) {
md_result_activity_setn(result, "Checking staging area");
rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ad->md, d->p);
if (APR_SUCCESS == rv) {
/* So, we have a copy in staging, but is it a recent or an old one? */
if (md_is_newer(d->store, MD_SG_DOMAINS, MD_SG_STAGING, d->md->name, d->p)) {
reset_staging = 1;
}
}
else if (APR_STATUS_IS_ENOENT(rv)) {
reset_staging = 1;
rv = APR_SUCCESS;
}
}
/* What CA are we using this time? */
if (ad->md && ad->md->ca_effective) {
/* There was one chosen on the previous run. Do we stick to it? */
ca_effective = ad->md->ca_effective;
if (d->md->ca_urls->nelts > 1 && d->attempt >= d->retry_failover) {
/* We have more than one CA to choose from and this is the (at least)
* third attempt with the same CA. Let's switch to the next one. */
int last_idx = md_array_str_index(d->md->ca_urls, ca_effective, 0, 1);
if (last_idx >= 0) {
int next_idx = (last_idx+1) % d->md->ca_urls->nelts;
ca_effective = APR_ARRAY_IDX(d->md->ca_urls, next_idx, const char*);
}
else {
/* not part of current configuration? */
ca_effective = NULL;
}
/* switching CA means we need to wipe the staging area */
reset_staging = 1;
}
}
if (!ca_effective) {
/* None chosen yet, pick the first one configured */
ca_effective = APR_ARRAY_IDX(d->md->ca_urls, 0, const char*);
}
if (md_log_is_level(d->p, MD_LOG_DEBUG)) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, "
"state=%d, attempt=%d, acme=%s, challenges='%s'",
d->md->name, d->md->state, d->attempt, ca_effective,
apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
}
if (reset_staging) {
md_result_activity_setn(result, "Resetting staging area");
/* reset the staging area for this domain */
rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
"%s: reset staging area", d->md->name);
if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
md_result_printf(result, rv, "resetting staging area");
goto out;
}
rv = APR_SUCCESS;
ad->md = NULL;
ad->order = NULL;
}
md_result_activity_setn(result, "Assessing current status");
if (ad->md && ad->md->state == MD_S_MISSING_INFORMATION) {
/* ToS agreement is missing. It makes no sense to drive this MD further */
md_result_printf(result, APR_INCOMPLETE,
"The managed domain %s is missing required information", d->md->name);
goto out;
}
if (ad->md && APR_SUCCESS == load_missing_creds(d)) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: all credentials staged", d->md->name);
goto ready;
}
/* Need to renew */
if (!ad->md || !md_array_str_eq(ad->md->ca_urls, d->md->ca_urls, 1)) {
md_result_activity_printf(result, "Resetting staging for %s", d->md->name);
/* re-initialize staging */
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: setup staging", d->md->name);
md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
ad->md = md_copy(d->p, d->md);
ad->md->ca_effective = ca_effective;
ad->md->ca_account = NULL;
ad->order = NULL;
rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
if (APR_SUCCESS != rv) {
md_result_printf(result, rv, "Saving MD information in staging area.");
md_result_log(result, MD_LOG_ERR);
goto out;
}
}
if (!ad->domains) {
ad->domains = md_dns_make_minimal(d->p, ad->md->domains);
}
ad->profile = ad->md->profile;
ad->profile_mandatory = ad->md->profile_mandatory;
md_result_activity_printf(result, "Contacting ACME server for %s at %s",
d->md->name, ca_effective);
if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, ca_effective,
d->proxy_url, d->ca_file))) {
md_result_printf(result, rv, "setup ACME communications");
md_result_log(result, MD_LOG_ERR);
goto out;
}
if (APR_SUCCESS != (rv = md_acme_setup(ad->acme, result))) {
md_result_log(result, MD_LOG_ERR);
goto out;
}
if (APR_SUCCESS != load_missing_creds(d)) {
for (i = 0; i < ad->creds->nelts; ++i) {
ad->cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
if (!ad->cred->pkey || md_array_is_empty(ad->cred->chain)) {
md_result_activity_printf(result, "Driving ACME to renew %s certificate for %s",
md_pkey_spec_name(ad->cred->spec),d->md->name);
/* The process of setting up challenges and verifying domain
* names differs between ACME versions. */
switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) {
case 1:
md_result_printf(result, APR_EINVAL,
"ACME server speaks version 1, an obsolete version of the ACME "
"protocol that is no longer supported.");
rv = result->status;
break;
default:
/* In principle, we only know ACME version 2. But we assume
that a new protocol which announces a directory with all members
from version 2 will act backward compatible.
This is, of course, an assumption...
*/
rv = md_acmev2_drive_renew(ad, d, result);
break;
}
if (APR_SUCCESS != rv) goto out;
if (md_array_is_empty(ad->cred->chain) || ad->chain_up_link) {
md_result_activity_printf(result, "Retrieving %s certificate chain for %s",
md_pkey_spec_name(ad->cred->spec), d->md->name);
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p,
"%s: retrieving %s certificate chain",
d->md->name, md_pkey_spec_name(ad->cred->spec));
rv = ad_chain_retrieve(d);
if (APR_SUCCESS != rv) {
md_result_printf(result, rv, "Unable to retrieve %s certificate chain.",
md_pkey_spec_name(ad->cred->spec));
goto out;
}
if (!md_array_is_empty(ad->cred->chain)) {
if (!ad->cred->pkey) {
rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, ad->cred->spec, &ad->cred->pkey, d->p);
if (APR_SUCCESS != rv) {
md_result_printf(result, rv, "Loading the private key.");
goto out;
}
}
if (ad->cred->pkey) {
rv = md_check_cert_and_pkey(ad->cred->chain, ad->cred->pkey);
if (APR_SUCCESS != rv) {
md_result_printf(result, rv, "Certificate and private key do not match.");
/* Delete the order */
md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
goto out;
}
}
rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, d->md->name,
ad->cred->spec, ad->cred->chain, 0);
if (APR_SUCCESS != rv) {
md_result_printf(result, rv, "Saving new %s certificate chain.",
md_pkey_spec_name(ad->cred->spec));
goto out;
}
}
}
/* Clean up the order, so the next pkey spec sets up a new one */
md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
}
}
}
/* As last step, cleanup any order we created so that challenge data
* may be removed asap. */
md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
/* first time this job ran through */
first = 1;
ready:
md_result_activity_setn(result, NULL);
/* we should have the complete cert chain now */
assert(APR_SUCCESS == load_missing_creds(d));
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
"%s: certificates ready, activation delay set to %s",
d->md->name, md_duration_format(d->p, d->activation_delay));
/* determine when it should be activated */
t = apr_time_now();
for (i = 0; i < ad->creds->nelts; ++i) {
cred = APR_ARRAY_IDX(ad->creds, i, md_credentials_t*);
t2 = md_cert_get_not_before(APR_ARRAY_IDX(cred->chain, 0, md_cert_t*));
if (t2 > t) t = t2;
}
md_result_delay_set(result, t);
/* If the existing MD is complete and un-expired, delay the activation
* to 24 hours after new cert is valid (if there is enough time left), so
* that cients with skewed clocks do not see a problem. */
now = apr_time_now();
if (d->md->state == MD_S_COMPLETE) {
apr_time_t valid_until, delay_activation;
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
"%s: state is COMPLETE, checking existing certificates", d->md->name);
valid_until = md_reg_valid_until(d->reg, d->md, d->p);
if (d->activation_delay < 0) {
/* special simulation for test case */
if (first) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
"%s: delay ready_at to now+1s", d->md->name);
md_result_delay_set(result, apr_time_now() + apr_time_from_sec(1));
}
}
else if (valid_until > now) {
delay_activation = d->activation_delay;
if (delay_activation > (valid_until - now)) {
delay_activation = (valid_until - now);
}
md_result_delay_set(result, result->ready_at + delay_activation);
}
}
/* There is a full set staged, to be loaded */
apr_rfc822_date(ts, result->ready_at);
if (result->ready_at > now) {
md_result_printf(result, APR_SUCCESS,
"The certificate for the managed domain has been renewed successfully and can "
"be used from %s on.", ts);
}
else {
md_result_printf(result, APR_SUCCESS,
"The certificate for the managed domain has been renewed successfully and can "
"be used (valid since %s). A graceful server restart now is recommended.", ts);
}
out:
return rv;
}