summaryrefslogtreecommitdiff
path: root/modules/md
diff options
context:
space:
mode:
authorStefan Eissing <icing@apache.org>2021-04-13 11:12:00 +0000
committerStefan Eissing <icing@apache.org>2021-04-13 11:12:00 +0000
commitdb5aa786d86e1ef1105f9d9962b496130170bcf6 (patch)
tree1d083d775c397b453c13c5f5be484d1a08ab774f /modules/md
parent9e2ed5bb859577184b9dfba1e07ee3a55671532f (diff)
downloadhttpd-db5aa786d86e1ef1105f9d9962b496130170bcf6.tar.gz
*) core/mod_ssl/mod_md: adding OCSP response provisioning as core feature. This
allows modules to access and provide OCSP response data without being tied of each other. The data is exchanged in standard, portable formats (PEM encoded certificates and DER encoded responses), so that the actual SSL/crypto implementations used by the modules are independant of each other. Registration and retrieval happen in the context of a server (server_rec) which modules may use to decide if they are configured for this or not. The area of changes: 1. core: defines 2 functions in include/http_ssl.h, so that modules may register a certificate, together with its issuer certificate for OCSP response provisioning and ask for current response data (DER bytes) later. Also, 2 hooks are defined that allow modules to implement this OCSP provisioning. 2. mod_ssl uses the new functions, in addition to what it did already, to register its certificates this way. If no one is interested in providing OCSP, it falls back to its own (if configured) stapling implementation. 3. mod_md registers itself at the core hooks for OCSP provisioning. Depending on configuration, it will accept registrations of its own certificates only, all certficates or none. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1888723 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules/md')
-rw-r--r--modules/md/md_acme_drive.c2
-rw-r--r--modules/md/md_crypt.c94
-rw-r--r--modules/md/md_crypt.h6
-rw-r--r--modules/md/md_ocsp.c209
-rw-r--r--modules/md/md_ocsp.h13
-rw-r--r--modules/md/mod_md.c6
-rw-r--r--modules/md/mod_md_ocsp.c116
-rw-r--r--modules/md/mod_md_ocsp.h6
8 files changed, 290 insertions, 162 deletions
diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c
index bc5eb2b2a0..4bdaf6bf65 100644
--- a/modules/md/md_acme_drive.c
+++ b/modules/md/md_acme_drive.c
@@ -201,6 +201,8 @@ static apr_status_t add_http_certs(apr_array_header_t *chain, apr_pool_t *p,
ct = apr_table_get(res->headers, "Content-Type");
ct = md_util_parse_ct(res->req->pool, ct);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p,
+ "parse certs from %s -> %d (%s)", res->req->url, res->status, ct);
if (ct && !strcmp("application/x-pkcs7-mime", ct)) {
/* this looks like a root cert and we do not want those in our chain */
goto out;
diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c
index 4c97f4375b..5c4d9f047e 100644
--- a/modules/md/md_crypt.c
+++ b/modules/md/md_crypt.c
@@ -1359,22 +1359,44 @@ static int md_cert_read_pem(BIO *bf, apr_pool_t *p, md_cert_t **pcert)
{
md_cert_t *cert;
X509 *x509;
- apr_status_t rv;
+ apr_status_t rv = APR_ENOENT;
ERR_clear_error();
x509 = PEM_read_bio_X509(bf, NULL, NULL, NULL);
- if (x509 == NULL) {
- rv = APR_ENOENT;
- goto out;
- }
+ if (x509 == NULL) goto cleanup;
cert = md_cert_make(p, x509);
rv = APR_SUCCESS;
-
-out:
+cleanup:
*pcert = (APR_SUCCESS == rv)? cert : NULL;
return rv;
}
+apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p,
+ const char *pem, apr_size_t pem_len)
+{
+ BIO *bf = NULL;
+ apr_status_t rv = APR_SUCCESS;
+ md_cert_t *cert;
+ int added = 0;
+
+ if (NULL == (bf = BIO_new_mem_buf(pem, (int)pem_len))) {
+ rv = APR_ENOMEM;
+ goto cleanup;
+ }
+ while (APR_SUCCESS == (rv = md_cert_read_pem(bf, chain->pool, &cert))) {
+ APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+ added = 1;
+ }
+ if (APR_ENOENT == rv && added) {
+ rv = APR_SUCCESS;
+ }
+
+cleanup:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "read chain with %d certs", chain->nelts);
+ if (bf) BIO_free(bf);
+ return rv;
+}
+
apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p,
const md_http_response_t *res)
{
@@ -1420,64 +1442,40 @@ out:
apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain,
apr_pool_t *p, const struct md_http_response_t *res)
{
- const char *ct;
+ const char *ct = NULL;
apr_off_t blen;
- apr_size_t data_len;
+ apr_size_t data_len = 0;
char *data;
- BIO *bf = NULL;
- apr_status_t rv;
+ md_cert_t *cert;
+ apr_status_t rv = APR_ENOENT;
- if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto out;
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p,
+ "chain_read, processing %d response", res->status);
+ if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto cleanup;
if (blen > 1024*1024) { /* certs usually are <2k each */
rv = APR_EINVAL;
- goto out;
+ goto cleanup;
}
data_len = (apr_size_t)blen;
ct = apr_table_get(res->headers, "Content-Type");
- if (!res->body || !ct) {
- rv = APR_ENOENT;
- goto out;
- }
+ if (!res->body || !ct) goto cleanup;
ct = md_util_parse_ct(res->req->pool, ct);
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 */
- if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool))) {
- int added = 0;
- md_cert_t *cert;
-
- if (NULL == (bf = BIO_new_mem_buf(data, (int)data_len))) {
- rv = APR_ENOMEM;
- goto out;
- }
-
- while (APR_SUCCESS == (rv = md_cert_read_pem(bf, p, &cert))) {
- APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
- added = 1;
- }
- if (APR_ENOENT == rv && added) {
- rv = APR_SUCCESS;
- }
- }
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
- "parsing cert from content-type=%s, content-length=%ld", ct, (long)data_len);
+ 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)) {
- md_cert_t *cert;
-
rv = md_cert_read_http(&cert, p, res);
- if (APR_SUCCESS == rv) {
- APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
- }
+ if (APR_SUCCESS != rv) goto cleanup;
+ APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
}
- else {
- /* unrecongized content type */
- rv = APR_ENOENT;
- goto out;
- }
-out:
- if (bf) BIO_free(bf);
+cleanup:
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p,
+ "parsed certs from content-type=%s, content-length=%ld", ct, (long)data_len);
return rv;
}
diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h
index 4706207da9..cd1db29441 100644
--- a/modules/md/md_crypt.h
+++ b/modules/md/md_crypt.h
@@ -154,6 +154,12 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *pool,
const struct md_http_response_t *res);
/**
+ * Read at least one certificate from the given PEM data.
+ */
+apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p,
+ const char *pem, apr_size_t pem_len);
+
+/**
* Read one or even a chain of certificates from a http response.
* Will return APR_ENOENT if content-type is not recognized (currently
* supports only "application/pem-certificate-chain" and "application/pkix-cert").
diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c
index ea9366b84a..c6301a0f0c 100644
--- a/modules/md/md_ocsp.c
+++ b/modules/md/md_ocsp.c
@@ -59,7 +59,8 @@ struct md_ocsp_reg_t {
md_store_t *store;
const char *user_agent;
const char *proxy_url;
- apr_hash_t *hash;
+ apr_hash_t *id_by_external_id;
+ apr_hash_t *ostat_by_id;
apr_thread_mutex_t *mutex;
md_timeslice_t renew_window;
md_job_notify_cb *notify;
@@ -92,6 +93,12 @@ struct md_ocsp_status_t {
apr_time_t resp_last_check;
};
+typedef struct md_ocsp_id_map_t md_ocsp_id_map_t;
+struct md_ocsp_id_map_t {
+ md_data_t id;
+ md_data_t external_id;
+};
+
const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat)
{
switch (stat) {
@@ -108,16 +115,17 @@ md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name)
return MD_OCSP_CERT_ST_UNKNOWN;
}
-static apr_status_t init_cert_id(md_data_t *data, const md_cert_t *cert)
+apr_status_t md_ocsp_init_id(md_data_t *id, apr_pool_t *p, const md_cert_t *cert)
{
+ unsigned char iddata[SHA_DIGEST_LENGTH];
X509 *x = md_cert_get_X509(cert);
unsigned int ulen = 0;
- assert(data->len == SHA_DIGEST_LENGTH);
- if (X509_digest(x, EVP_sha1(), (unsigned char*)data->data, &ulen) != 1) {
+ if (X509_digest(x, EVP_sha1(), iddata, &ulen) != 1) {
return APR_EGENERAL;
}
- data->len = ulen;
+ id->len = ulen;
+ id->data = apr_pmemdup(p, iddata, id->len);
return APR_SUCCESS;
}
@@ -173,7 +181,7 @@ static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat,
s = OPENSSL_malloc(der->len);
if (!s) {
rv = APR_ENOMEM;
- goto leave;
+ goto cleanup;
}
memcpy((char*)s, der->data, der->len);
}
@@ -194,7 +202,7 @@ static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat,
ostat->next_run = md_timeperiod_slice_before_end(
&ostat->resp_valid, &ostat->reg->renew_window).start;
-leave:
+cleanup:
return rv;
}
@@ -213,12 +221,12 @@ static apr_status_t ostat_from_json(md_ocsp_cert_stat_t *pstat,
s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_UNTIL, NULL);
if (s && *s) valid.end = apr_date_parse_rfc(s);
s = md_json_dups(p, json, MD_KEY_RESPONSE, NULL);
- if (!s || !*s) goto leave;
+ if (!s || !*s) goto cleanup;
md_util_base64url_decode(resp_der, s, p);
*pstat = md_ocsp_cert_stat_value(md_json_gets(json, MD_KEY_STATUS, NULL));
*resp_valid = valid;
rv = APR_SUCCESS;
-leave:
+cleanup:
return rv;
}
@@ -247,14 +255,14 @@ static apr_status_t ocsp_status_refresh(md_ocsp_status_t *ostat, apr_pool_t *pte
md_ocsp_cert_stat_t resp_stat;
/* Check if the store holds a newer response than the one we have */
mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
- if (mtime <= ostat->resp_mtime) goto leave;
+ if (mtime <= ostat->resp_mtime) goto cleanup;
rv = md_store_load_json(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, &jprops, ptemp);
- if (APR_SUCCESS != rv) goto leave;
+ if (APR_SUCCESS != rv) goto cleanup;
rv = ostat_from_json(&resp_stat, &resp_der, &resp_valid, jprops, ptemp);
- if (APR_SUCCESS != rv) goto leave;
+ if (APR_SUCCESS != rv) goto cleanup;
rv = ostat_set(ostat, resp_stat, &resp_der, &resp_valid, mtime);
- if (APR_SUCCESS != rv) goto leave;
-leave:
+ if (APR_SUCCESS != rv) goto cleanup;
+cleanup:
return rv;
}
@@ -271,10 +279,10 @@ static apr_status_t ocsp_status_save(md_ocsp_cert_stat_t stat, const md_data_t *
jprops = md_json_create(ptemp);
ostat_to_json(jprops, stat, resp_der, resp_valid, ptemp);
rv = md_store_save_json(store, ptemp, MD_SG_OCSP, ostat->md_name, ostat->file_name, jprops, 0);
- if (APR_SUCCESS != rv) goto leave;
+ if (APR_SUCCESS != rv) goto cleanup;
mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
if (mtime) ostat->resp_mtime = mtime;
-leave:
+cleanup:
return rv;
}
@@ -283,7 +291,7 @@ static apr_status_t ocsp_reg_cleanup(void *data)
md_ocsp_reg_t *reg = data;
/* free all OpenSSL structures that we hold */
- apr_hash_do(ostat_cleanup, reg, reg->hash);
+ apr_hash_do(ostat_cleanup, reg, reg->ostat_by_id);
return APR_SUCCESS;
}
@@ -297,53 +305,53 @@ apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, md_store_t *s
reg = apr_palloc(p, sizeof(*reg));
if (!reg) {
rv = APR_ENOMEM;
- goto leave;
+ goto cleanup;
}
reg->p = p;
reg->store = store;
reg->user_agent = user_agent;
reg->proxy_url = proxy_url;
- reg->hash = apr_hash_make(p);
+ reg->id_by_external_id = apr_hash_make(p);
+ reg->ostat_by_id = apr_hash_make(p);
reg->renew_window = *renew_window;
rv = apr_thread_mutex_create(&reg->mutex, APR_THREAD_MUTEX_NESTED, p);
- if (APR_SUCCESS != rv) goto leave;
+ if (APR_SUCCESS != rv) goto cleanup;
apr_pool_cleanup_register(p, reg, ocsp_reg_cleanup, apr_pool_cleanup_null);
-leave:
+cleanup:
*preg = (APR_SUCCESS == rv)? reg : NULL;
return rv;
}
-apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issuer, const md_t *md)
+apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const md_data_t *external_id,
+ md_cert_t *cert, md_cert_t *issuer, const md_t *md)
{
- char iddata[MD_OCSP_ID_LENGTH];
md_ocsp_status_t *ostat;
STACK_OF(OPENSSL_STRING) *ssk = NULL;
const char *name, *s;
md_data_t id;
- apr_status_t rv;
+ apr_status_t rv = APR_SUCCESS;
/* Called during post_config. no mutex protection needed */
name = md? md->name : MD_OTHER;
- id.data = iddata; id.len = sizeof(iddata);
-
- md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p,
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p,
"md[%s]: priming OCSP status", name);
- rv = init_cert_id(&id, cert);
- if (APR_SUCCESS != rv) goto leave;
-
- ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
- if (ostat) goto leave; /* already seen it, cert is used in >1 server_rec */
-
+
+ rv = md_ocsp_init_id(&id, reg->p, cert);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ ostat = apr_hash_get(reg->ostat_by_id, id.data, (apr_ssize_t)id.len);
+ if (ostat) goto cleanup; /* already seen it, cert is used in >1 server_rec */
+
ostat = apr_pcalloc(reg->p, sizeof(*ostat));
- md_data_assign_pcopy(&ostat->id, &id, reg->p);
+ ostat->id = id;
ostat->reg = reg;
ostat->md_name = name;
md_data_to_hex(&ostat->hexid, 0, reg->p, &ostat->id);
ostat->file_name = apr_psprintf(reg->p, "ocsp-%s.json", ostat->hexid);
rv = md_cert_to_sha256_fingerprint(&ostat->hex_sha256, cert, reg->p);
- if (APR_SUCCESS != rv) goto leave;
+ if (APR_SUCCESS != rv) goto cleanup;
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
"md[%s]: getting ocsp responder from cert", name);
@@ -353,7 +361,7 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p,
"md[%s]: certificate with serial %s has not OCSP responder URL",
name, md_cert_get_serial_number(cert, reg->p));
- goto leave;
+ goto cleanup;
}
s = sk_OPENSSL_STRING_value(ssk, 0);
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
@@ -367,7 +375,7 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue
md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p,
"md[%s]: unable to create OCSP certid for certificate with serial %s",
name, md_cert_get_serial_number(cert, reg->p));
- goto leave;
+ goto cleanup;
}
/* See, if we have something in store */
@@ -375,38 +383,46 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, reg->p,
"md[%s]: adding ocsp info (responder=%s)",
name, ostat->responder_url);
- apr_hash_set(reg->hash, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat);
+ apr_hash_set(reg->ostat_by_id, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat);
+ if (external_id) {
+ md_ocsp_id_map_t *id_map;
+
+ id_map = apr_pcalloc(reg->p, sizeof(*id_map));
+ id_map->id = id;
+ md_data_assign_pcopy(&id_map->external_id, external_id, reg->p);
+ /* check for collision/uniqness? */
+ apr_hash_set(reg->id_by_external_id, id_map->external_id.data,
+ (apr_ssize_t)id_map->external_id.len, id_map);
+ }
rv = APR_SUCCESS;
-leave:
+cleanup:
return rv;
}
-apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
- md_ocsp_reg_t *reg, const md_cert_t *cert,
+apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata,
+ md_ocsp_reg_t *reg, const md_data_t *external_id,
apr_pool_t *p, const md_t *md)
{
- char iddata[MD_OCSP_ID_LENGTH];
md_ocsp_status_t *ostat;
const char *name;
- apr_status_t rv;
+ apr_status_t rv = APR_SUCCESS;
+ md_ocsp_id_map_t *id_map;
+ const md_data_t *id;
int locked = 0;
- md_data_t id;
-
+
(void)p;
(void)md;
- id.data = iddata; id.len = sizeof(iddata);
- *pder = NULL;
- *pderlen = 0;
name = md? md->name : MD_OTHER;
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
"md[%s]: OCSP, get_status", name);
- rv = init_cert_id(&id, cert);
- if (APR_SUCCESS != rv) goto leave;
-
- ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
+
+ id_map = apr_hash_get(reg->id_by_external_id,
+ external_id->data, (apr_ssize_t)external_id->len);
+ id = id_map? &id_map->id : external_id;
+ ostat = apr_hash_get(reg->ostat_by_id, id->data, (apr_ssize_t)id->len);
if (!ostat) {
rv = APR_ENOENT;
- goto leave;
+ goto cleanup;
}
/* While the ostat instance itself always exists, the response data it holds
@@ -420,7 +436,8 @@ apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
if (ostat->resp_der.len <= 0) {
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
"md[%s]: OCSP, no response available", name);
- goto leave;
+ cb(NULL, 0, userdata);
+ goto cleanup;
}
}
/* We have a response */
@@ -441,18 +458,12 @@ apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
ocsp_status_refresh(ostat, p);
}
}
-
- *pder = OPENSSL_malloc(ostat->resp_der.len);
- if (*pder == NULL) {
- rv = APR_ENOMEM;
- goto leave;
- }
- memcpy(*pder, ostat->resp_der.data, ostat->resp_der.len);
- *pderlen = (int)ostat->resp_der.len;
- md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
- "md[%s]: OCSP, returning %ld bytes of response",
+
+ cb((const unsigned char*)ostat->resp_der.data, ostat->resp_der.len, userdata);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
+ "md[%s]: OCSP, provided %ld bytes of response",
name, (long)ostat->resp_der.len);
-leave:
+cleanup:
if (locked) apr_thread_mutex_unlock(reg->mutex);
return rv;
}
@@ -475,7 +486,6 @@ apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvali
md_ocsp_reg_t *reg, const md_cert_t *cert,
apr_pool_t *p, const md_t *md)
{
- char iddata[MD_OCSP_ID_LENGTH];
md_ocsp_status_t *ostat;
const char *name;
apr_status_t rv;
@@ -485,23 +495,22 @@ apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvali
(void)p;
(void)md;
- id.data = iddata; id.len = sizeof(iddata);
name = md? md->name : MD_OTHER;
memset(&valid, 0, sizeof(valid));
stat = MD_OCSP_CERT_ST_UNKNOWN;
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p,
"md[%s]: OCSP, get_status", name);
- rv = init_cert_id(&id, cert);
- if (APR_SUCCESS != rv) goto leave;
+ rv = md_ocsp_init_id(&id, p, cert);
+ if (APR_SUCCESS != rv) goto cleanup;
- ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
+ ostat = apr_hash_get(reg->ostat_by_id, id.data, (apr_ssize_t)id.len);
if (!ostat) {
rv = APR_ENOENT;
- goto leave;
+ goto cleanup;
}
ocsp_get_meta(&stat, &valid, reg, ostat, p);
-leave:
+cleanup:
*pstat = stat;
*pvalid = valid;
return rv;
@@ -509,7 +518,7 @@ leave:
apr_size_t md_ocsp_count(md_ocsp_reg_t *reg)
{
- return apr_hash_count(reg->hash);
+ return apr_hash_count(reg->ostat_by_id);
}
static const char *certid_as_hex(const OCSP_CERTID *certid, apr_pool_t *p)
@@ -618,14 +627,14 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
ostat->hexid);
if (APR_SUCCESS != (rv = apr_brigade_pflatten(resp->body, (char**)&der.data,
&der.len, req->pool))) {
- goto leave;
+ goto cleanup;
}
if (NULL == (ocsp_resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char**)&der.data,
(long)der.len))) {
rv = APR_EINVAL;
md_result_set(update->result, rv, "response body does not parse as OCSP response");
md_result_log(update->result, MD_LOG_DEBUG);
- goto leave;
+ goto cleanup;
}
/* got a response! but what does it say? */
n = OCSP_response_status(ocsp_resp);
@@ -633,14 +642,14 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
rv = APR_EINVAL;
md_result_printf(update->result, rv, "OCSP response status is, unsuccessfully, %d", n);
md_result_log(update->result, MD_LOG_DEBUG);
- goto leave;
+ goto cleanup;
}
basic_resp = OCSP_response_get1_basic(ocsp_resp);
if (!basic_resp) {
rv = APR_EINVAL;
md_result_set(update->result, rv, "OCSP response has no basicresponse");
md_result_log(update->result, MD_LOG_DEBUG);
- goto leave;
+ goto cleanup;
}
/* The notion of nonce enabled freshness in OCSP responses, e.g. that the response
* contains the signed nonce we sent to the responder, does not scale well. Responders
@@ -656,7 +665,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
rv = APR_EINVAL;
md_result_printf(update->result, rv, "OCSP nonce mismatch in response", n);
md_result_log(update->result, MD_LOG_WARNING);
- goto leave;
+ goto cleanup;
case -1:
md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool,
@@ -682,19 +691,19 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
}
md_result_printf(update->result, rv, "%s, status list [%s]", prefix, slist);
md_result_log(update->result, MD_LOG_DEBUG);
- goto leave;
+ goto cleanup;
}
if (V_OCSP_CERTSTATUS_UNKNOWN == bstatus) {
rv = APR_ENOENT;
md_result_set(update->result, rv, "OCSP basicresponse says cert is unknown");
md_result_log(update->result, MD_LOG_DEBUG);
- goto leave;
+ goto cleanup;
}
if (!bnextup) {
rv = APR_EINVAL;
md_result_set(update->result, rv, "OCSP basicresponse reports not valid dates");
md_result_log(update->result, MD_LOG_DEBUG);
- goto leave;
+ goto cleanup;
}
/* Coming here, we have a response for our certid and it is either GOOD
@@ -704,7 +713,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
rv = APR_EGENERAL;
md_result_set(update->result, rv, "error DER encoding OCSP response");
md_result_log(update->result, MD_LOG_WARNING);
- goto leave;
+ goto cleanup;
}
nstat = (bstatus == V_OCSP_CERTSTATUS_GOOD)? MD_OCSP_CERT_ST_GOOD : MD_OCSP_CERT_ST_REVOKED;
new_der.len = (apr_size_t)n;
@@ -721,7 +730,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
if (APR_SUCCESS != rv) {
md_result_set(update->result, rv, "error saving OCSP status");
md_result_log(update->result, MD_LOG_ERR);
- goto leave;
+ goto cleanup;
}
md_result_printf(update->result, rv, "certificate status is %s, status valid %s",
@@ -729,7 +738,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
md_timeperiod_print(req->pool, &ostat->resp_valid));
md_result_log(update->result, MD_LOG_DEBUG);
-leave:
+cleanup:
if (new_der.data) OPENSSL_free((void*)new_der.data);
if (basic_resp) OCSP_BASICRESP_free(basic_resp);
if (ocsp_resp) OCSP_RESPONSE_free(ocsp_resp);
@@ -753,11 +762,11 @@ static apr_status_t ostat_on_req_status(const md_http_request_t *req, apr_status
md_job_log_append(update->job, "ocsp-error",
update->result->problem, update->result->detail);
md_event_holler("ocsp-errored", update->job->mdomain, update->job, update->result, update->p);
- goto leave;
+ goto cleanup;
}
md_event_holler("ocsp-renewed", update->job->mdomain, update->job, update->result, update->p);
-leave:
+cleanup:
md_job_save(update->job, update->result, update->p);
ostat_req_cleanup(ostat);
return APR_SUCCESS;
@@ -795,16 +804,16 @@ static apr_status_t next_todo(md_http_request_t **preq, void *baton,
if (!ostat->ocsp_req) {
ostat->ocsp_req = OCSP_REQUEST_new();
- if (!ostat->ocsp_req) goto leave;
+ if (!ostat->ocsp_req) goto cleanup;
certid = OCSP_CERTID_dup(ostat->certid);
- if (!certid) goto leave;
- if (!OCSP_request_add0_id(ostat->ocsp_req, certid)) goto leave;
+ if (!certid) goto cleanup;
+ if (!OCSP_request_add0_id(ostat->ocsp_req, certid)) goto cleanup;
OCSP_request_add1_nonce(ostat->ocsp_req, 0, -1);
certid = NULL;
}
if (0 == ostat->req_der.len) {
len = i2d_OCSP_REQUEST(ostat->ocsp_req, (unsigned char**)&ostat->req_der.data);
- if (len < 0) goto leave;
+ if (len < 0) goto cleanup;
ostat->req_der.len = (apr_size_t)len;
}
md_result_activity_printf(update->result, "status of certid %s, "
@@ -813,13 +822,13 @@ static apr_status_t next_todo(md_http_request_t **preq, void *baton,
apr_table_set(headers, "Expect", "");
rv = md_http_POSTd_create(&req, http, ostat->responder_url, headers,
"application/ocsp-request", &ostat->req_der);
- if (APR_SUCCESS != rv) goto leave;
+ if (APR_SUCCESS != rv) goto cleanup;
md_http_set_on_status_cb(req, ostat_on_req_status, update);
md_http_set_on_response_cb(req, ostat_on_resp, update);
rv = APR_SUCCESS;
}
}
-leave:
+cleanup:
*preq = (APR_SUCCESS == rv)? req : NULL;
if (certid) OCSP_CERTID_free(certid);
return rv;
@@ -873,21 +882,21 @@ void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_tim
/* Create a list of update tasks that are needed now or in the next minute */
ctx.time = apr_time_now() + apr_time_from_sec(60);;
- apr_hash_do(select_updates, &ctx, reg->hash);
+ apr_hash_do(select_updates, &ctx, reg->ostat_by_id);
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
"OCSP status updates due: %d", ctx.todos->nelts);
- if (!ctx.todos->nelts) goto leave;
+ if (!ctx.todos->nelts) goto cleanup;
rv = md_http_create(&http, ptemp, reg->user_agent, reg->proxy_url);
- if (APR_SUCCESS != rv) goto leave;
+ if (APR_SUCCESS != rv) goto cleanup;
rv = md_http_multi_perform(http, next_todo, &ctx);
-leave:
+cleanup:
/* When do we need to run next? *pnext_run contains the planned schedule from
* the watchdog. We can make that earlier if we need it. */
ctx.time = *pnext_run;
- apr_hash_do(select_next_run, &ctx, reg->hash);
+ apr_hash_do(select_next_run, &ctx, reg->ostat_by_id);
/* sanity check and return */
if (ctx.time < apr_time_now()) ctx.time = apr_time_now() + apr_time_from_sec(1);
@@ -940,7 +949,7 @@ void md_ocsp_get_summary(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p)
memset(&ctx, 0, sizeof(ctx));
ctx.p = p;
ctx.reg = reg;
- apr_hash_do(add_to_summary, &ctx, reg->hash);
+ apr_hash_do(add_to_summary, &ctx, reg->ostat_by_id);
json = md_json_create(p);
md_json_setl(ctx.good+ctx.revoked+ctx.unknown, json, MD_KEY_TOTAL, NULL);
@@ -1020,10 +1029,10 @@ void md_ocsp_get_status_all(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p
memset(&ctx, 0, sizeof(ctx));
ctx.p = p;
ctx.reg = reg;
- ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->hash), sizeof(md_ocsp_status_t*));
+ ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->ostat_by_id), sizeof(md_ocsp_status_t*));
json = md_json_create(p);
- apr_hash_do(add_ostat, &ctx, reg->hash);
+ apr_hash_do(add_ostat, &ctx, reg->ostat_by_id);
qsort(ctx.ostats->elts, (size_t)ctx.ostats->nelts, sizeof(md_json_t*), md_ostat_cmp);
for (i = 0; i < ctx.ostats->nelts; ++i) {
diff --git a/modules/md/md_ocsp.h b/modules/md/md_ocsp.h
index 61c387ec1e..7f2e356e50 100644
--- a/modules/md/md_ocsp.h
+++ b/modules/md/md_ocsp.h
@@ -17,6 +17,7 @@
#ifndef md_ocsp_h
#define md_ocsp_h
+struct md_data_t;
struct md_job_t;
struct md_json_t;
struct md_result_t;
@@ -39,11 +40,15 @@ apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p,
const md_timeslice_t *renew_window,
const char *user_agent, const char *proxy_url);
-apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *x,
- md_cert_t *issuer, const md_t *md);
+apr_status_t md_ocsp_init_id(struct md_data_t *id, apr_pool_t *p, const md_cert_t *cert);
-apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
- md_ocsp_reg_t *reg, const md_cert_t *cert,
+apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const struct md_data_t *external_id,
+ md_cert_t *x, md_cert_t *issuer, const md_t *md);
+
+typedef void md_ocsp_copy_der(const unsigned char *der, apr_size_t der_len, void *userdata);
+
+apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata,
+ md_ocsp_reg_t *reg, const struct md_data_t *external_id,
apr_pool_t *p, const md_t *md);
apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid,
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c
index ac3ff6fb35..2263fdb292 100644
--- a/modules/md/mod_md.c
+++ b/modules/md/mod_md.c
@@ -1497,10 +1497,16 @@ static void md_hooks(apr_pool_t *pool)
ap_hook_ssl_add_cert_files(md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_ssl_add_fallback_cert_files(md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
+#if AP_MODULE_MAGIC_AT_LEAST(20201214, 4)
+ ap_hook_ssl_ocsp_prime_hook(md_ocsp_prime_status, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_ssl_ocsp_get_resp_hook(md_ocsp_provide_status, NULL, NULL, APR_HOOK_MIDDLE);
+#else
+
#ifndef SSL_CERT_HOOKS
#error "This version of mod_md requires Apache httpd 2.4.41 or newer."
#endif
APR_OPTIONAL_HOOK(ssl, init_stapling_status, md_ocsp_init_stapling_status, NULL, NULL, APR_HOOK_MIDDLE);
APR_OPTIONAL_HOOK(ssl, get_stapling_status, md_ocsp_get_stapling_status, NULL, NULL, APR_HOOK_MIDDLE);
+#endif /* AP_MODULE_MAGIC_AT_LEAST() */
}
diff --git a/modules/md/mod_md_ocsp.c b/modules/md/mod_md_ocsp.c
index 2a01d5a846..0800650443 100644
--- a/modules/md/mod_md_ocsp.c
+++ b/modules/md/mod_md_ocsp.c
@@ -23,6 +23,7 @@
#include <httpd.h>
#include <http_core.h>
#include <http_log.h>
+#include <http_ssl.h>
#include "mod_watchdog.h"
@@ -53,7 +54,7 @@ static int staple_here(md_srv_conf_t *sc)
}
int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p,
- X509 *cert, X509 *issuer)
+ X509 *cert, X509 *issuer)
{
md_srv_conf_t *sc;
const md_t *md;
@@ -61,10 +62,10 @@ int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p,
sc = md_config_get(s);
if (!staple_here(sc)) goto declined;
-
md = ((sc->assigned && sc->assigned->nelts == 1)?
APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
- rv = md_ocsp_prime(sc->mc->ocsp, md_cert_wrap(p, cert),
+
+ rv = md_ocsp_prime(sc->mc->ocsp, NULL, md_cert_wrap(p, cert),
md_cert_wrap(p, issuer), md);
ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s",
md? md->name : s->server_hostname);
@@ -75,13 +76,75 @@ declined:
return DECLINED;
}
-int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen,
- conn_rec *c, server_rec *s, X509 *cert)
+int md_ocsp_prime_status(server_rec *s, apr_pool_t *p,
+ const ap_bytes_t *external_id, const char *pem)
+{
+ md_srv_conf_t *sc;
+ const md_t *md;
+ apr_array_header_t *chain;
+ apr_status_t rv = APR_ENOENT;
+ md_data_t eid;
+
+ sc = md_config_get(s);
+ if (!staple_here(sc)) goto cleanup;
+
+ md = ((sc->assigned && sc->assigned->nelts == 1)?
+ APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
+ chain = apr_array_make(p, 5, sizeof(md_cert_t*));
+ rv = md_cert_read_chain(chain, p, pem, strlen(pem));
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO() "init stapling for: %s, "
+ "unable to parse PEM data", md? md->name : s->server_hostname);
+ goto cleanup;
+ }
+ else if (chain->nelts < 2) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO() "init stapling for: %s, "
+ "need at least 2 certificates in PEM data", md? md->name : s->server_hostname);
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+
+ eid.data = (char*)external_id->data;
+ eid.len = external_id->len;
+ rv = md_ocsp_prime(sc->mc->ocsp, &eid,
+ APR_ARRAY_IDX(chain, 0, md_cert_t*),
+ APR_ARRAY_IDX(chain, 1, md_cert_t*), md);
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s",
+ md? md->name : s->server_hostname);
+
+cleanup:
+ return (APR_SUCCESS == rv)? OK : DECLINED;
+}
+
+typedef struct {
+ unsigned char *der;
+ apr_size_t der_len;
+} ocsp_copy_ctx_t;
+
+static void ocsp_copy_der(const unsigned char *der, apr_size_t der_len, void *userdata)
+{
+ ocsp_copy_ctx_t *ctx = userdata;
+
+ memset(ctx, 0, sizeof(*ctx));
+ if (der && der_len > 0) {
+ ctx->der = OPENSSL_malloc(der_len);
+ if (ctx->der != NULL) {
+ ctx->der_len = der_len;
+ memcpy(ctx->der, der, der_len);
+ }
+ }
+}
+
+int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen,
+ conn_rec *c, server_rec *s, X509 *x)
{
md_srv_conf_t *sc;
const md_t *md;
+ md_cert_t *cert;
+ md_data_t id;
apr_status_t rv;
-
+ ocsp_copy_ctx_t ctx;
+
sc = md_config_get(s);
if (!staple_here(sc)) goto declined;
@@ -89,15 +152,48 @@ int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen,
APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s",
md? md->name : s->server_hostname);
- rv = md_ocsp_get_status(pder, pderlen, sc->mc->ocsp,
- md_cert_wrap(c->pool, cert), c->pool, md);
+ cert = md_cert_wrap(c->pool, x);
+ rv = md_ocsp_init_id(&id, c->pool, cert);
+ if (APR_SUCCESS != rv) goto declined;
+
+ rv = md_ocsp_get_status(ocsp_copy_der, &ctx, sc->mc->ocsp, &id, c->pool, md);
if (APR_STATUS_IS_ENOENT(rv)) goto declined;
- return rv;
+ *pder = ctx.der;
+ *pderlen = ctx.der_len;
+ return OK;
declined:
return DECLINED;
}
-
+
+int md_ocsp_provide_status(server_rec *s, conn_rec *c,
+ const ap_bytes_t *external_id,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata)
+{
+ md_srv_conf_t *sc;
+ const md_t *md;
+ md_data_t eid;
+ apr_status_t rv;
+
+ sc = md_config_get(s);
+ if (!staple_here(sc)) goto declined;
+
+ md = ((sc->assigned && sc->assigned->nelts == 1)?
+ APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s",
+ md? md->name : s->server_hostname);
+
+ eid.data = (const char *)external_id->data;
+ eid.len = external_id->len;
+ rv = md_ocsp_get_status(cb, userdata, sc->mc->ocsp, &eid, c->pool, md);
+ if (APR_STATUS_IS_ENOENT(rv)) goto declined;
+ return OK;
+
+declined:
+ return DECLINED;
+}
+
+
/**************************************************************************************************/
/* watchdog based impl. */
diff --git a/modules/md/mod_md_ocsp.h b/modules/md/mod_md_ocsp.h
index ee58df678a..48f0db34aa 100644
--- a/modules/md/mod_md_ocsp.h
+++ b/modules/md/mod_md_ocsp.h
@@ -24,6 +24,12 @@ int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p,
int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen,
conn_rec *c, server_rec *s, X509 *cert);
+int md_ocsp_prime_status(server_rec *s, apr_pool_t *p,
+ const ap_bytes_t *id, const char *pem);
+
+int md_ocsp_provide_status(server_rec *s, conn_rec *c, const ap_bytes_t *id,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata);
+
/**
* Start watchdog for retrieving/updating ocsp status.
*/