Skip to content

Commit

Permalink
*) mod_md: update to v2.5.1
Browse files Browse the repository at this point in the history
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1923592 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
icing committed Feb 5, 2025
1 parent 0ba38c2 commit e07b7a2
Show file tree
Hide file tree
Showing 49 changed files with 863 additions and 383 deletions.
14 changes: 14 additions & 0 deletions changes-entries/md_v2.5.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
*) mod_md: update to version 2.5.1
- Added support for ACME profiles with new directives MDProfile and
MDProfileMandatory.
- When installing a custom CA file via `MDCACertificateFile`, also set the
libcurl option CURLSSLOPT_NO_REVOKE that suppresses complains by Schannel
(when curl is linked with it) about missing CRL/OCSP in certificates.
- Fixed handling of corrupted httpd.json and added test 300_30 for it.
File is removed on error and written again. Fixes #369.
- Added explanation in log for how to proceed when md_store.json could not be
parsed and prevented the server start.
- restored fixed to #336 and #337 which got lost in a sync with Apache svn
- Add Issue Name/Uris to certificate information in md-status handler
- MDomains with static certificate files have MDRenewMode "manual", unless
"always" is configured.
79 changes: 61 additions & 18 deletions docs/manual/mod/mod_md.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
ACME protocol (<a href="https://tools.ietf.org/html/rfc8555">RFC 8555</a>).
Certificates will be renewed by the module ahead of their expiration to account
for disruption in internet services. There are ways to monitor the status of all
certififcates managed this way and configurations that will run your own
certificates managed this way and configurations that will run your own
notification commands on renewal, expiration and errors.
</p><p>
Second, mod_md offers an alternate OCSP Stapling implementation. This works with
Expand Down Expand Up @@ -495,7 +495,7 @@ MDomain example2.org auto
For testing, CAs commonly offer a second service URL.
The 'test' service does not give certificates valid in a browser,
but are more relaxed in regard to rate limits.
This allows for verfication of your own setup before switching
This allows for verification of your own setup before switching
to the production service URL.
</p>
<example><title>LE Test Setup</title>
Expand Down Expand Up @@ -1299,7 +1299,7 @@ MDMessageCmd /etc/apache/md-message

<directivesynopsis>
<name>MDCertificateCheck</name>
<description>Set name and URL pattern for a certificate monitoring sitSet name and URL pattern for a certificate monitoring sitee</description>
<description>Set name and URL pattern for a certificate monitoring site.</description>
<syntax>MDCertificateCheck <var>name</var> <var>url</var></syntax>
<contextlist>
<context>server config</context>
Expand All @@ -1311,20 +1311,6 @@ MDMessageCmd /etc/apache/md-message
</usage>
</directivesynopsis>

<directivesynopsis>
<name>MDActivationDelay</name>
<description>How long to delay activation of new certificates</description>
<syntax>MDActivationDelay <var>duration</var></syntax>
<contextlist>
<context>server config</context>
</contextlist>
<compatibility>Available in version 2.4.42 and later</compatibility>
<usage>
<p>
</p>
</usage>
</directivesynopsis>

<directivesynopsis>
<name>MDContactEmail</name>
<description>Email address used for account registration</description>
Expand Down Expand Up @@ -1369,7 +1355,7 @@ MDMessageCmd /etc/apache/md-message
<p>
You can configure those globally or for a specific MDomain. Since
these values allow anyone to register under the same account, it is
adivsable to give the configuration file restricted permissions,
advisable to give the configuration file restricted permissions,
e.g. root only.
</p>
<p>
Expand Down Expand Up @@ -1537,4 +1523,61 @@ MDMessageCmd /etc/apache/md-message
</usage>
</directivesynopsis>

<directivesynopsis>
<name>MDProfile</name>
<description>Use a specific ACME profile from the CA</description>
<syntax>MDProfile name</syntax>
<contextlist>
<context>server config</context>
</contextlist>
<compatibility>Available in version 2.4.64 and later</compatibility>
<usage>
<p>
This about a non-standard ACME extension by Let's Encrypt.
</p><p>
Lets Encrypt announced they will add Certificate Profiles
support in their CA during 2025, beginning with their staging
servers. This, among some other details, let's you select the
lifetime of the certificates you get. The "default" profile
will keep the 90 days and a "tlsserver" profile will issue
certificates with only 6 days of validity.
</p><p>
If you do not change your mod_md configuration, you will
continue to get the 90 days certificates. Should you believe
that a shorter lifetime is beneficial for you (and take the
risk that the renewal time is way shorter),
you can configure the profile to use via 'MDProfile tlsserver'.
</p><p>
The profile names are defined by the CA. If a profile you
configure is not available, no profile will be used and
the certificate will be issue according to what the CA
considers default.
</p><p>
See <directive module="mod_md">MDProfileMandatory</directive>
on how to disable defaults for profiles.
</p>
</usage>
</directivesynopsis>

<directivesynopsis>
<name>MDProfileMandatory</name>
<description>Control if an MDProfile is mandatory.</description>
<syntax>MDProfileMandatory on|off</syntax>
<default>MDProfileMandatory off</default>
<contextlist>
<context>server config</context>
</contextlist>
<usage>
<p>
Controls if a <directive module="mod_md">MDProfile</directive>
you configure is mandatory or not. When mandatory and the CA
does not offer a configured profile, the certificate
renewal will fail.
</p><p>
When not mandatory and a profile is not offered by the CA,
renewals will be performed without specifying a profile and
the CA will issue a certificates according to its defaults.
</p>
</usage>
</directivesynopsis>
</modulesynopsis>
6 changes: 6 additions & 0 deletions modules/md/md.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ struct md_t {
struct apr_array_header_t *pkey_files; /* != NULL iff privkeys explicitly configured */
const char *ca_eab_kid; /* optional KEYID for external account binding */
const char *ca_eab_hmac; /* optional HMAC for external account binding */
const char *profile; /* optional cert profile to order */
int profile_mandatory; /* if profile, when given, is mandatory */

const char *state_descr; /* description of state of NULL */

Expand Down Expand Up @@ -154,6 +156,8 @@ struct md_t {
#define MD_KEY_HTTPS "https"
#define MD_KEY_ID "id"
#define MD_KEY_IDENTIFIER "identifier"
#define MD_KEY_ISSUER_NAME "issuer-name"
#define MD_KEY_ISSUER_URI "issuer-uri"
#define MD_KEY_KEY "key"
#define MD_KEY_KID "kid"
#define MD_KEY_KEYAUTHZ "keyAuthorization"
Expand All @@ -175,6 +179,8 @@ struct md_t {
#define MD_KEY_PKEY "privkey"
#define MD_KEY_PKEY_FILES "pkey-files"
#define MD_KEY_PROBLEM "problem"
#define MD_KEY_PROFILE "profile"
#define MD_KEY_PROFILE_MANDATORY "profile-mandatory"
#define MD_KEY_PROTO "proto"
#define MD_KEY_READY "ready"
#define MD_KEY_REGISTRATION "registration"
Expand Down
23 changes: 23 additions & 0 deletions modules/md/md_acme.c
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,15 @@ typedef struct {
md_result_t *result;
} update_dir_ctx;

static int collect_profiles(void *baton, const char* key, md_json_t *json)
{
update_dir_ctx *ctx = baton;
(void)json;
APR_ARRAY_PUSH(ctx->acme->api.v2.profiles, const char *) =
apr_pstrdup(ctx->acme->p, key);
return 1;
}

static apr_status_t update_directory(const md_http_response_t *res, void *data)
{
md_http_request_t *req = res->req;
Expand Down Expand Up @@ -728,6 +737,20 @@ static apr_status_t update_directory(const md_http_response_t *res, void *data)
acme->new_nonce_fn = acmev2_new_nonce;
acme->req_init_fn = acmev2_req_init;
acme->post_new_account_fn = acmev2_POST_new_account;

if (md_json_has_key(json, "meta", "profiles", NULL)) {
acme->api.v2.profiles = apr_array_make(acme->p, 5, sizeof(const char*));
md_json_iterkey(collect_profiles, data, json, "meta", "profiles", NULL);
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, req->pool,
"found %d profiles in ACME directory meta",
acme->api.v2.profiles->nelts);
}
else {
acme->api.v2.profiles = NULL;
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, req->pool,
"no profiles in ACME directory meta");

}
}
else if ((s = md_json_dups(acme->p, json, "new-authz", NULL))) {
acme->api.v1.new_authz = s;
Expand Down
1 change: 1 addition & 0 deletions modules/md/md_acme.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ struct md_acme_t {
const char *key_change;
const char *revoke_cert;
const char *new_nonce;
struct apr_array_header_t *profiles;
} v2;
} api;
const char *ca_agreement;
Expand Down
4 changes: 3 additions & 1 deletion modules/md/md_acme_drive.c
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,9 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
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,
Expand Down
2 changes: 2 additions & 0 deletions modules/md/md_acme_drive.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ typedef struct md_acme_driver_t {
md_t *md;
struct apr_array_header_t *domains;
apr_array_header_t *ca_challenges;
const char *profile;
int profile_mandatory;

int complete;
apr_array_header_t *creds; /* the new md_credentials_t */
Expand Down
20 changes: 12 additions & 8 deletions modules/md/md_acme_order.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,14 @@ typedef struct {
md_acme_order_t *order;
md_acme_t *acme;
const char *name;
const char *profile;
apr_array_header_t *domains;
md_result_t *result;
} order_ctx_t;

#define ORDER_CTX_INIT(ctx, p, o, a, n, d, r) \
#define ORDER_CTX_INIT(ctx, p, o, a, n, d, pf, r) \
(ctx)->p = (p); (ctx)->order = (o); (ctx)->acme = (a); \
(ctx)->name = (n); (ctx)->domains = d; (ctx)->result = r
(ctx)->name = (n); (ctx)->domains = d; (ctx)->profile = pf; (ctx)->result = r

static apr_status_t identifier_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
{
Expand All @@ -289,6 +290,8 @@ static apr_status_t on_init_order_register(md_acme_req_t *req, void *baton)

jpayload = md_json_create(req->p);
md_json_seta(ctx->domains, identifier_to_json, NULL, jpayload, "identifiers", NULL);
if (ctx->profile)
md_json_sets(ctx->profile, jpayload, "profile", NULL);

return md_acme_req_body_init(req, jpayload);
}
Expand Down Expand Up @@ -321,13 +324,14 @@ static apr_status_t on_order_upd(md_acme_t *acme, apr_pool_t *p, const apr_table
}

apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p,
const char *name, apr_array_header_t *domains)
const char *name, apr_array_header_t *domains,
const char *profile)
{
order_ctx_t ctx;
apr_status_t rv;

assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
ORDER_CTX_INIT(&ctx, p, NULL, acme, name, domains, NULL);
ORDER_CTX_INIT(&ctx, p, NULL, acme, name, domains, profile, NULL);
rv = md_acme_POST(acme, acme->api.v2.new_order, on_init_order_register, on_order_upd, NULL, NULL, &ctx);
*porder = (APR_SUCCESS == rv)? ctx.order : NULL;
return rv;
Expand All @@ -340,7 +344,7 @@ apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme,
apr_status_t rv;

assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
ORDER_CTX_INIT(&ctx, p, order, acme, NULL, NULL, result);
ORDER_CTX_INIT(&ctx, p, order, acme, NULL, NULL, NULL, result);
rv = md_acme_GET(acme, order->url, NULL, on_order_upd, NULL, NULL, &ctx);
if (APR_SUCCESS != rv && APR_SUCCESS != acme->last->status) {
md_result_dup(result, acme->last);
Expand Down Expand Up @@ -380,7 +384,7 @@ apr_status_t md_acme_order_await_ready(md_acme_order_t *order, md_acme_t *acme,
apr_status_t rv;

assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, result);

md_result_activity_setn(result, "Waiting for order to become ready");
rv = md_util_try(await_ready, &ctx, 0, timeout, 0, 0, 1);
Expand Down Expand Up @@ -423,7 +427,7 @@ apr_status_t md_acme_order_await_valid(md_acme_order_t *order, md_acme_t *acme,
apr_status_t rv;

assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, result);

md_result_activity_setn(result, "Waiting for finalized order to become valid");
rv = md_util_try(await_valid, &ctx, 0, timeout, 0, 0, 1);
Expand Down Expand Up @@ -552,7 +556,7 @@ apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acm
order_ctx_t ctx;
apr_status_t rv;

ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, NULL, result);

md_result_activity_printf(result, "Monitoring challenge status for %s", md->name);
rv = md_util_try(check_challenges, &ctx, 0, timeout, 0, 0, 1);
Expand Down
3 changes: 2 additions & 1 deletion modules/md/md_acme_order.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acm
/* ACMEv2 only ************************************************************************************/

apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p,
const char *name, struct apr_array_header_t *domains);
const char *name, struct apr_array_header_t *domains,
const char *profile);

apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme,
struct md_result_t *result, apr_pool_t *p);
Expand Down
29 changes: 28 additions & 1 deletion modules/md/md_acmev2_drive.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result, in
md_acme_driver_t *ad = d->baton;
apr_status_t rv;
md_t *md = ad->md;
const char *profile = NULL;

assert(ad->md);
assert(ad->acme);
Expand All @@ -77,7 +78,33 @@ static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result, in
}

md_result_activity_setn(result, "Creating new order");
rv = md_acme_order_register(&ad->order, ad->acme, d->p, d->md->name, ad->domains);
if (ad->profile) {
if(ad->acme->api.v2.profiles) {
int i;
for (i = 0; !profile && i < ad->acme->api.v2.profiles->nelts; ++i) {
const char *s = APR_ARRAY_IDX(ad->acme->api.v2.profiles, i, const char*);
if (!apr_strnatcasecmp(s, ad->profile))
profile = s;
}
}
if (profile)
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
"%s: ordering ACME profile '%s'", md->name, profile);
else if (ad->profile_mandatory) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
"%s: mandatory ACME profile '%s' is not offered by CA",
md->name, ad->profile);
rv = APR_EINVAL;
goto leave;
}
else {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
"%s: ACME profile '%s' is not offered by CA, continuing without",
md->name, ad->profile);
}
}

rv = md_acme_order_register(&ad->order, ad->acme, d->p, d->md->name, ad->domains, profile);
if (APR_SUCCESS !=rv) goto leave;
rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING, d->md->name, ad->order, 0);
if (APR_SUCCESS != rv) {
Expand Down
6 changes: 6 additions & 0 deletions modules/md/md_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
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);
}
if (md->profile) md_json_sets(md->profile, json, MD_KEY_PROFILE, NULL);
md_json_setb(md->profile_mandatory > 0, json, MD_KEY_PROFILE_MANDATORY, NULL);
return json;
}
return NULL;
Expand Down Expand Up @@ -383,6 +385,10 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
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);
}

md->profile_mandatory = (int)md_json_getb(json, MD_KEY_PROFILE_MANDATORY, NULL);
if (md_json_has_key(json, MD_KEY_PROFILE, NULL))
md->profile = md_json_dups(p, json, MD_KEY_PROFILE, NULL);
return md;
}
return NULL;
Expand Down
12 changes: 12 additions & 0 deletions modules/md/md_crypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,18 @@ int md_cert_covers_md(md_cert_t *cert, const md_t *md)
return 0;
}

const char *md_cert_get_issuer_name(const md_cert_t *cert, apr_pool_t *p)
{
X509_NAME *xname = X509_get_issuer_name(cert->x509);
if(xname) {
char *name, *s = X509_NAME_oneline(xname, NULL, 0);
name = apr_pstrdup(p, s);
OPENSSL_free(s);
return name;
}
return NULL;
}

apr_status_t md_cert_get_issuers_uri(const char **puri, const md_cert_t *cert, apr_pool_t *p)
{
apr_status_t rv = APR_ENOENT;
Expand Down
Loading

0 comments on commit e07b7a2

Please sign in to comment.