Mailing List Archive

svn commit: r1895289 [1/2] - in /httpd/httpd/branches/2.4.x: ./ changes-entries/ docs/manual/mod/ modules/md/
Author: icing
Date: Wed Nov 24 11:07:53 2021
New Revision: 1895289

URL: http://svn.apache.org/viewvc?rev=1895289&view=rev
Log:
Merge of 1893969,1894610,1894718,1895285,1895287 from trunk:

*) mod_md: Fix memory leak in case of failures to load the private key.
PR 65620 [ Filipe Casal <filipe.casal@trailofbits.com> ]
*) mod_md: adding v2.4.8 with the following changes
- Added support for ACME External Account Binding (EAB).
Use the new directive `MDExternalAccountBinding` to provide the
server with the value for key identifier and hmac as provided by
your CA.
While working on some servers, EAB handling is not uniform
across CAs. First tests with a Sectigo Certificate Manager in
demo mode are successful. But ZeroSSL, for example, seems to
regard EAB values as a one-time-use-only thing, which makes them
fail if you create a seconde account or retry the creation of the
first account with the same EAB.
- The directive 'MDCertificateAuthority' now checks if its parameter
is a http/https url or one of a set of known names. Those are
'LetsEncrypt', 'LetsEncrypt-Test', 'Buypass' and 'Buypass-Test'
for now and they are not case-sensitive.
The default of LetsEncrypt is unchanged.
- `MDContactEmail` can now be specified inside a `<MDomain dnsname>`
section.
- Treating 401 HTTP status codes for orders like 403, since some ACME
servers seem to prefer that for accessing oders from other accounts.
- When retrieving certificate chains, try to read the repsonse even
if the HTTP Content-Type is unrecognized.
- Fixed a bug that reset the error counter of a certificate renewal
and prevented the increasing delays in further attempts.
- Fixed the renewal process giving up every time on an already existing
order with some invalid domains. Now, if such are seen in a previous
order, a new order is created for a clean start over again.
See <https://github.com/icing/mod_md/issues/268>
- Fixed a mixup in md-status handler when static certificate files
and renewal was configured at the same time.
*) mod_md: values for External Account Binding (EAB) can
now also be configured to be read from a separate JSON
file. This allows to keep server configuration permissions
world readable without exposing secrets.


Added:
httpd/httpd/branches/2.4.x/changes-entries/md_2.4.8.txt
- copied unchanged from r1894610, httpd/httpd/trunk/changes-entries/md_2.4.8.txt
httpd/httpd/branches/2.4.x/changes-entries/md_2.4.9.txt
- copied unchanged from r1895285, httpd/httpd/trunk/changes-entries/md_2.4.9.txt
httpd/httpd/branches/2.4.x/changes-entries/pr65620.txt
- copied unchanged from r1893969, httpd/httpd/trunk/changes-entries/pr65620.txt
Modified:
httpd/httpd/branches/2.4.x/ (props changed)
httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml
httpd/httpd/branches/2.4.x/modules/md/md.h
httpd/httpd/branches/2.4.x/modules/md/md_acme.c
httpd/httpd/branches/2.4.x/modules/md/md_acme.h
httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c
httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.h
httpd/httpd/branches/2.4.x/modules/md/md_acme_authz.c
httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c
httpd/httpd/branches/2.4.x/modules/md/md_acme_order.c
httpd/httpd/branches/2.4.x/modules/md/md_acme_order.h
httpd/httpd/branches/2.4.x/modules/md/md_acmev2_drive.c
httpd/httpd/branches/2.4.x/modules/md/md_core.c
httpd/httpd/branches/2.4.x/modules/md/md_crypt.c
httpd/httpd/branches/2.4.x/modules/md/md_crypt.h
httpd/httpd/branches/2.4.x/modules/md/md_jws.c
httpd/httpd/branches/2.4.x/modules/md/md_jws.h
httpd/httpd/branches/2.4.x/modules/md/md_reg.c
httpd/httpd/branches/2.4.x/modules/md/md_status.c
httpd/httpd/branches/2.4.x/modules/md/md_store_fs.c
httpd/httpd/branches/2.4.x/modules/md/md_version.h
httpd/httpd/branches/2.4.x/modules/md/mod_md.c
httpd/httpd/branches/2.4.x/modules/md/mod_md_config.c
httpd/httpd/branches/2.4.x/modules/md/mod_md_config.h
httpd/httpd/branches/2.4.x/modules/md/mod_md_status.c

Propchange: httpd/httpd/branches/2.4.x/
------------------------------------------------------------------------------
Merged /httpd/httpd/trunk:r1893969,1894610,1894718,1895285,1895287

Modified: httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml (original)
+++ httpd/httpd/branches/2.4.x/docs/manual/mod/mod_md.xml Wed Nov 24 11:07:53 2021
@@ -1292,4 +1292,47 @@ MDMessageCmd /etc/apache/md-message
</usage>
</directivesynopsis>

+ <directivesynopsis>
+ <name>MDExternalAccountBinding</name>
+ <description></description>
+ <syntax>MDExternalAccountBinding <var>key-id</var> <var>hmac-64</var> | none | <var>file</var></syntax>
+ <default>MDExternalAccountBinding none</default>
+ <contextlist>
+ <context>server config</context>
+ </contextlist>
+ <usage>
+ <p>
+ Configure values for ACME "External Account Binding", a feature
+ of the ACME standard that allows clients to bind registrations
+ to an existing customer account on ACME servers.
+ </p>
+ <p>
+ Let's Encrypt does not require those, but other ACME CAs do.
+ Check with your ACME CA if you need those and how to obtain the
+ values. They are two strings, a key identifier and a base64 encoded
+ 'hmac' value.
+ </p>
+ <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,
+ e.g. root only.
+ </p>
+ <p>
+ The value can also be taken from a JSON file, to keep more open
+ permissions on the server configuration and restrict the ones on that
+ file. The JSON itself is:
+ </p>
+ <example><title>EAB JSON Example file</title>
+ <highlight language="config">
+{"kid": "kid-1", "hmac": "zWND..."}
+ </highlight>
+ </example>
+ <p>
+ If you change EAB values, the new ones will be used when the next
+ certificate renewal is due.
+ </p>
+ </usage>
+ </directivesynopsis>
+
</modulesynopsis>

Modified: httpd/httpd/branches/2.4.x/modules/md/md.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md.h?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md.h Wed Nov 24 11:07:53 2021
@@ -90,12 +90,15 @@ struct md_t {
const char *ca_url; /* url of CA certificate service */
const char *ca_proto; /* protocol used vs CA (e.g. ACME) */
const char *ca_account; /* account used at CA */
- const char *ca_agreement; /* accepted agreement uri between CA and user */
+ const char *ca_agreement; /* accepted agreement uri between CA and user */
struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */
struct apr_array_header_t *cert_files; /* != NULL iff pubcerts explicitly configured */
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 accont binding */
+
md_state_t state; /* state of this MD */
+ const char *state_descr; /* description of state of NULL */

struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
int stapling; /* if OCSP stapling is enabled */
@@ -133,6 +136,8 @@ struct md_t {
#define MD_KEY_DIR "dir"
#define MD_KEY_DOMAIN "domain"
#define MD_KEY_DOMAINS "domains"
+#define MD_KEY_EAB "eab"
+#define MD_KEY_EAB_REQUIRED "externalAccountRequired"
#define MD_KEY_ENTRIES "entries"
#define MD_KEY_ERRORED "errored"
#define MD_KEY_ERROR "error"
@@ -142,11 +147,13 @@ struct md_t {
#define MD_KEY_FINISHED "finished"
#define MD_KEY_FROM "from"
#define MD_KEY_GOOD "good"
+#define MD_KEY_HMAC "hmac"
#define MD_KEY_HTTP "http"
#define MD_KEY_HTTPS "https"
#define MD_KEY_ID "id"
#define MD_KEY_IDENTIFIER "identifier"
#define MD_KEY_KEY "key"
+#define MD_KEY_KID "kid"
#define MD_KEY_KEYAUTHZ "keyAuthorization"
#define MD_KEY_LAST "last"
#define MD_KEY_LAST_RUN "last-run"
@@ -183,10 +190,12 @@ struct md_t {
#define MD_KEY_SHA256_FINGERPRINT "sha256-fingerprint"
#define MD_KEY_STAPLING "stapling"
#define MD_KEY_STATE "state"
+#define MD_KEY_STATE_DESCR "state-descr"
#define MD_KEY_STATUS "status"
#define MD_KEY_STORE "store"
#define MD_KEY_SUBPROBLEMS "subproblems"
#define MD_KEY_TEMPORARY "temporary"
+#define MD_KEY_TOS "termsOfService"
#define MD_KEY_TOKEN "token"
#define MD_KEY_TOTAL "total"
#define MD_KEY_TRANSITIVE "transitive"
@@ -280,20 +289,21 @@ md_t *md_copy(apr_pool_t *p, const md_t
*
* This reads and writes the following information: name, domains, ca_url, ca_proto and state.
*/
-struct md_json_t *md_to_json (const md_t *md, apr_pool_t *p);
+struct md_json_t *md_to_json(const md_t *md, apr_pool_t *p);
md_t *md_from_json(struct md_json_t *json, apr_pool_t *p);

+/**
+ * Same as md_to_json(), but with sensitive fields stripped.
+ */
+struct md_json_t *md_to_public_json(const md_t *md, apr_pool_t *p);
+
int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t* alt_names);

/* how many certificates this domain has/will eventually have. */
int md_cert_count(const md_t *md);

-#define LE_ACMEv1_PROD "https://acme-v01.api.letsencrypt.org/directory"
-#define LE_ACMEv1_STAGING "https://acme-staging.api.letsencrypt.org/directory"
-
-#define LE_ACMEv2_PROD "https://acme-v02.api.letsencrypt.org/directory"
-#define LE_ACMEv2_STAGING "https://acme-staging-v02.api.letsencrypt.org/directory"
-
+const char *md_get_ca_name_from_url(apr_pool_t *p, const char *url);
+apr_status_t md_get_ca_url_from_name(const char **purl, apr_pool_t *p, const char *name);

/**************************************************************************************************/
/* notifications */

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme.c Wed Nov 24 11:07:53 2021
@@ -52,6 +52,7 @@ static acme_problem_status_t Problems[]
{ "acme:error:badCSR", APR_EINVAL, 1 },
{ "acme:error:badNonce", APR_EAGAIN, 0 },
{ "acme:error:badSignatureAlgorithm", APR_EINVAL, 1 },
+ { "acme:error:externalAccountRequired", APR_EINVAL, 1 },
{ "acme:error:invalidContact", APR_BADARG, 1 },
{ "acme:error:unsupportedContact", APR_EGENERAL, 1 },
{ "acme:error:malformed", APR_EINVAL, 1 },
@@ -147,11 +148,7 @@ static md_acme_req_t *md_acme_req_create
req->p = pool;
req->method = method;
req->url = url;
- req->prot_hdrs = apr_table_make(pool, 5);
- if (!req->prot_hdrs) {
- apr_pool_destroy(pool);
- return NULL;
- }
+ req->prot_fields = md_json_create(pool);
req->max_retries = acme->max_retries;
req->result = md_result_make(req->p, APR_SUCCESS);
return req;
@@ -207,6 +204,7 @@ static apr_status_t inspect_problem(md_a
switch (res->status) {
case 400:
return APR_EINVAL;
+ case 401: /* sectigo returns this instead of 403 */
case 403:
return APR_EACCES;
case 404:
@@ -246,7 +244,7 @@ static apr_status_t acmev2_req_init(md_a
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p,
"acme payload(len=%" APR_SIZE_T_FMT "): %s", payload.len, payload.data);
return md_jws_sign(&req->req_json, req->p, &payload,
- req->prot_hdrs, req->acme->acct_key, req->acme->acct->url);
+ req->prot_fields, req->acme->acct_key, req->acme->acct->url);
}

apr_status_t md_acme_req_body_init(md_acme_req_t *req, md_json_t *payload)
@@ -377,9 +375,9 @@ static apr_status_t md_acme_req_send(md_
"error retrieving new nonce from ACME server");
goto leave;
}
-
- apr_table_set(req->prot_hdrs, "nonce", acme->nonce);
- apr_table_set(req->prot_hdrs, "url", req->url);
+
+ md_json_sets(acme->nonce, req->prot_fields, "nonce", NULL);
+ md_json_sets(req->url, req->prot_fields, "url", NULL);
acme->nonce = NULL;
}

@@ -389,14 +387,13 @@ static apr_status_t md_acme_req_send(md_
if (req->req_json) {
body = apr_pcalloc(req->p, sizeof(*body));
body->data = md_json_writep(req->req_json, req->p, MD_JSON_FMT_INDENT);
- if (!body) {
- rv = APR_EINVAL; goto leave;
- }
body->len = strlen(body->data);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->p,
+ "sending JSON body: %s", body->data);
}

- if (body && md_log_is_level(req->p, MD_LOG_TRACE2)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, req->p,
+ if (body && md_log_is_level(req->p, MD_LOG_TRACE4)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->p,
"req: %s %s, body:\n%s", req->method, req->url, body->data);
}
else {
@@ -549,10 +546,35 @@ apr_status_t md_acme_use_acct(md_acme_t
md_acme_acct_t *acct;
md_pkey_t *pkey;
apr_status_t rv;
-
- if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
+
+ if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
+ store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
+ if (md_acme_acct_matches_url(acct, acme->url)) {
+ acme->acct_id = apr_pstrdup(p, acct_id);
+ acme->acct = acct;
+ acme->acct_key = pkey;
+ rv = md_acme_acct_validate(acme, store, p);
+ }
+ else {
+ /* account is from another server or, more likely, from another
+ * protocol endpoint on the same server */
+ rv = APR_ENOENT;
+ }
+ }
+ return rv;
+}
+
+apr_status_t md_acme_use_acct_for_md(md_acme_t *acme, struct md_store_t *store,
+ apr_pool_t *p, const char *acct_id,
+ const md_t *md)
+{
+ md_acme_acct_t *acct;
+ md_pkey_t *pkey;
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
- if (acct->ca_url && !strcmp(acct->ca_url, acme->url)) {
+ if (md_acme_acct_matches_md(acct, md)) {
acme->acct_id = apr_pstrdup(p, acct_id);
acme->acct = acct;
acme->acct_key = pkey;
@@ -701,7 +723,8 @@ static apr_status_t update_directory(con
&& acme->api.v2.new_nonce) {
acme->version = MD_ACME_VERSION_2;
}
- acme->ca_agreement = md_json_dups(acme->p, json, "meta", "termsOfService", NULL);
+ acme->ca_agreement = md_json_dups(acme->p, json, "meta", MD_KEY_TOS, NULL);
+ acme->eab_required = md_json_getb(json, "meta", MD_KEY_EAB_REQUIRED, NULL);
acme->new_nonce_fn = acmev2_new_nonce;
acme->req_init_fn = acmev2_req_init;
acme->post_new_account_fn = acmev2_POST_new_account;

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme.h?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme.h Wed Nov 24 11:07:53 2021
@@ -122,6 +122,7 @@ struct md_acme_t {
} api;
const char *ca_agreement;
const char *acct_name;
+ int eab_required;

md_acme_new_nonce_fn *new_nonce_fn;
md_acme_req_init_fn *req_init_fn;
@@ -185,12 +186,29 @@ const char *md_acme_acct_url_get(md_acme

/**
* Specify the account to use by name in local store. On success, the account
- * the "current" one used by the acme instance.
+ * is the "current" one used by the acme instance.
+ * @param acme the acme instance to set the account for
+ * @param store the store to load accounts from
+ * @param p pool for allocations
+ * @param acct_id name of the account to load
*/
apr_status_t md_acme_use_acct(md_acme_t *acme, struct md_store_t *store,
apr_pool_t *p, const char *acct_id);

/**
+ * Specify the account to use for a specific MD by name in local store.
+ * On success, the account is the "current" one used by the acme instance.
+ * @param acme the acme instance to set the account for
+ * @param store the store to load accounts from
+ * @param p pool for allocations
+ * @param acct_id name of the account to load
+ * @param md the MD the account shall be used for
+ */
+apr_status_t md_acme_use_acct_for_md(md_acme_t *acme, struct md_store_t *store,
+ apr_pool_t *p, const char *acct_id,
+ const md_t *md);
+
+/**
* Get the local name of the account currently used by the acme instance.
* Will be NULL if no account has been setup successfully.
*/
@@ -232,7 +250,7 @@ struct md_acme_req_t {

const char *url; /* url to POST the request to */
const char *method; /* HTTP method to use */
- apr_table_t *prot_hdrs; /* JWS headers needing protection (nonce) */
+ struct md_json_t *prot_fields; /* JWS protected fields */
struct md_json_t *req_json; /* JSON to be POSTed in request body */

apr_table_t *resp_hdrs; /* HTTP response headers */

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.c Wed Nov 24 11:07:53 2021
@@ -30,6 +30,7 @@
#include "md_json.h"
#include "md_jws.h"
#include "md_log.h"
+#include "md_result.h"
#include "md_store.h"
#include "md_util.h"
#include "md_version.h"
@@ -43,7 +44,6 @@ static apr_status_t acct_make(md_acme_ac
md_acme_acct_t *acct;

acct = apr_pcalloc(p, sizeof(*acct));
-
acct->ca_url = ca_url;
if (!contacts || apr_is_empty_array(contacts)) {
acct->contacts = apr_array_make(p, 5, sizeof(const char *));
@@ -114,7 +114,9 @@ md_json_t *md_acme_acct_to_json(md_acme_
if (acct->registration) md_json_setj(acct->registration, jacct, MD_KEY_REGISTRATION, NULL);
if (acct->agreement) md_json_sets(acct->agreement, jacct, MD_KEY_AGREEMENT, NULL);
if (acct->orders) md_json_sets(acct->orders, jacct, MD_KEY_ORDERS, NULL);
-
+ if (acct->eab_kid) md_json_sets(acct->eab_kid, jacct, MD_KEY_EAB, MD_KEY_KID, NULL);
+ if (acct->eab_hmac) md_json_sets(acct->eab_hmac, jacct, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+
return jacct;
}

@@ -129,22 +131,17 @@ apr_status_t md_acme_acct_from_json(md_a
if (md_json_has_key(json, MD_KEY_STATUS, NULL)) {
status = acct_st_from_str(md_json_gets(json, MD_KEY_STATUS, NULL));
}
- else {
- /* old accounts only had disabled boolean field */
- status = md_json_getb(json, MD_KEY_DISABLED, NULL)?
- MD_ACME_ACCT_ST_DEACTIVATED : MD_ACME_ACCT_ST_VALID;
- }
-
+
url = md_json_gets(json, MD_KEY_URL, NULL);
if (!url) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no url");
- goto out;
+ goto leave;
}

ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
if (!ca_url) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no CA url: %s", url);
- goto out;
+ goto leave;
}

contacts = apr_array_make(p, 5, sizeof(const char *));
@@ -155,14 +152,23 @@ apr_status_t md_acme_acct_from_json(md_a
md_json_getsa(contacts, json, MD_KEY_REGISTRATION, MD_KEY_CONTACT, NULL);
}
rv = acct_make(&acct, p, ca_url, contacts);
- if (APR_SUCCESS == rv) {
- acct->status = status;
- acct->url = url;
+ if (APR_SUCCESS != rv) goto leave;
+
+ acct->status = status;
+ acct->url = url;
+ acct->agreement = md_json_gets(json, MD_KEY_AGREEMENT, NULL);
+ if (!acct->agreement) {
+ /* backward compatible check */
acct->agreement = md_json_gets(json, "terms-of-service", NULL);
- acct->orders = md_json_gets(json, MD_KEY_ORDERS, NULL);
+ }
+ acct->orders = md_json_gets(json, MD_KEY_ORDERS, NULL);
+ if (md_json_has_key(json, MD_KEY_EAB, MD_KEY_KID, NULL)
+ && md_json_has_key(json, MD_KEY_EAB, MD_KEY_HMAC, NULL)) {
+ acct->eab_kid = md_json_gets(json, MD_KEY_EAB, MD_KEY_KID, NULL);
+ acct->eab_hmac = md_json_gets(json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
}

-out:
+leave:
*pacct = (APR_SUCCESS == rv)? acct : NULL;
return rv;
}
@@ -228,10 +234,36 @@ out:
/**************************************************************************************************/
/* Lookup */

+int md_acme_acct_matches_url(md_acme_acct_t *acct, const char *url)
+{
+ /* The ACME url must match exactly */
+ if (!url || !acct->ca_url || strcmp(acct->ca_url, url)) return 0;
+ return 1;
+}
+
+int md_acme_acct_matches_md(md_acme_acct_t *acct, const md_t *md)
+{
+ if (!md_acme_acct_matches_url(acct, md->ca_url)) return 0;
+ /* if eab values are not mentioned, we match an account regardless
+ * if it was registered with eab or not */
+ if (!md->ca_eab_kid || !md->ca_eab_hmac) {
+ /* No eab only acceptable when no eab is asked for.
+ * This prevents someone that has no external account binding
+ * to re-use an account from another MDomain that was created
+ * with a binding. */
+ return !acct->eab_kid || !acct->eab_hmac;
+ }
+ /* But of eab is asked for, we need an acct that matches exactly.
+ * When someone configures a new EAB and we need
+ * to created a new account for it. */
+ if (!acct->eab_kid || !acct->eab_hmac) return 0;
+ return !strcmp(acct->eab_kid, md->ca_eab_kid)
+ && !strcmp(acct->eab_hmac, md->ca_eab_hmac);
+}
+
typedef struct {
apr_pool_t *p;
- md_acme_t *acme;
- int url_match;
+ const md_t *md;
const char *id;
} find_ctx;

@@ -239,50 +271,46 @@ static int find_acct(void *baton, const
md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
{
find_ctx *ctx = baton;
- int disabled;
- const char *ca_url, *status;
-
+ md_acme_acct_t *acct;
+ apr_status_t rv;
+
(void)aspect;
(void)ptemp;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p, "account candidate %s/%s", name, aspect);
if (MD_SV_JSON == vtype) {
- md_json_t *json = value;
-
- status = md_json_gets(json, MD_KEY_STATUS, NULL);
- disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
- ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
-
- if ((!status || !strcmp("valid", status)) && !disabled
- && (!ctx->url_match || (ca_url && !strcmp(ctx->acme->url, ca_url)))) {
+ rv = md_acme_acct_from_json(&acct, (md_json_t*)value, ptemp);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ if (MD_ACME_ACCT_ST_VALID == acct->status
+ && (!ctx->md || md_acme_acct_matches_md(acct, ctx->md))) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p,
- "found account %s for %s: %s, status=%s, disabled=%d, ca-url=%s",
- name, ctx->acme->url, aspect, status, disabled, ca_url);
+ "found account %s for %s: %s, status=%d",
+ acct->id, ctx->md->ca_url, aspect, acct->status);
ctx->id = apr_pstrdup(ctx->p, name);
return 0;
}
}
+cleanup:
return 1;
}

static apr_status_t acct_find(const char **pid, md_acme_acct_t **pacct, md_pkey_t **ppkey,
md_store_t *store, md_store_group_t group,
- const char *name_pattern, int url_match,
- md_acme_t *acme, apr_pool_t *p)
+ const char *name_pattern,
+ const md_t *md, apr_pool_t *p)
{
apr_status_t rv;
find_ctx ctx;
-
+
+ memset(&ctx, 0, sizeof(ctx));
ctx.p = p;
- ctx.acme = acme;
- ctx.id = NULL;
- ctx.url_match = url_match;
- *pid = NULL;
-
+ ctx.md = md;
+
rv = md_store_iter(find_acct, &ctx, store, p, group, name_pattern, MD_FN_ACCOUNT, MD_SV_JSON);
if (ctx.id) {
*pid = ctx.id;
rv = md_acme_acct_load(pacct, ppkey, store, group, ctx.id, p);
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "loading account %s", ctx.id);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_find: got account %s", ctx.id);
}
else {
*pacct = NULL;
@@ -293,19 +321,26 @@ static apr_status_t acct_find(const char
}

static apr_status_t acct_find_and_verify(md_store_t *store, md_store_group_t group,
- const char *name_pattern, md_acme_t *acme, apr_pool_t *p)
+ const char *name_pattern,
+ md_acme_t *acme, const md_t *md,
+ apr_pool_t *p)
{
md_acme_acct_t *acct;
md_pkey_t *pkey;
const char *id;
apr_status_t rv;

- if (APR_SUCCESS == (rv = acct_find(&id, &acct, &pkey, store, group, name_pattern, 1, acme, p))) {
+ rv = acct_find(&id, &acct, &pkey, store, group, name_pattern, md, p);
+ if (APR_SUCCESS == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, "acct_find_and_verify: found %s",
+ id);
acme->acct_id = (MD_SG_STAGING == group)? NULL : id;
acme->acct = acct;
acme->acct_key = pkey;
- rv = md_acme_acct_validate(acme, NULL, p);
-
+ rv = md_acme_acct_validate(acme, (MD_SG_STAGING == group)? NULL : store, p);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p, "acct_find_and_verify: verified %s",
+ id);
+
if (APR_SUCCESS != rv) {
acme->acct_id = NULL;
acme->acct = NULL;
@@ -320,13 +355,13 @@ static apr_status_t acct_find_and_verify
return rv;
}

-apr_status_t md_acme_find_acct(md_acme_t *acme, md_store_t *store)
+apr_status_t md_acme_find_acct_for_md(md_acme_t *acme, md_store_t *store, const md_t *md)
{
apr_status_t rv;

while (APR_EAGAIN == (rv = acct_find_and_verify(store, MD_SG_ACCOUNTS,
mk_acct_pattern(acme->p, acme),
- acme, acme->p))) {
+ acme, md, acme->p))) {
/* nop */
}

@@ -335,61 +370,31 @@ apr_status_t md_acme_find_acct(md_acme_t
* can already be found in MD_SG_STAGING? */
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p,
"no account found, looking in STAGING");
- while (APR_EAGAIN == (rv = acct_find_and_verify(store, MD_SG_STAGING, "*",
- acme, acme->p))) {
- /* nop */
+ rv = acct_find_and_verify(store, MD_SG_STAGING, "*", acme, md, acme->p);
+ if (APR_EAGAIN == rv) {
+ rv = APR_ENOENT;
}
}
return rv;
}

-typedef struct {
- apr_pool_t *p;
- const char *url;
- const char *id;
-} load_ctx;
-
-static int id_by_url(void *baton, const char *name, const char *aspect,
- md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
-{
- load_ctx *ctx = baton;
- int disabled;
- const char *acct_url, *status;
-
- (void)aspect;
- (void)ptemp;
- if (MD_SV_JSON == vtype) {
- md_json_t *json = value;
-
- status = md_json_gets(json, MD_KEY_STATUS, NULL);
- disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
- acct_url = md_json_gets(json, MD_KEY_URL, NULL);
-
- if ((!status || !strcmp("valid", status)) && !disabled
- && acct_url && !strcmp(ctx->url, acct_url)) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p,
- "found account %s for url %s: %s, status=%s, disabled=%d",
- name, ctx->url, aspect, status, disabled);
- ctx->id = apr_pstrdup(ctx->p, name);
- return 0;
- }
- }
- return 1;
-}
-
-apr_status_t md_acme_acct_id_for_url(const char **pid, md_store_t *store,
- md_store_group_t group, const char *url, apr_pool_t *p)
+apr_status_t md_acme_acct_id_for_md(const char **pid, md_store_t *store,
+ md_store_group_t group, const md_t *md,
+ apr_pool_t *p)
{
apr_status_t rv;
- load_ctx ctx;
-
+ find_ctx ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
ctx.p = p;
- ctx.url = url;
- ctx.id = NULL;
-
- rv = md_store_iter(id_by_url, &ctx, store, p, group, "*", MD_FN_ACCOUNT, MD_SV_JSON);
- *pid = (APR_SUCCESS == rv)? ctx.id : NULL;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_id_by_url %s -> %s", url, *pid);
+ ctx.md = md;
+
+ rv = md_store_iter(find_acct, &ctx, store, p, group, "*", MD_FN_ACCOUNT, MD_SV_JSON);
+ if (ctx.id) {
+ *pid = ctx.id;
+ rv = APR_SUCCESS;
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_id_for_md %s -> %s", md->name, *pid);
return rv;
}

@@ -399,6 +404,8 @@ typedef struct {
md_acme_t *acme;
apr_pool_t *p;
const char *agreement;
+ const char *eab_kid;
+ const char *eab_hmac;
} acct_ctx_t;

/**************************************************************************************************/
@@ -416,7 +423,12 @@ static apr_status_t acct_upd(md_acme_t *
acct_ctx_t *ctx = baton;
apr_status_t rv = APR_SUCCESS;
md_acme_acct_t *acct = acme->acct;
-
+
+ if (md_log_is_level(p, MD_LOG_TRACE2)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, acme->p, "acct update response: %s",
+ md_json_writep(body, p, MD_JSON_FMT_COMPACT));
+ }
+
if (!acct->url) {
const char *location = apr_table_get(hdrs, "location");
if (!location) {
@@ -437,6 +449,10 @@ static apr_status_t acct_upd(md_acme_t *
if (md_json_has_key(body, MD_KEY_ORDERS, NULL)) {
acct->orders = md_json_dups(acme->p, body, MD_KEY_ORDERS, NULL);
}
+ if (ctx->eab_kid && ctx->eab_hmac) {
+ acct->eab_kid = ctx->eab_kid;
+ acct->eab_hmac = ctx->eab_hmac;
+ }
acct->registration = md_json_clone(ctx->p, body);

md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "updated acct %s", acct->url);
@@ -451,6 +467,7 @@ apr_status_t md_acme_acct_update(md_acme
if (!acme->acct) {
return APR_EINVAL;
}
+ memset(&ctx, 0, sizeof(ctx));
ctx.acme = acme;
ctx.p = acme->p;
return md_acme_POST(acme, acme->acct->url, on_init_acct_upd, acct_upd, NULL, NULL, &ctx);
@@ -461,7 +478,16 @@ apr_status_t md_acme_acct_validate(md_ac
apr_status_t rv;

if (APR_SUCCESS != (rv = md_acme_acct_update(acme))) {
- if (acme->acct && (APR_ENOENT == rv || APR_EACCES == rv)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, acme->p,
+ "acct update failed for %s", acme->acct->url);
+ if (APR_EINVAL == rv && (acme->acct->agreement || !acme->ca_agreement)) {
+ /* Sadly, some proprietary ACME servers choke on empty POSTs
+ * on accounts. Try a faked ToS agreement. */
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, acme->p,
+ "trying acct update via ToS agreement");
+ rv = md_acme_agree(acme, p, "accepted");
+ }
+ if (acme->acct && (APR_ENOENT == rv || APR_EACCES == rv || APR_EINVAL == rv)) {
if (MD_ACME_ACCT_ST_VALID == acme->acct->status) {
acme->acct->status = MD_ACME_ACCT_ST_UNKNOWN;
if (store) {
@@ -479,21 +505,73 @@ apr_status_t md_acme_acct_validate(md_ac
/**************************************************************************************************/
/* Register a new account */

+static apr_status_t get_eab(md_json_t **peab, md_acme_req_t *req, const char *kid,
+ const char *hmac64, md_pkey_t *account_key,
+ const char *url)
+{
+ md_json_t *eab, *prot_fields, *jwk;
+ md_data_t payload, hmac_key;
+ apr_status_t rv;
+
+ prot_fields = md_json_create(req->p);
+ md_json_sets(url, prot_fields, "url", NULL);
+ md_json_sets(kid, prot_fields, "kid", NULL);
+
+ rv = md_jws_get_jwk(&jwk, req->p, account_key);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ md_data_null(&payload);
+ payload.data = md_json_writep(jwk, req->p, MD_JSON_FMT_COMPACT);
+ if (!payload.data) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ payload.len = strlen(payload.data);
+
+ md_util_base64url_decode(&hmac_key, hmac64, req->p);
+ if (!hmac_key.len) {
+ rv = APR_EINVAL;
+ md_result_problem_set(req->result, rv, "apache:eab-hmac-invalid",
+ "external account binding HMAC value is not valid base64", NULL);
+ goto cleanup;
+ }
+
+ rv = md_jws_hmac(&eab, req->p, &payload, prot_fields, &hmac_key);
+ if (APR_SUCCESS != rv) {
+ md_result_problem_set(req->result, rv, "apache:eab-hmac-fail",
+ "external account binding MAC could not be computed", NULL);
+ }
+
+cleanup:
+ *peab = (APR_SUCCESS == rv)? eab : NULL;
+ return rv;
+}
+
static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
{
acct_ctx_t *ctx = baton;
- md_json_t *jpayload;
+ md_json_t *jpayload, *jeab;
+ apr_status_t rv;

jpayload = md_json_create(req->p);
md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
if (ctx->agreement) {
md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
}
- return md_acme_req_body_init(req, jpayload);
+ if (ctx->eab_kid && ctx->eab_hmac) {
+ rv = get_eab(&jeab, req, ctx->eab_kid, ctx->eab_hmac,
+ req->acme->acct_key, req->url);
+ if (APR_SUCCESS != rv) goto cleanup;
+ md_json_setj(jeab, jpayload, "externalAccountBinding", NULL);
+ }
+ rv = md_acme_req_body_init(req, jpayload);
+
+cleanup:
+ return rv;
}

-apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store, apr_pool_t *p,
- apr_array_header_t *contacts, const char *agreement)
+apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store,
+ const md_t *md, apr_pool_t *p)
{
apr_status_t rv;
md_pkey_t *pkey;
@@ -503,15 +581,17 @@ apr_status_t md_acme_acct_register(md_ac
acct_ctx_t ctx;

md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account");
-
+
+ memset(&ctx, 0, sizeof(ctx));
ctx.acme = acme;
ctx.p = p;
/* The agreement URL is submitted when the ACME server announces Terms-of-Service
* in its directory meta data. The magic value "accepted" will always use the
* advertised URL. */
ctx.agreement = NULL;
- if (acme->ca_agreement && agreement) {
- ctx.agreement = !strcmp("accepted", agreement)? acme->ca_agreement : agreement;
+ if (acme->ca_agreement && md->ca_agreement) {
+ ctx.agreement = !strcmp("accepted", md->ca_agreement)?
+ acme->ca_agreement : md->ca_agreement;
}

if (ctx.agreement) {
@@ -521,9 +601,11 @@ apr_status_t md_acme_acct_register(md_ac
goto out;
}
}
+ ctx.eab_kid = md->ca_eab_kid;
+ ctx.eab_hmac = md->ca_eab_hmac;

- for (i = 0; i < contacts->nelts; ++i) {
- uri = APR_ARRAY_IDX(contacts, i, const char *);
+ for (i = 0; i < md->contacts->nelts; ++i) {
+ uri = APR_ARRAY_IDX(md->contacts, i, const char *);
if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, uri, &err))) {
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
"invalid contact uri (%s): %s", err, uri);
@@ -540,19 +622,19 @@ apr_status_t md_acme_acct_register(md_ac
*/
if (!acme->acct_key) {
find_ctx fctx;
-
+
+ memset(&fctx, 0, sizeof(fctx));
fctx.p = p;
- fctx.acme = acme;
- fctx.id = NULL;
- fctx.url_match = 0;
-
+ fctx.md = md;
+
md_store_iter(find_acct, &fctx, store, p, MD_SG_ACCOUNTS,
mk_acct_pattern(p, acme), MD_FN_ACCOUNT, MD_SV_JSON);
if (fctx.id) {
- rv = md_store_load(store, MD_SG_ACCOUNTS, fctx.id, MD_FN_ACCT_KEY, MD_SV_PKEY,
+ rv = md_store_load(store, MD_SG_ACCOUNTS, fctx.id, MD_FN_ACCT_KEY, MD_SV_PKEY,
(void**)&acme->acct_key, p);
if (APR_SUCCESS == rv) {
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "reusing key from account %s", fctx.id);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "reusing key from account %s", fctx.id);
}
else {
acme->acct_key = NULL;
@@ -570,7 +652,7 @@ apr_status_t md_acme_acct_register(md_ac
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "created new account key");
}

- if (APR_SUCCESS != (rv = acct_make(&acme->acct, p, acme->url, contacts))) goto out;
+ if (APR_SUCCESS != (rv = acct_make(&acme->acct, p, acme->url, md->contacts))) goto out;
rv = md_acme_POST_new_account(acme, on_init_acct_new, acct_upd, NULL, NULL, &ctx);
if (APR_SUCCESS == rv) {
md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p,
@@ -608,6 +690,7 @@ apr_status_t md_acme_acct_deactivate(md_
}
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "delete account %s from %s",
acct->url, acct->ca_url);
+ memset(&ctx, 0, sizeof(ctx));
ctx.acme = acme;
ctx.p = p;
return md_acme_POST(acme, acct->url, on_init_acct_del, acct_upd, NULL, NULL, &ctx);
@@ -637,6 +720,7 @@ apr_status_t md_acme_agree(md_acme_t *ac
acme->acct->agreement = acme->ca_agreement;
}

+ memset(&ctx, 0, sizeof(ctx));
ctx.acme = acme;
ctx.p = p;
return md_acme_POST(acme, acme->acct->url, on_init_agree_tos, acct_upd, NULL, NULL, &ctx);

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.h?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme_acct.h Wed Nov 24 11:07:53 2021
@@ -44,6 +44,8 @@ struct md_acme_acct_t {
const char *tos_required; /* terms of service asked for by CA */
const char *agreement; /* terms of service agreed to by user */
const char *orders; /* URL where certificate orders are found (ACMEv2) */
+ const char *eab_kid; /* external account binding keyid used or NULL */
+ const char *eab_hmac; /* external account binding hmac used or NULL */
struct md_json_t *registration; /* data from server registration */
};

@@ -104,21 +106,20 @@ const char *md_acme_get_agreement(md_acm
* Find an existing account in the local store. On APR_SUCCESS, the acme
* instance will have a current, validated account to use.
*/
-apr_status_t md_acme_find_acct(md_acme_t *acme, md_store_t *store);
+apr_status_t md_acme_find_acct_for_md(md_acme_t *acme, md_store_t *store, const md_t *md);

/**
- * Find the account id for a given account url.
+ * Find the account id for a given md.
*/
-apr_status_t md_acme_acct_id_for_url(const char **pid, md_store_t *store,
- md_store_group_t group, const char *url, apr_pool_t *p);
+apr_status_t md_acme_acct_id_for_md(const char **pid, md_store_t *store,
+ md_store_group_t group, const md_t *md, apr_pool_t *p);

/**
- * Create a new account at the ACME server. The
+ * Create a new account at the ACME server for an MD. The
* new account is the one used by the acme instance afterwards, on success.
*/
apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store,
- apr_pool_t *p, apr_array_header_t *contacts,
- const char *agreement);
+ const md_t *md, apr_pool_t *p);

apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme,
const char **pid, struct md_acme_acct_t *acct,
@@ -133,4 +134,15 @@ apr_status_t md_acme_acct_load(struct md
md_store_t *store, md_store_group_t group,
const char *name, apr_pool_t *p);

+/*
+ * Return != 0 iff the account can be used for the ACME url.
+ */
+int md_acme_acct_matches_url(md_acme_acct_t *acct, const char *url);
+
+/*
+ * Return != 0 iff the account can be used for the MD, including
+ * its CA url and EAB settings.
+ */
+int md_acme_acct_matches_md(md_acme_acct_t *acct, const md_t *md);
+
#endif /* md_acme_acct_h */

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme_authz.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme_authz.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme_authz.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme_authz.c Wed Nov 24 11:07:53 2021
@@ -393,7 +393,13 @@ static apr_status_t cha_tls_alpn_01_setu
/* Raise event that challenge data has been set up before we tell the
ACME server. Clusters might want to distribute it. */
event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_TLSALPN01, authz->domain);
- md_result_holler(result, event, p);
+ rv = md_result_raise(result, event, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: event '%s' failed. aborting challenge setup",
+ authz->domain, event);
+ goto out;
+ }
/* challenge is setup or was changed from previous data, tell ACME server
* so it may (re)try verification */
authz_req_ctx_init(&ctx, acme, NULL, authz, p);
@@ -463,7 +469,13 @@ static apr_status_t cha_dns_01_setup(md_
/* Raise event that challenge data has been set up before we tell the
ACME server. Clusters might want to distribute it. */
event = apr_psprintf(p, "challenge-setup:%s:%s", MD_AUTHZ_TYPE_DNS01, authz->domain);
- md_result_holler(result, event, p);
+ rv = md_result_raise(result, event, p);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
+ "%s: event '%s' failed. aborting challenge setup",
+ authz->domain, event);
+ 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);

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme_drive.c Wed Nov 24 11:07:53 2021
@@ -46,14 +46,14 @@
/* account setup */

static apr_status_t use_staged_acct(md_acme_t *acme, struct md_store_t *store,
- const char *md_name, apr_pool_t *p)
+ const md_t *md, apr_pool_t *p)
{
md_acme_acct_t *acct;
md_pkey_t *pkey;
apr_status_t rv;

if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey, store,
- MD_SG_STAGING, md_name, acme->p))) {
+ MD_SG_STAGING, md->name, acme->p))) {
acme->acct_id = NULL;
acme->acct = acct;
acme->acct_key = pkey;
@@ -89,7 +89,7 @@ apr_status_t md_acme_drive_set_acct(md_p
md_acme_clear_acct(ad->acme);

/* Do we have a staged (modified) account? */
- if (APR_SUCCESS == (rv = use_staged_acct(ad->acme, d->store, md->name, d->p))) {
+ if (APR_SUCCESS == (rv = use_staged_acct(ad->acme, d->store, md, d->p))) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-using staged account");
}
else if (!APR_STATUS_IS_ENOENT(rv)) {
@@ -99,7 +99,7 @@ apr_status_t md_acme_drive_set_acct(md_p
/* Get an account for the ACME server for this MD */
if (!ad->acme->acct && md->ca_account) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-use account '%s'", md->ca_account);
- rv = md_acme_use_acct(ad->acme, d->store, d->p, md->ca_account);
+ rv = md_acme_use_acct_for_md(ad->acme, d->store, d->p, md->ca_account, md);
if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_EINVAL(rv)) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "rejected %s", md->ca_account);
md->ca_account = NULL;
@@ -114,7 +114,7 @@ apr_status_t md_acme_drive_set_acct(md_p
/* Find a local account for server, store at MD */
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: looking at existing accounts",
d->proto->protocol);
- if (APR_SUCCESS == (rv = md_acme_find_acct(ad->acme, d->store))) {
+ if (APR_SUCCESS == (rv = md_acme_find_acct_for_md(ad->acme, d->store, md))) {
md->ca_account = md_acme_acct_id_get(ad->acme);
update_md = 1;
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: using account %s (id=%s)",
@@ -153,7 +153,19 @@ apr_status_t md_acme_drive_set_acct(md_p
goto leave;
}

- rv = md_acme_acct_register(ad->acme, d->store, d->p, md->contacts, md->ca_agreement);
+ if (ad->acme->eab_required && (!md->ca_eab_kid || !strcmp("none", md->ca_eab_kid))) {
+ md_result_printf(result, APR_EINVAL,
+ "the CA requires 'External Account Binding' which is not "
+ "configured. This means you need to obtain a 'Key ID' and a "
+ "'HMAC' from the CA and configure that using the "
+ "MDExternalAccountBinding directive in your config. "
+ "The creation of a new ACME account will most likely fail, "
+ "but an attempt is made anyway.",
+ ad->acme->ca_agreement);
+ md_result_log(result, MD_LOG_INFO);
+ }
+
+ rv = md_acme_acct_register(ad->acme, d->store, md, d->p);
if (APR_SUCCESS != rv) {
if (APR_SUCCESS != ad->acme->last->status) {
md_result_dup(result, ad->acme->last);
@@ -169,11 +181,11 @@ apr_status_t md_acme_drive_set_acct(md_p

leave:
/* Persist MD changes in STAGING, so we pick them up on next run */
- if (APR_SUCCESS == rv&& update_md) {
+ if (APR_SUCCESS == rv && update_md) {
rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
}
/* Persist account changes in STAGING, so we pick them up on next run */
- if (APR_SUCCESS == rv&& update_acct) {
+ if (APR_SUCCESS == rv && update_acct) {
rv = save_acct_staged(ad->acme, d->store, md->name, d->p);
}
return rv;
@@ -894,6 +906,7 @@ static apr_status_t acme_preload(md_prot
md_credentials_t *creds;
apr_array_header_t *all_creds;
struct md_acme_acct_t *acct;
+ const char *id;
int i;

md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name);
@@ -953,7 +966,6 @@ static apr_status_t acme_preload(md_prot

if (acct) {
md_acme_t *acme;
- const char *id = md->ca_account;

/* We may have STAGED the same account several times. This happens when
* several MDs are renewed at once and need a new account. They will all store
@@ -961,8 +973,9 @@ static apr_status_t acme_preload(md_prot
* the same url, we save them all into a single one.
*/
md_result_activity_setn(result, "saving staged account");
- if (!id && acct->url) {
- rv = md_acme_acct_id_for_url(&id, d->store, MD_SG_ACCOUNTS, acct->url, d->p);
+ id = md->ca_account;
+ if (!id) {
+ rv = md_acme_acct_id_for_md(&id, d->store, MD_SG_ACCOUNTS, md, d->p);
if (APR_STATUS_IS_ENOENT(rv)) {
id = NULL;
}
@@ -983,6 +996,14 @@ static apr_status_t acme_preload(md_prot
}
md->ca_account = id;
}
+ else if (!md->ca_account) {
+ /* staging reused another account and did not create a new one. find
+ * the account, if it is already there */
+ rv = md_acme_acct_id_for_md(&id, d->store, MD_SG_ACCOUNTS, md, d->p);
+ if (APR_SUCCESS == rv) {
+ md->ca_account = id;
+ }
+ }

md_result_activity_setn(result, "saving staged md/privkey/pubcert");
if (APR_SUCCESS != (rv = md_save(d->store, d->p, load_group, md, 1))) {

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme_order.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme_order.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme_order.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme_order.c Wed Nov 24 11:07:53 2021
@@ -397,10 +397,15 @@ static apr_status_t await_valid(void *ba
ctx->result, ctx->p))) goto out;
switch (ctx->order->status) {
case MD_ACME_ORDER_ST_VALID:
+ md_result_set(ctx->result, APR_EINVAL, "ACME server order status is 'valid'.");
break;
case MD_ACME_ORDER_ST_PROCESSING:
rv = APR_EAGAIN;
break;
+ case MD_ACME_ORDER_ST_INVALID:
+ md_result_set(ctx->result, APR_EINVAL, "ACME server order status is 'invalid'.");
+ rv = APR_EINVAL;
+ break;
default:
rv = APR_EINVAL;
break;
@@ -515,12 +520,10 @@ static apr_status_t check_challenges(voi
goto leave;
case MD_ACME_AUTHZ_S_INVALID:
rv = APR_EINVAL;
- if (!authz->error_type) {
- md_result_printf(ctx->result, rv,
- "domain authorization for %s failed, CA considers "
- "answer to challenge invalid, no error given",
- authz->domain);
- }
+ md_result_printf(ctx->result, rv,
+ "domain authorization for %s failed, CA considers "
+ "answer to challenge invalid%s.",
+ authz->domain, authz->error_type? "" : ", no error given");
md_result_log(ctx->result, MD_LOG_ERR);
goto leave;
default:

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acme_order.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acme_order.h?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acme_order.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acme_order.h Wed Nov 24 11:07:53 2021
@@ -63,8 +63,7 @@ apr_status_t md_acme_order_purge(struct
md_store_group_t group, const char *md_name,
apr_table_t *env);

-
-apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme,
+apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme,
apr_array_header_t *challenge_types,
md_store_t *store, const md_t *md,
apr_table_t *env, struct md_result_t *result,

Modified: httpd/httpd/branches/2.4.x/modules/md/md_acmev2_drive.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_acmev2_drive.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_acmev2_drive.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_acmev2_drive.c Wed Nov 24 11:07:53 2021
@@ -51,7 +51,7 @@
* Either we have an order stored in the STAGING area, or we need to create a
* new one at the ACME server.
*/
-static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result)
+static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result, int *pis_new)
{
md_acme_driver_t *ad = d->baton;
apr_status_t rv;
@@ -65,6 +65,7 @@ static apr_status_t ad_setup_order(md_pr
* if known AUTHZ resource is not valid, remove, goto 4.1.1
* if no AUTHZ available, create a new one for the domain, store it
*/
+ if (pis_new) *pis_new = 0;
rv = md_acme_order_load(d->store, MD_SG_STAGING, md->name, &ad->order, d->p);
if (APR_SUCCESS == rv) {
md_result_activity_setn(result, "Loaded order from staging");
@@ -82,7 +83,8 @@ static apr_status_t ad_setup_order(md_pr
if (APR_SUCCESS != rv) {
md_result_set(result, rv, "saving order in staging");
}
-
+ if (pis_new) *pis_new = 1;
+
leave:
md_acme_report_result(ad->acme, rv, result);
return rv;
@@ -94,7 +96,8 @@ leave:
apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, md_result_t *result)
{
apr_status_t rv = APR_SUCCESS;
-
+ int is_new_order = 0;
+
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: (ACMEv2) need certificate", d->md->name);

/* Chose (or create) and ACME account to use */
@@ -115,27 +118,36 @@ apr_status_t md_acmev2_drive_renew(md_ac
* * COMPLETE: all done, return success
* * INVALID and otherwise: fail renewal, delete local order
*/
- if (APR_SUCCESS != (rv = ad_setup_order(d, result))) {
+ if (APR_SUCCESS != (rv = ad_setup_order(d, result, &is_new_order))) {
goto leave;
}

rv = md_acme_order_update(ad->order, ad->acme, result, d->p);
- if (APR_STATUS_IS_ENOENT(rv)) {
- /* order is no longer known at the ACME server */
+ if (APR_STATUS_IS_ENOENT(rv)
+ || APR_STATUS_IS_EACCES(rv)
+ || 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);
}
else if (APR_SUCCESS != rv) {
goto leave;
}
-
+
+retry:
if (!ad->order) {
- rv = ad_setup_order(d, result);
+ rv = ad_setup_order(d, result, &is_new_order);
if (APR_SUCCESS != rv) goto leave;
}

rv = md_acme_order_start_challenges(ad->order, ad->acme, ad->ca_challenges,
d->store, d->md, d->env, result, d->p);
+ 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);
+ goto retry;
+ }
if (APR_SUCCESS != rv) goto leave;

rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md,

Modified: httpd/httpd/branches/2.4.x/modules/md/md_core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_core.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_core.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_core.c Wed Nov 24 11:07:53 2021
@@ -19,6 +19,7 @@

#include <apr_lib.h>
#include <apr_strings.h>
+#include <apr_uri.h>
#include <apr_tables.h>
#include <apr_time.h>
#include <apr_date.h>
@@ -277,6 +278,8 @@ md_json_t *md_to_json(const md_t *md, ap
md_json_setj(md_pkeys_spec_to_json(md->pks, p), json, MD_KEY_PKEY, NULL);
}
md_json_setl(md->state, json, MD_KEY_STATE, NULL);
+ if (md->state_descr)
+ md_json_sets(md->state_descr, json, MD_KEY_STATE_DESCR, NULL);
md_json_setl(md->renew_mode, json, MD_KEY_RENEW_MODE, NULL);
if (md->renew_window)
md_json_sets(md_timeslice_format(md->renew_window, p), json, MD_KEY_RENEW_WINDOW, NULL);
@@ -302,6 +305,10 @@ md_json_t *md_to_json(const md_t *md, ap
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->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);
+ }
return json;
}
return NULL;
@@ -323,6 +330,7 @@ md_t *md_from_json(md_json_t *json, apr_
md->pks = md_pkeys_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->state_descr = md_json_dups(p, json, MD_KEY_STATE_DESCR, NULL);
if (MD_S_EXPIRED_DEPRECATED == md->state) md->state = MD_S_COMPLETE;
md->renew_mode = (int)md_json_getl(json, MD_KEY_RENEW_MODE, NULL);
md->domains = md_array_str_compact(p, md->domains, 0);
@@ -354,8 +362,84 @@ md_t *md_from_json(md_json_t *json, apr_
}
md->stapling = (int)md_json_getb(json, MD_KEY_STAPLING, 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);
+ }
return md;
}
return NULL;
}

+md_json_t *md_to_public_json(const md_t *md, apr_pool_t *p)
+{
+ md_json_t *json = md_to_json(md, p);
+ if (md_json_has_key(json, MD_KEY_EAB, MD_KEY_HMAC, NULL)) {
+ md_json_sets("***", json, MD_KEY_EAB, MD_KEY_HMAC, NULL);
+ }
+ return json;
+}
+
+typedef struct {
+ const char *name;
+ const char *url;
+} md_ca_t;
+
+#define LE_ACMEv2_PROD "https://acme-v02.api.letsencrypt.org/directory"
+#define LE_ACMEv2_STAGING "https://acme-staging-v02.api.letsencrypt.org/directory"
+#define BUYPASS_ACME "https://api.buypass.com/acme/directory"
+#define BUYPASS_ACME_TEST "https://api.test4.buypass.no/acme/directory"
+
+static md_ca_t KNOWN_CAs[] = {
+ { "LetsEncrypt", LE_ACMEv2_PROD },
+ { "LetsEncrypt-Test", LE_ACMEv2_STAGING },
+ { "Buypass", BUYPASS_ACME },
+ { "Buypass-Test", BUYPASS_ACME_TEST },
+};
+
+const char *md_get_ca_name_from_url(apr_pool_t *p, const char *url)
+{
+ apr_uri_t uri_parsed;
+ unsigned int i;
+
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ if (!apr_strnatcasecmp(KNOWN_CAs[i].url, url)) {
+ return KNOWN_CAs[i].name;
+ }
+ }
+ if (APR_SUCCESS == apr_uri_parse(p, url, &uri_parsed)) {
+ return uri_parsed.hostname;
+ }
+ return apr_pstrdup(p, url);
+}
+
+apr_status_t md_get_ca_url_from_name(const char **purl, apr_pool_t *p, const char *name)
+{
+ const char *err;
+ unsigned int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ *purl = NULL;
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ if (!apr_strnatcasecmp(KNOWN_CAs[i].name, name)) {
+ *purl = KNOWN_CAs[i].url;
+ goto leave;
+ }
+ }
+ *purl = name;
+ rv = md_util_abs_http_uri_check(p, name, &err);
+ if (APR_SUCCESS != rv) {
+ apr_array_header_t *names;
+
+ names = apr_array_make(p, 10, sizeof(const char*));
+ for (i = 0; i < sizeof(KNOWN_CAs)/sizeof(KNOWN_CAs[0]); ++i) {
+ APR_ARRAY_PUSH(names, const char *) = KNOWN_CAs[i].name;
+ }
+ *purl = apr_psprintf(p,
+ "The CA name '%s' is not known and it is not a URL either (%s). "
+ "Known CA names are: %s.",
+ name, err, apr_array_pstrcat(p, names, ' '));
+ }
+leave:
+ return rv;
+}

Modified: httpd/httpd/branches/2.4.x/modules/md/md_crypt.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_crypt.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_crypt.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_crypt.c Wed Nov 24 11:07:53 2021
@@ -27,6 +27,7 @@

#include <openssl/err.h>
#include <openssl/evp.h>
+#include <openssl/hmac.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
@@ -643,6 +644,7 @@ static apr_status_t pkey_to_buffer(md_da
const EVP_CIPHER *cipher = NULL;
pem_password_cb *cb = NULL;
void *cb_baton = NULL;
+ apr_status_t rv = APR_SUCCESS;
passwd_ctx ctx;
unsigned long err;
int i;
@@ -651,7 +653,8 @@ static apr_status_t pkey_to_buffer(md_da
return APR_ENOMEM;
}
if (pass_len > INT_MAX) {
- return APR_EINVAL;
+ rv = APR_EINVAL;
+ goto cleanup;
}
if (pass && pass_len > 0) {
ctx.pass_phrase = pass;
@@ -660,7 +663,8 @@ static apr_status_t pkey_to_buffer(md_da
cb_baton = &ctx;
cipher = EVP_aes_256_cbc();
if (!cipher) {
- return APR_ENOTIMPL;
+ rv = APR_ENOTIMPL;
+ goto cleanup;
}
}

@@ -670,11 +674,11 @@ static apr_status_t pkey_to_buffer(md_da
#else
if (!PEM_write_bio_PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) {
#endif
- BIO_free(bio);
err = ERR_get_error();
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "PEM_write key: %ld %s",
err, ERR_error_string(err, NULL));
- return APR_EINVAL;
+ rv = APR_EINVAL;
+ goto cleanup;
}

md_data_null(buf);
@@ -684,8 +688,10 @@ static apr_status_t pkey_to_buffer(md_da
i = BIO_read(bio, (char*)buf->data, i);
buf->len = (apr_size_t)i;
}
+
+cleanup:
BIO_free(bio);
- return APR_SUCCESS;
+ return rv;
}

apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
@@ -786,21 +792,25 @@ static apr_status_t gen_ec(md_pkey_t **p
#ifdef NID_secp384r1
if (NID_undef == curve_nid && !apr_strnatcasecmp("secp384r1", curve)) {
curve_nid = NID_secp384r1;
+ curve = EC_curve_nid2nist(curve_nid);
}
#endif
#ifdef NID_X9_62_prime256v1
if (NID_undef == curve_nid && !apr_strnatcasecmp("secp256r1", curve)) {
curve_nid = NID_X9_62_prime256v1;
+ curve = EC_curve_nid2nist(curve_nid);
}
#endif
#ifdef NID_X9_62_prime192v1
if (NID_undef == curve_nid && !apr_strnatcasecmp("secp192r1", curve)) {
curve_nid = NID_X9_62_prime192v1;
+ curve = EC_curve_nid2nist(curve_nid);
}
#endif
#if defined(NID_X25519) && !defined(LIBRESSL_VERSION_NUMBER)
if (NID_undef == curve_nid && !apr_strnatcasecmp("X25519", curve)) {
curve_nid = NID_X25519;
+ curve = EC_curve_nid2nist(curve_nid);
}
#endif
if (NID_undef == curve_nid) {
@@ -844,6 +854,7 @@ static apr_status_t gen_ec(md_pkey_t **p
#endif

default:
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
if (APR_SUCCESS != (rv = check_EC_curve(curve_nid, p))) goto leave;
if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL))
|| EVP_PKEY_paramgen_init(ctx) <= 0
@@ -855,6 +866,17 @@ static apr_status_t gen_ec(md_pkey_t **p
"error generate EC key for group: %s", curve);
rv = APR_EGENERAL; goto leave;
}
+#else
+ if (APR_SUCCESS != (rv = check_EC_curve(curve_nid, p))) goto leave;
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL))
+ || EVP_PKEY_keygen_init(ctx) <= 0
+ || EVP_PKEY_CTX_ctrl_str(ctx, "ec_paramgen_curve", curve) <= 0
+ || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
+ "error generate EC key for group: %s", curve);
+ rv = APR_EGENERAL; goto leave;
+ }
+#endif
rv = APR_SUCCESS;
break;
}
@@ -987,8 +1009,6 @@ static apr_status_t sha256_digest(md_dat
unsigned int dlen;

digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
- if (!digest) goto leave;
-
ctx = EVP_MD_CTX_create();
if (ctx) {
rv = APR_ENOTIMPL;
@@ -1002,7 +1022,6 @@ static apr_status_t sha256_digest(md_dat
}
}
}
-leave:
if (ctx) {
EVP_MD_CTX_destroy(ctx);
}
@@ -1038,6 +1057,31 @@ apr_status_t md_crypt_sha256_digest_hex(
return rv;
}

+apr_status_t md_crypt_hmac64(const char **pmac64, const md_data_t *hmac_key,
+ apr_pool_t *p, const char *d, size_t dlen)
+{
+ const char *mac64 = NULL;
+ unsigned char *s;
+ unsigned int digest_len = 0;
+ md_data_t *digest;
+ apr_status_t rv = APR_SUCCESS;
+
+ digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
+ s = HMAC(EVP_sha256(), (const unsigned char*)hmac_key->data, (int)hmac_key->len,
+ (const unsigned char*)d, (size_t)dlen,
+ (unsigned char*)digest->data, &digest_len);
+ if (!s) {
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ digest->len = digest_len;
+ mac64 = md_util_base64url_encode(digest, p);
+
+cleanup:
+ *pmac64 = (APR_SUCCESS == rv)? mac64 : NULL;
+ return rv;
+}
+
/**************************************************************************************************/
/* certificates */

@@ -1326,17 +1370,13 @@ apr_status_t md_cert_to_sha256_digest(md
{
md_data_t *digest;
unsigned int dlen;
- apr_status_t rv = APR_ENOMEM;

digest = md_data_pmake(EVP_MAX_MD_SIZE, p);
- if (!digest) goto leave;
-
X509_digest(cert->x509, EVP_sha256(), (unsigned char*)digest->data, &dlen);
digest->len = dlen;
- rv = APR_SUCCESS;
-leave:
- *pdigest = (APR_SUCCESS == rv)? digest : NULL;
- return rv;
+
+ *pdigest = digest;
+ return APR_SUCCESS;
}

apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p)
@@ -1458,17 +1498,30 @@ apr_status_t md_cert_chain_read_http(str
ct = apr_table_get(res->headers, "Content-Type");
if (!res->body || !ct) goto cleanup;
ct = md_util_parse_ct(res->req->pool, ct);
- if (!strcmp("application/pem-certificate-chain", ct)
+ if (!strcmp("application/pkix-cert", ct)) {
+ rv = md_cert_read_http(&cert, p, res);
+ if (APR_SUCCESS != rv) goto cleanup;
+ APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+ }
+ else if (!strcmp("application/pem-certificate-chain", ct)
|| !strncmp("text/plain", ct, sizeof("text/plain")-1)) {
/* Some servers seem to think 'text/plain' is sufficient, see #232 */
rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool);
if (APR_SUCCESS != rv) goto cleanup;
rv = md_cert_read_chain(chain, res->req->pool, data, data_len);
}
- else if (!strcmp("application/pkix-cert", ct)) {
- rv = md_cert_read_http(&cert, p, res);
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
+ "attempting to parse certificates from unrecognized content-type: %s", ct);
+ rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool);
if (APR_SUCCESS != rv) goto cleanup;
- APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+ rv = md_cert_read_chain(chain, res->req->pool, data, data_len);
+ if (APR_SUCCESS == rv && chain->nelts == 0) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
+ "certificiate chain response did not contain any certificates "
+ "(suspicious content-type: %s)", ct);
+ rv = APR_ENOENT;
+ }
}
cleanup:
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,

Modified: httpd/httpd/branches/2.4.x/modules/md/md_crypt.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_crypt.h?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_crypt.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_crypt.h Wed Nov 24 11:07:53 2021
@@ -114,6 +114,9 @@ apr_status_t md_crypt_sign64(const char

void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey);

+apr_status_t md_crypt_hmac64(const char **pmac64, const struct md_data_t *hmac_key,
+ apr_pool_t *p, const char *d, size_t dlen);
+
/**************************************************************************************************/
/* X509 certificates */


Modified: httpd/httpd/branches/2.4.x/modules/md/md_jws.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_jws.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_jws.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_jws.c Wed Nov 24 11:07:53 2021
@@ -25,67 +25,67 @@
#include "md_log.h"
#include "md_util.h"

-static int header_set(void *data, const char *key, const char *val)
+apr_status_t md_jws_get_jwk(md_json_t **pjwk, apr_pool_t *p, struct md_pkey_t *pkey)
{
- md_json_sets(val, (md_json_t *)data, key, NULL);
- return 1;
+ md_json_t *jwk;
+
+ if (!pkey) return APR_EINVAL;
+
+ jwk = md_json_create(p);
+ md_json_sets(md_pkey_get_rsa_e64(pkey, p), jwk, "e", NULL);
+ md_json_sets("RSA", jwk, "kty", NULL);
+ md_json_sets(md_pkey_get_rsa_n64(pkey, p), jwk, "n", NULL);
+ *pjwk = jwk;
+ return APR_SUCCESS;
}

apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
- md_data_t *payload, struct apr_table_t *protected,
+ md_data_t *payload, md_json_t *prot_fields,
struct md_pkey_t *pkey, const char *key_id)
{
- md_json_t *msg, *jprotected;
+ md_json_t *msg, *jprotected, *jwk;
const char *prot64, *pay64, *sign64, *sign, *prot;
- apr_status_t rv = APR_SUCCESS;
+ md_data_t data;
+ apr_status_t rv;

- *pmsg = NULL;
-
msg = md_json_create(p);
-
- jprotected = md_json_create(p);
+ jprotected = md_json_clone(p, prot_fields);
md_json_sets("RS256", jprotected, "alg", NULL);
if (key_id) {
md_json_sets(key_id, jprotected, "kid", NULL);
}
else {
- md_json_sets(md_pkey_get_rsa_e64(pkey, p), jprotected, "jwk", "e", NULL);
- md_json_sets("RSA", jprotected, "jwk", "kty", NULL);
- md_json_sets(md_pkey_get_rsa_n64(pkey, p), jprotected, "jwk", "n", NULL);
+ rv = md_jws_get_jwk(&jwk, p, pkey);
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "get jwk");
+ goto cleanup;
+ }
+ md_json_setj(jwk, jprotected, "jwk", NULL);
}
- apr_table_do(header_set, jprotected, protected, NULL);
- prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, p, "protected: %s",
- prot ? prot : "<failed to serialize!>");

+ prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
if (!prot) {
rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "serialize protected");
+ goto cleanup;
}
-
- if (rv == APR_SUCCESS) {
- md_data_t data;

- md_data_init(&data, prot, strlen(prot));
- prot64 = md_util_base64url_encode(&data, p);
- md_json_sets(prot64, msg, "protected", NULL);
- pay64 = md_util_base64url_encode(payload, p);
+ md_data_init(&data, prot, strlen(prot));
+ prot64 = md_util_base64url_encode(&data, p);
+ md_json_sets(prot64, msg, "protected", NULL);

- md_json_sets(pay64, msg, "payload", NULL);
- sign = apr_psprintf(p, "%s.%s", prot64, pay64);
+ pay64 = md_util_base64url_encode(payload, p);
+ md_json_sets(pay64, msg, "payload", NULL);
+ sign = apr_psprintf(p, "%s.%s", prot64, pay64);

- rv = md_crypt_sign64(&sign64, pkey, p, sign, strlen(sign));
+ rv = md_crypt_sign64(&sign64, pkey, p, sign, strlen(sign));
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "jwk signed message");
+ goto cleanup;
}
+ md_json_sets(sign64, msg, "signature", NULL);

- if (rv == APR_SUCCESS) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p,
- "jws pay64=%s\nprot64=%s\nsign64=%s", pay64, prot64, sign64);
-
- md_json_sets(sign64, msg, "signature", NULL);
- }
- else {
- md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "jwk signed message");
- }
-
+cleanup:
*pmsg = (APR_SUCCESS == rv)? msg : NULL;
return rv;
}
@@ -108,3 +108,41 @@ apr_status_t md_jws_pkey_thumb(const cha
rv = md_crypt_sha256_digest64(pthumb, p, &data);
return rv;
}
+
+apr_status_t md_jws_hmac(md_json_t **pmsg, apr_pool_t *p,
+ md_data_t *payload, md_json_t *prot_fields,
+ const md_data_t *hmac_key)
+{
+ md_json_t *msg, *jprotected;
+ const char *prot64, *pay64, *mac64, *sign, *prot;
+ md_data_t data;
+ apr_status_t rv;
+
+ msg = md_json_create(p);
+ jprotected = md_json_clone(p, prot_fields);
+ md_json_sets("HS256", jprotected, "alg", NULL);
+ prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
+ if (!prot) {
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "serialize protected");
+ goto cleanup;
+ }
+
+ md_data_init(&data, prot, strlen(prot));
+ prot64 = md_util_base64url_encode(&data, p);
+ md_json_sets(prot64, msg, "protected", NULL);
+
+ pay64 = md_util_base64url_encode(payload, p);
+ md_json_sets(pay64, msg, "payload", NULL);
+ sign = apr_psprintf(p, "%s.%s", prot64, pay64);
+
+ rv = md_crypt_hmac64(&mac64, hmac_key, p, sign, strlen(sign));
+ if (APR_SUCCESS != rv) {
+ goto cleanup;
+ }
+ md_json_sets(mac64, msg, "signature", NULL);
+
+cleanup:
+ *pmsg = (APR_SUCCESS == rv)? msg : NULL;
+ return rv;
+}

Modified: httpd/httpd/branches/2.4.x/modules/md/md_jws.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_jws.h?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_jws.h (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_jws.h Wed Nov 24 11:07:53 2021
@@ -22,10 +22,31 @@ struct md_json_t;
struct md_pkey_t;
struct md_data_t;

+/**
+ * Get the JSON value of the 'jwk' field for the given key.
+ */
+apr_status_t md_jws_get_jwk(md_json_t **pjwk, apr_pool_t *p, struct md_pkey_t *pkey);
+
+/**
+ * Get the JWS key signed JSON message with given payload and protected fields, signed
+ * using the given key and optional key_id.
+ */
apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
- struct md_data_t *payload, struct apr_table_t *protected,
+ struct md_data_t *payload, md_json_t *prot_fields,
struct md_pkey_t *pkey, const char *key_id);
+/**
+ * Get the 'Thumbprint' as defined in RFC8555 for the given key in
+ * base64 encoding.
+ */
+apr_status_t md_jws_pkey_thumb(const char **pthumb64, apr_pool_t *p, struct md_pkey_t *pkey);
+
+/**
+ * Get the JWS HS256 signed message for given payload and protected fields,
+ * using the base64 encoded MAC key.
+ */
+apr_status_t md_jws_hmac(md_json_t **pmsg, apr_pool_t *p,
+ struct md_data_t *payload, md_json_t *prot_fields,
+ const struct md_data_t *hmac_key);

-apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pkey_t *pkey);

#endif /* md_jws_h */

Modified: httpd/httpd/branches/2.4.x/modules/md/md_reg.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/md/md_reg.c?rev=1895289&r1=1895288&r2=1895289&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/md/md_reg.c (original)
+++ httpd/httpd/branches/2.4.x/modules/md/md_reg.c Wed Nov 24 11:07:53 2021
@@ -92,7 +92,8 @@ apr_status_t md_reg_create(md_reg_t **pr
reg->can_http = 1;
reg->can_https = 1;
reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
- reg->ca_file = ca_file? apr_pstrdup(p, ca_file) : NULL;
+ reg->ca_file = (ca_file && apr_strnatcasecmp("none", ca_file))?
+ apr_pstrdup(p, ca_file) : NULL;

md_timeslice_create(&reg->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF);
md_timeslice_create(&reg->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF);
@@ -197,9 +198,11 @@ static apr_status_t check_values(md_reg_

static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
{
- md_state_t state;
+ md_state_t state = MD_S_COMPLETE;
+ const char *state_descr = NULL;
const md_pubcert_t *pub;
const md_cert_t *cert;
+ const md_pkey_spec_t *spec;
apr_status_t rv = APR_SUCCESS;
int i;

@@ -207,45 +210,50 @@ static apr_status_t state_init(md_reg_t
if (md->warn_window == NULL) md->warn_window = reg->warn_window;

for (i = 0; i < md_cert_count(md); ++i) {
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: check cert %d", md->name, i);
- if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, i, p))) {
+ spec = md_pkeys_spec_get(md->pks, i);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
+ "md{%s}: check cert %s", md->name, md_pkey_spec_name(spec));
+ rv = md_reg_get_pubcert(&pub, reg, md, i, p);
+ if (APR_SUCCESS == rv) {
cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
if (!md_is_covered_by_alt_names(md, pub->alt_names)) {
state = MD_S_INCOMPLETE;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: incomplete, certificate(%d) does not cover all domains.",
- md->name, i);
- goto out;
+ state_descr = apr_psprintf(p, "certificate(%s) does not cover all domains.",
+ md_pkey_spec_name(spec));
+ goto cleanup;
}
if (!md->must_staple != !md_cert_must_staple(cert)) {
state = MD_S_INCOMPLETE;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: incomplete, OCSP Stapling is%s requested, but "
- "certificate(%d) has it%s enabled.",
- md->name, md->must_staple? "" : " not", i,
+ state_descr = apr_psprintf(p, "'must-staple' is%s requested, but "
+ "certificate(%s) has it%s enabled.",
+ md->must_staple? "" : " not",
+ md_pkey_spec_name(spec),
!md->must_staple? "" : " not");
- goto out;
+ goto cleanup;
}
- state = MD_S_COMPLETE;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: certificate(%d) is ok",
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: certificate(%d) is ok",
md->name, i);
}
else if (APR_STATUS_IS_ENOENT(rv)) {
state = MD_S_INCOMPLETE;
+ state_descr = apr_psprintf(p, "certificate(%s) is missing",
+ md_pkey_spec_name(spec));
rv = APR_SUCCESS;
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
- "md{%s}: incomplete, certificate(%d) is missing", md->name, i);
- goto out;
+ goto cleanup;
+ }
+ else {
+ state = MD_S_ERROR;
+ state_descr = "error intializing";
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name);
+ goto cleanup;
}
}

-out:
- if (APR_SUCCESS != rv) {
- state = MD_S_ERROR;
- md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name);
- }
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: state==%d", md->name, state);
+cleanup:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "md{%s}: state=%d, %s",
+ md->name, state, state_descr);
md->state = state;
+ md->state_descr = state_descr;
return rv;
}