static apr_status_t acme_renew()

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;
}