--- /dev/null
+ *) mod_md:
+ - Enabling ED25519 support and certificate transparency information when
+ building with libressl v3.5.0 and newer. Thanks to Giovanni Bechis.
+ - MDChallengeDns01 can now be configured for individual domains.
+ Thanks to Jérôme Billiras (@bilhackmac) for the initial PR.
+ - Fixed a bug found by Jérôme Billiras (@bilhackmac) that caused the challenge
+ teardown not being invoked as it should.
+ [Stefan Eissing]
\ No newline at end of file
method is possible. However, Let's Encrypt makes 'dns-01' the only
challenge available for wildcard certificates. If you require
one of those, you need to configure this.
+ </p><p>
+ It is now possible to use this directive inside a <directive module="mod_md">MDomain</directive>
+ section to specify a specific command for that domain. This allows to configure
+ a script specific for the particular DNS provider involved.
</p><p>
See the section about wildcard certificates above for more details.
</p>
struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
int stapling; /* if OCSP stapling is enabled */
-
+ const char *dns01_cmd; /* DNS challenge command, override global command */
+
int watched; /* if certificate is supervised (renew or expiration warning) */
const struct md_srv_conf_t *sc; /* server config where it was defined or NULL */
const char *defn_name; /* config file this MD was defined */
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,
md_pkeys_spec_t *key_specs,
- apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
apr_table_t *env, md_result_t *result, apr_pool_t *p)
{
const char *data;
(void)key_specs;
(void)env;
(void)acme_tls_1_domains;
- (void)mdomain;
+ (void)md;
if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) {
goto out;
static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
md_acme_t *acme, md_store_t *store,
md_pkeys_spec_t *key_specs,
- apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
apr_table_t *env, md_result_t *result, apr_pool_t *p)
{
const char *acme_id, *token;
int i;
(void)env;
- (void)mdomain;
+ (void)md;
if (md_array_str_index(acme_tls_1_domains, authz->domain, 0, 0) < 0) {
rv = APR_ENOTIMPL;
if (acme_tls_1_domains->nelts) {
static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
md_acme_t *acme, md_store_t *store,
md_pkeys_spec_t *key_specs,
- apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
apr_table_t *env, md_result_t *result, apr_pool_t *p)
{
const char *token;
(void)key_specs;
(void)acme_tls_1_domains;
- dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
+ dns01_cmd = md->dns01_cmd;
+ if (!dns01_cmd)
+ dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
if (!dns01_cmd) {
rv = APR_ENOTIMPL;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 command not set",
rv = md_crypt_sha256_digest64(&token, p, &data);
if (APR_SUCCESS != rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create dns-01 token for %s",
- mdomain, authz->domain);
+ md->name, authz->domain);
goto out;
}
apr_tokenize_to_argv(cmdline, (char***)&argv, p);
if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code))) {
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
- "%s: dns-01 setup command failed to execute for %s", mdomain, authz->domain);
+ "%s: dns-01 setup command failed to execute for %s", md->name, authz->domain);
goto out;
}
if (exit_code) {
rv = APR_EGENERAL;
md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p,
- "%s: dns-01 setup command returns %d for %s", mdomain, exit_code, authz->domain);
+ "%s: dns-01 setup command returns %d for %s", md->name, exit_code, authz->domain);
goto out;
}
}
/* challenge is setup, tell ACME server so it may (re)try verification */
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 setup succeeded for %s",
- mdomain, authz->domain);
+ md->name, authz->domain);
authz_req_ctx_init(&ctx, acme, NULL, authz, p);
ctx.challenge = cha;
rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx);
return rv;
}
-static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, const char *mdomain,
+static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, const md_t *md,
apr_table_t *env, apr_pool_t *p)
{
const char * const *argv;
int exit_code;
(void)store;
-
- dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
+
+ dns01_cmd = md->dns01_cmd;
+ if (!dns01_cmd)
+ dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
if (!dns01_cmd) {
rv = APR_ENOTIMPL;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 command not set for %s",
- mdomain, domain);
+ md->name, domain);
goto out;
}
if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, NULL, &exit_code)) || exit_code) {
md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
"%s: dns-01 teardown command failed (exit code=%d) for %s",
- mdomain, exit_code, domain);
+ md->name, exit_code, domain);
}
out:
return rv;
}
-static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, const char *mdomain,
+static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, const md_t *md,
apr_table_t *env, apr_pool_t *p)
{
+ (void)md;
(void)env;
- (void)mdomain;
return md_store_purge(store, p, MD_SG_CHALLENGES, domain);
}
typedef apr_status_t cha_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
md_acme_t *acme, md_store_t *store,
md_pkeys_spec_t *key_specs,
- apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
apr_table_t *env, md_result_t *result, apr_pool_t *p);
-typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, const char *mdomain,
+typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, const md_t *md,
apr_table_t *env, apr_pool_t *p);
typedef struct {
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, md_pkeys_spec_t *key_specs,
- apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
apr_table_t *env, apr_pool_t *p, const char **psetup_token,
md_result_t *result)
{
md_json_itera(find_type, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p,
"%s: challenge type '%s' for %s: %s",
- authz->domain, fctx.type, mdomain,
+ authz->domain, fctx.type, md->name,
fctx.accepted? "maybe acceptable" : "not applicable");
if (fctx.accepted) {
md_result_activity_printf(result, "Setting up challenge '%s' for domain %s",
fctx.accepted->type, authz->domain);
rv = CHA_TYPES[j].setup(fctx.accepted, authz, acme, store, key_specs,
- acme_tls_1_domains, mdomain, env, result, p);
+ acme_tls_1_domains, md, env, result, p);
if (APR_SUCCESS == rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
"%s: set up challenge '%s' for %s",
- authz->domain, fctx.accepted->type, mdomain);
- challenge_setup = CHA_TYPES[i].name;
+ authz->domain, fctx.accepted->type, md->name);
+ challenge_setup = CHA_TYPES[j].name;
goto out;
}
md_result_printf(result, rv, "error setting up challenge '%s' for %s, "
"for domain %s, looking for other option",
- fctx.accepted->type, authz->domain, mdomain);
+ fctx.accepted->type, authz->domain, md->name);
md_result_log(result, MD_LOG_INFO);
}
}
}
apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *token,
- const char *mdomain, apr_table_t *env, apr_pool_t *p)
+ const md_t *md, apr_table_t *env, apr_pool_t *p)
{
char *challenge, *domain;
int i;
for (i = 0; i < (int)CHA_TYPES_LEN; ++i) {
if (!apr_strnatcasecmp(CHA_TYPES[i].name, challenge)) {
if (CHA_TYPES[i].teardown) {
- return CHA_TYPES[i].teardown(store, domain, mdomain, env, p);
+ return CHA_TYPES[i].teardown(store, domain, md, env, p);
}
break;
}
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,
struct md_pkeys_spec_t *key_spec,
- apr_array_header_t *acme_tls_1_domains, const char *mdomain,
+ apr_array_header_t *acme_tls_1_domains, const md_t *md,
struct apr_table_t *env,
apr_pool_t *p, const char **setup_token,
struct md_result_t *result);
apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *setup_token,
- const char *mdomain, struct apr_table_t *env, apr_pool_t *p);
+ const md_t *md, struct apr_table_t *env, apr_pool_t *p);
#endif /* md_acme_authz_h */
ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
dis_alpn_acme = 1;
}
- if (!apr_table_get(d->env, MD_KEY_CMD_DNS01) && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0, 1) >= 0) {
+ if (!apr_table_get(d->env, MD_KEY_CMD_DNS01)
+ && NULL == d->md->dns01_cmd
+ && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0, 1) >= 0) {
ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0);
dis_dns = 1;
}
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->name, d->env);
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
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->name, d->env);
+ 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->name, d->env);
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
/* first time this job ran through */
first = 1;
}
md_result_activity_setn(result, "purging order information");
- md_acme_order_purge(d->store, d->p, MD_SG_STAGING, name, d->env);
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md, d->env);
md_result_activity_setn(result, "purging store tmp space");
rv = md_store_purge(d->store, d->p, load_group, name);
md_store_t *store = baton;
md_acme_order_t *order;
md_store_group_t group;
- const char *md_name, *setup_token;
+ const md_t *md;
+ const char *setup_token;
apr_table_t *env;
int i;
group = (md_store_group_t)va_arg(ap, int);
- md_name = va_arg(ap, const char *);
+ md = va_arg(ap, const md_t *);
env = va_arg(ap, apr_table_t *);
- if (APR_SUCCESS == md_acme_order_load(store, group, md_name, &order, p)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "order loaded for %s", md_name);
+ if (APR_SUCCESS == md_acme_order_load(store, group, md->name, &order, p)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "order loaded for %s", md->name);
for (i = 0; i < order->challenge_setups->nelts; ++i) {
setup_token = APR_ARRAY_IDX(order->challenge_setups, i, const char*);
if (setup_token) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
"order teardown setup %s", setup_token);
- md_acme_authz_teardown(store, setup_token, md_name, env, p);
+ md_acme_authz_teardown(store, setup_token, md, env, p);
}
}
}
- return md_store_remove(store, group, md_name, MD_FN_ORDER, ptemp, 1);
+ return md_store_remove(store, group, md->name, MD_FN_ORDER, ptemp, 1);
}
apr_status_t md_acme_order_purge(md_store_t *store, apr_pool_t *p, md_store_group_t group,
- const char *md_name, apr_table_t *env)
+ const md_t *md, apr_table_t *env)
{
- return md_util_pool_vdo(p_purge, store, p, group, md_name, env, NULL);
+ return md_util_pool_vdo(p_purge, store, p, group, md, env, NULL);
}
/**************************************************************************************************/
md->name, authz->domain);
rv = md_acme_authz_respond(authz, acme, store, challenge_types,
md->pks,
- md->acme_tls_1_domains, md->name,
+ md->acme_tls_1_domains, md,
env, p, &setup_token, result);
if (APR_SUCCESS != rv) {
goto leave;
md_acme_order_t *authz_set, int create);
apr_status_t md_acme_order_purge(struct md_store_t *store, apr_pool_t *p,
- md_store_group_t group, const char *md_name,
+ md_store_group_t group, const md_t *md,
apr_table_t *env);
apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme,
}
else if (!APR_STATUS_IS_ENOENT(rv)) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: loading order", md->name);
- md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md->name, d->env);
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md, d->env);
}
md_result_activity_setn(result, "Creating new order");
|| MD_ACME_ORDER_ST_INVALID == ad->order->status) {
/* order is invalid or no longer known at the ACME server */
ad->order = NULL;
- md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env);
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
}
else if (APR_SUCCESS != rv) {
goto leave;
if (!is_new_order && APR_STATUS_IS_EINVAL(rv)) {
/* found 'invalid' domains in previous order, need to start over */
ad->order = NULL;
- md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env);
+ md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md, d->env);
goto retry;
}
if (APR_SUCCESS != rv) goto leave;
}
md->acme_tls_1_domains = md_array_str_compact(p, src->acme_tls_1_domains, 0);
md->stapling = src->stapling;
+ if (src->dns01_cmd) md->dns01_cmd = apr_pstrdup(p, src->dns01_cmd);
if (src->cert_files) md->cert_files = md_array_str_clone(p, src->cert_files);
if (src->pkey_files) md->pkey_files = md_array_str_clone(p, src->pkey_files);
}
if (md->cert_files) md_json_setsa(md->cert_files, json, MD_KEY_CERT_FILES, NULL);
if (md->pkey_files) md_json_setsa(md->pkey_files, json, MD_KEY_PKEY_FILES, NULL);
md_json_setb(md->stapling > 0, json, MD_KEY_STAPLING, NULL);
+ if (md->dns01_cmd) md_json_sets(md->dns01_cmd, json, MD_KEY_CMD_DNS01, NULL);
if (md->ca_eab_kid && strcmp("none", md->ca_eab_kid)) {
md_json_sets(md->ca_eab_kid, json, MD_KEY_EAB, MD_KEY_KID, NULL);
if (md->ca_eab_hmac) md_json_sets(md->ca_eab_hmac, json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
md_json_dupsa(md->pkey_files, p, json, MD_KEY_PKEY_FILES, NULL);
}
md->stapling = (int)md_json_getb(json, MD_KEY_STAPLING, NULL);
-
+ md->dns01_cmd = md_json_dups(p, json, MD_KEY_CMD_DNS01, NULL);
if (md_json_has_key(json, MD_KEY_EAB, NULL)) {
md->ca_eab_kid = md_json_dups(p, json, MD_KEY_EAB, MD_KEY_KID, NULL);
md->ca_eab_hmac = md_json_dups(p, json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
#define MD_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L)
#endif
-#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x10100000L)
-/* Missing from LibreSSL and only available since OpenSSL v1.1.x */
+#if (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x3050000fL)) || (OPENSSL_VERSION_NUMBER < 0x10100000L)
+/* Missing from LibreSSL < 3.5.0 and only available since OpenSSL v1.1.x */
#ifndef OPENSSL_NO_CT
#define OPENSSL_NO_CT
#endif
*/
static apr_time_t md_asn1_time_get(const ASN1_TIME* time)
{
-#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
+#if OPENSSL_VERSION_NUMBER < 0x10002000L || (defined(LIBRESSL_VERSION_NUMBER) && \
+ LIBRESSL_VERSION_NUMBER < 0x3060000fL)
/* courtesy: https://stackoverflow.com/questions/10975542/asn1-time-to-time-t-conversion#11263731
* all bugs are mine */
apr_time_exp_t t;
curve = EC_curve_nid2nist(curve_nid);
}
#endif
-#if defined(NID_X25519) && !defined(LIBRESSL_VERSION_NUMBER)
+#if defined(NID_X25519) && (!defined(LIBRESSL_VERSION_NUMBER) || \
+ LIBRESSL_VERSION_NUMBER >= 0x3070000fL)
if (NID_undef == curve_nid && !apr_strnatcasecmp("X25519", curve)) {
curve_nid = NID_X25519;
curve = EC_curve_nid2nist(curve_nid);
*ppkey = make_pkey(p);
switch (curve_nid) {
-#if defined(NID_X25519) && !defined(LIBRESSL_VERSION_NUMBER)
+#if defined(NID_X25519) && (!defined(LIBRESSL_VERSION_NUMBER) || \
+ LIBRESSL_VERSION_NUMBER >= 0x3070000fL)
case NID_X25519:
/* no parameters */
if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL))
* @macro
* Version number of the md module as c string
*/
-#define MOD_MD_VERSION "2.4.19"
+#define MOD_MD_VERSION "2.4.21"
/**
* @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 0x020413
+#define MOD_MD_VERSION_NUM 0x020415
#define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory"
#define MD_TAILSCALE_DEF_URL "file://localhost/var/run/tailscale/tailscaled.sock"
NULL, /* ca eab hmac */
0, /* stapling */
1, /* staple others */
+ NULL, /* dns01_cmd */
NULL, /* currently defined md */
NULL, /* assigned md, post config */
0, /* is_ssl, set during mod_ssl post_config */
sc->ca_eab_hmac = NULL;
sc->stapling = DEF_VAL;
sc->staple_others = DEF_VAL;
+ sc->dns01_cmd = NULL;
}
static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
to->ca_eab_hmac = from->ca_eab_hmac;
to->stapling = from->stapling;
to->staple_others = from->staple_others;
+ to->dns01_cmd = from->dns01_cmd;
}
static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t *p)
if (from->ca_eab_kid) md->ca_eab_kid = from->ca_eab_kid;
if (from->ca_eab_hmac) md->ca_eab_hmac = from->ca_eab_hmac;
if (from->stapling != DEF_VAL) md->stapling = from->stapling;
+ if (from->dns01_cmd) md->dns01_cmd = from->dns01_cmd;
}
void *md_config_create_svr(apr_pool_t *pool, server_rec *s)
nsc->ca_eab_hmac = add->ca_eab_hmac? add->ca_eab_hmac : base->ca_eab_hmac;
nsc->stapling = (add->stapling != DEF_VAL)? add->stapling : base->stapling;
nsc->staple_others = (add->staple_others != DEF_VAL)? add->staple_others : base->staple_others;
+ nsc->dns01_cmd = (add->dns01_cmd)? add->dns01_cmd : base->dns01_cmd;
nsc->current = NULL;
return nsc;
md_srv_conf_t *sc = md_config_get(cmd->server);
const char *err;
- if ((err = md_conf_check_location(cmd, MD_LOC_NOT_MD))) {
+ if ((err = md_conf_check_location(cmd, MD_LOC_ALL))) {
return err;
}
- apr_table_set(sc->mc->env, MD_KEY_CMD_DNS01, arg);
+
+ if (inside_md_section(cmd)) {
+ sc->dns01_cmd = arg;
+ } else {
+ apr_table_set(sc->mc->env, MD_KEY_CMD_DNS01, arg);
+ }
+
(void)mconfig;
return NULL;
}
int stapling; /* OCSP stapling enabled */
int staple_others; /* Provide OCSP stapling for non-MD certificates */
+ const char *dns01_cmd; /* DNS challenge command, override global command */
+
md_t *current; /* md currently defined in <MDomainSet xxx> section */
struct apr_array_header_t *assigned; /* post_config: MDs that apply to this server */
int is_ssl; /* SSLEngine is enabled here */