summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes-entries/ocsp_stapling_core.txt20
-rw-r--r--include/ap_mmn.h4
-rw-r--r--include/http_ssl.h80
-rw-r--r--include/httpd.h11
-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
-rw-r--r--modules/ssl/ssl_util_ssl.c23
-rw-r--r--modules/ssl/ssl_util_ssl.h13
-rw-r--r--modules/ssl/ssl_util_stapling.c70
-rw-r--r--server/ssl.c25
16 files changed, 515 insertions, 183 deletions
diff --git a/changes-entries/ocsp_stapling_core.txt b/changes-entries/ocsp_stapling_core.txt
new file mode 100644
index 0000000000..4830b23d6a
--- /dev/null
+++ b/changes-entries/ocsp_stapling_core.txt
@@ -0,0 +1,20 @@
+ *) 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.
+ [Stefan Eissing]
diff --git a/include/ap_mmn.h b/include/ap_mmn.h
index d1435ff9a7..c1a0c9c8d5 100644
--- a/include/ap_mmn.h
+++ b/include/ap_mmn.h
@@ -667,6 +667,8 @@
* 20201214.1 (2.5.1-dev) Add ap_ssl_conn_is_ssl()/ap_ssl_var_lookup() and hooks
* 20201214.2 (2.5.1-dev) Add ap_ssl_add_cert_files, ap_ssl_add_fallback_cert_files
* 20201214.3 (2.5.1-dev) Move ap_ssl_* into new http_ssl.h header file
+ * 20201214.4 (2.5.1-dev) Add `ap_bytes_t` to httpd.h.
+ * Add ap_ssl_ocsp* hooks and functions to http_ssl.h.
*/
#define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */
@@ -674,7 +676,7 @@
#ifndef MODULE_MAGIC_NUMBER_MAJOR
#define MODULE_MAGIC_NUMBER_MAJOR 20201214
#endif
-#define MODULE_MAGIC_NUMBER_MINOR 3 /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 4 /* 0...n */
/**
* Determine if the server's current MODULE_MAGIC_NUMBER is at least a
diff --git a/include/http_ssl.h b/include/http_ssl.h
index 90d672eda2..d238439e9a 100644
--- a/include/http_ssl.h
+++ b/include/http_ssl.h
@@ -190,6 +190,86 @@ AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name,
*/
AP_DECLARE(void) ap_setup_ssl_optional_fns(apr_pool_t *pool);
+/**
+ * Providers of OCSP status responses register at this hook. Installed hooks returning OK
+ * are expected to provide later OCSP responses via a 'ap_ssl_ocsp_get_resp_hook'.
+ * @param s the server being configured
+ * @params p a memory pool to use
+ * @param id opaque data uniquely identifying the certificate, provided by caller
+ * @param pem PEM data of certificate first, followed by PEM of issuer cert
+ * @return OK iff stapling is being provided
+ */
+AP_DECLARE_HOOK(int, ssl_ocsp_prime_hook, (server_rec *s, apr_pool_t *p,
+ const ap_bytes_t *id, const char *pem))
+
+/**
+ * Registering a certificate for Provisioning of OCSP responses. It is the caller's
+ * responsibility to provide a global (apache instance) unique id for the certificate
+ * that is then used later in retrieving the OCSP response.
+ * A certificate can be primed this way more than once, however the same identifier
+ * has to be provided each time (byte-wise same, not pointer same).
+ * The memory pointed to by `id` and `pem` is only valid for the duration of the call.
+ *
+ * @param s the server being configured
+ * @params p a memory pool to use
+ * @param id opaque data uniquely identifying the certificate, provided by caller
+ * @param pem PEM data of certificate first, followed by chain certs, at least the issuer
+ * @return APR_SUCCESS iff OCSP responses will be provided.
+ * APR_ENOENT when no provided was found or took responsibility.
+ */
+AP_DECLARE(apr_status_t) ap_ssl_ocsp_prime(server_rec *s, apr_pool_t *p,
+ const ap_bytes_t *id,
+ const char *pem);
+
+/**
+ * Callback to copy over the OCSP response data. If OCSP response data is not
+ * available, this will be called with NULL, 0 parameters!
+ *
+ * Memory allocation methods and lifetime of data will vary per module and
+ * SSL library used. The caller requesting OCSP data will need to make a copy
+ * for his own use.
+ * Any passed data may only be valid for the duration of the call.
+ */
+typedef void ap_ssl_ocsp_copy_resp(const unsigned char *der, apr_size_t der_len, void *userdata);
+
+/**
+ * Asking for OCSP response DER data for a certificate formerly primed.
+ * @param s the (SNI selected) server of the connection
+ * @param c the connection
+ * @param id identifier for the certifate, as used in ocsp_stapling_prime()
+ * @param cb callback to invoke when response data is available
+ * @param userdata caller supplied data passed to callback
+ * @return OK iff response data has been provided, DECLINED otherwise
+ */
+AP_DECLARE_HOOK(int, ssl_ocsp_get_resp_hook,
+ (server_rec *s, conn_rec *c, const ap_bytes_t *id,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata))
+
+/**
+ * Retrieve the OCSP response data for a previously primed certificate. The id needs
+ * to be byte-wise identical to the one used on priming. If the call return ARP_SUCCESS,
+ * the callback has been invoked with the OCSP response DER data.
+ * Otherwise, a different status code must be returned. Callers in SSL connection
+ * handshakes are encouraged to continue the handshake without OCSP data for
+ * server reliability. The decision to accept or reject a handshake with missing
+ * OCSP stapling data needs to be done by the client.
+ * For similar reasons, providers of responses might return seemingly expired ones
+ * if they were unable to refresh a response in time.
+ *
+ * The memory pointed to by `id` is only valid for the duration of the call.
+ * Also, the DER data passed to the callback is only valid for the duration
+ * of the call.
+ *
+ * @param s the (SNI selected) server of the connection
+ * @param c the connection
+ * @param id identifier for the certifate, as used in ocsp_stapling_prime()
+ * @param cb callback to invoke when response data is available
+ * @param userdata caller supplied data passed to callback
+ * @return APR_SUCCESS iff data has been provided
+ */
+AP_DECLARE(apr_status_t) ap_ssl_ocsp_get_resp(server_rec *s, conn_rec *c,
+ const ap_bytes_t *id,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata);
#ifdef __cplusplus
}
diff --git a/include/httpd.h b/include/httpd.h
index 5e4c036d8a..397c80b290 100644
--- a/include/httpd.h
+++ b/include/httpd.h
@@ -830,6 +830,8 @@ typedef struct conn_slave_rec conn_slave_rec;
typedef struct request_rec request_rec;
/** A structure that represents the status of the current connection */
typedef struct conn_state_t conn_state_t;
+/** A structure that represents a number of bytes */
+typedef struct ap_bytes_t ap_bytes_t;
/* ### would be nice to not include this from httpd.h ... */
/* This comes after we have defined the request_rec type */
@@ -1483,6 +1485,15 @@ struct ap_loadavg_t {
};
/**
+ * @struct ap_bytes_t
+ * @brief A structure to hold a number of bytes
+ */
+struct ap_bytes_t {
+ unsigned char *data;
+ apr_size_t len;
+};
+
+/**
* Get the context_document_root for a request. This is a generalization of
* the document root, which is too limited in the presence of mappers like
* mod_userdir and mod_alias. The context_document_root is the directory
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.
*/
diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c
index d6448957f6..df25c49925 100644
--- a/modules/ssl/ssl_util_ssl.c
+++ b/modules/ssl/ssl_util_ssl.c
@@ -578,3 +578,26 @@ cleanup:
if (in != NULL) BIO_free(in);
return rv;
}
+
+apr_status_t modssl_cert_get_pem(apr_pool_t *p,
+ X509 *cert1, X509 *cert2,
+ const char **ppem)
+{
+ apr_status_t rv = APR_ENOMEM;
+ BIO *bio;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL) goto cleanup;
+ if (PEM_write_bio_X509(bio, cert1) != 1) goto cleanup;
+ if (cert2 && PEM_write_bio_X509(bio, cert2) != 1) goto cleanup;
+ rv = APR_SUCCESS;
+
+cleanup:
+ if (rv != APR_SUCCESS) {
+ *ppem = NULL;
+ if (bio) BIO_free(bio);
+ }
+ else {
+ *ppem = modssl_bio_free_read(p, bio);
+ }
+ return rv;
+}
diff --git a/modules/ssl/ssl_util_ssl.h b/modules/ssl/ssl_util_ssl.h
index 335d6fdde7..0f01128528 100644
--- a/modules/ssl/ssl_util_ssl.h
+++ b/modules/ssl/ssl_util_ssl.h
@@ -90,7 +90,18 @@ apr_status_t modssl_read_cert(apr_pool_t *p,
const char *cert_pem, const char *key_pem,
pem_password_cb *cb, void *ud,
X509 **pcert, EVP_PKEY **pkey);
-
+
+/* Convert a certificate (and optionally a second) into a PEM string.
+ * @param p pool for allocations
+ * @param cert1 the certificate to convert
+ * @param cert2 a second cert to add to the PEM afterwards or NULL.
+ * @param ppem the certificate(s) in PEM format, NUL-terminated.
+ * @return APR_SUCCESS if ppem is valid.
+ */
+apr_status_t modssl_cert_get_pem(apr_pool_t *p,
+ X509 *cert1, X509 *cert2,
+ const char **ppem);
+
#endif /* __SSL_UTIL_SSL_H__ */
/** @} */
diff --git a/modules/ssl/ssl_util_stapling.c b/modules/ssl/ssl_util_stapling.c
index a6ee6e8e6c..54fb4e0415 100644
--- a/modules/ssl/ssl_util_stapling.c
+++ b/modules/ssl/ssl_util_stapling.c
@@ -130,6 +130,8 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp,
X509 *issuer = NULL;
OCSP_CERTID *cid = NULL;
STACK_OF(OPENSSL_STRING) *aia = NULL;
+ const char *pem = NULL;
+ ap_bytes_t key;
int rv = 1; /* until further notice */
if (x == NULL)
@@ -149,7 +151,20 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp,
return 1;
}
- if (ssl_run_init_stapling_status(s, p, x, issuer) == OK) {
+ if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) {
+ rv = 0;
+ goto cleanup;
+ }
+
+ if (modssl_cert_get_pem(ptemp, x, issuer, &pem) != APR_SUCCESS) {
+ rv = 0;
+ goto cleanup;
+ }
+
+ key.data = idx;
+ key.len = sizeof(idx);
+ if (ap_ssl_ocsp_prime(s, p, &key, pem) == APR_SUCCESS
+ || ssl_run_init_stapling_status(s, p, x, issuer) == OK) {
/* Someone's taken over or mod_ssl's own implementation is not enabled */
if (mctx->stapling_enabled != TRUE) {
SSL_CTX_set_tlsext_status_cb(mctx->ssl_ctx, stapling_cb);
@@ -163,11 +178,6 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp,
goto cleanup;
}
- if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) {
- rv = 0;
- goto cleanup;
- }
-
cinf = apr_hash_get(stapling_certinfo, idx, sizeof(idx));
if (cinf) {
/*
@@ -228,14 +238,11 @@ cleanup:
return rv;
}
-static certinfo *stapling_get_certinfo(server_rec *s, X509 *x, modssl_ctx_t *mctx,
- SSL *ssl)
+static certinfo *stapling_get_certinfo(server_rec *s, UCHAR *idx, apr_size_t idx_len,
+ modssl_ctx_t *mctx, SSL *ssl)
{
certinfo *cinf;
- UCHAR idx[SHA_DIGEST_LENGTH];
- if (X509_digest(x, EVP_sha1(), idx, NULL) != 1)
- return NULL;
- cinf = apr_hash_get(stapling_certinfo, idx, sizeof(idx));
+ cinf = apr_hash_get(stapling_certinfo, idx, idx_len);
if (cinf && cinf->cid)
return cinf;
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01926)
@@ -765,6 +772,18 @@ static int get_and_check_cached_response(server_rec *s, modssl_ctx_t *mctx,
return 0;
}
+static void copy_ocsp_resp(const unsigned char *der, apr_size_t der_len, void *userdata)
+{
+ ap_bytes_t *resp = userdata;
+
+ resp->len = 0;
+ resp->data = der? OPENSSL_malloc(der_len) : NULL;
+ if (resp->data) {
+ memcpy(resp->data, der, der_len);
+ resp->len = der_len;
+ }
+}
+
/* Certificate Status callback. This is called when a client includes a
* certificate status request extension.
*
@@ -779,13 +798,14 @@ static int stapling_cb(SSL *ssl, void *arg)
SSLSrvConfigRec *sc = mySrvConfig(s);
SSLConnRec *sslconn = myConnConfig(conn);
modssl_ctx_t *mctx = myCtxConfig(sslconn, sc);
+ UCHAR idx[SHA_DIGEST_LENGTH];
+ ap_bytes_t key, resp;
certinfo *cinf = NULL;
OCSP_RESPONSE *rsp = NULL;
int rv;
BOOL ok = TRUE;
X509 *x;
- unsigned char *rspder = NULL;
- int rspderlen;
+ int rspderlen, provided = 0;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01951)
"stapling_cb: OCSP Stapling callback called");
@@ -795,12 +815,26 @@ static int stapling_cb(SSL *ssl, void *arg)
return SSL_TLSEXT_ERR_NOACK;
}
- if (ssl_run_get_stapling_status(&rspder, &rspderlen, conn, s, x) == APR_SUCCESS) {
+ if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ key.data = idx;
+ key.len = sizeof(idx);
+
+ if (ap_ssl_ocsp_get_resp(s, conn, &key, copy_ocsp_resp, &resp) == APR_SUCCESS) {
+ provided = 1;
+ }
+ else if (ssl_run_get_stapling_status(&resp.data, &rspderlen, conn, s, x) == APR_SUCCESS) {
+ resp.len = (apr_size_t)rspderlen;
+ provided = 1;
+ }
+
+ if (provided) {
/* a hook handles stapling for this certificate and determines the response */
- if (rspder == NULL || rspderlen <= 0) {
+ if (resp.data == NULL || resp.len == 0) {
return SSL_TLSEXT_ERR_NOACK;
}
- SSL_set_tlsext_status_ocsp_resp(ssl, rspder, rspderlen);
+ SSL_set_tlsext_status_ocsp_resp(ssl, resp.data, (int)resp.len);
return SSL_TLSEXT_ERR_OK;
}
@@ -810,7 +844,7 @@ static int stapling_cb(SSL *ssl, void *arg)
return SSL_TLSEXT_ERR_NOACK;
}
- if ((cinf = stapling_get_certinfo(s, x, mctx, ssl)) == NULL) {
+ if ((cinf = stapling_get_certinfo(s, idx, sizeof(idx), mctx, ssl)) == NULL) {
return SSL_TLSEXT_ERR_NOACK;
}
diff --git a/server/ssl.c b/server/ssl.c
index eddd5303dc..65112ca9da 100644
--- a/server/ssl.c
+++ b/server/ssl.c
@@ -56,6 +56,8 @@ APR_HOOK_STRUCT(
APR_HOOK_LINK(ssl_add_cert_files)
APR_HOOK_LINK(ssl_add_fallback_cert_files)
APR_HOOK_LINK(ssl_answer_challenge)
+ APR_HOOK_LINK(ssl_ocsp_prime_hook)
+ APR_HOOK_LINK(ssl_ocsp_get_resp_hook)
)
APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
@@ -145,6 +147,22 @@ AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name,
return (ap_run_ssl_answer_challenge(c, server_name, pcert_pem, pkey_pem) == OK);
}
+AP_DECLARE(apr_status_t) ap_ssl_ocsp_prime(server_rec *s, apr_pool_t *p,
+ const ap_bytes_t *id,
+ const char *pem)
+{
+ int rv = ap_run_ssl_ocsp_prime_hook(s, p, id, pem);
+ return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL);
+}
+
+AP_DECLARE(apr_status_t) ap_ssl_ocsp_get_resp(server_rec *s, conn_rec *c,
+ const ap_bytes_t *id,
+ ap_ssl_ocsp_copy_resp *cb, void *userdata)
+{
+ int rv = ap_run_ssl_ocsp_get_resp_hook(s, c, id, cb, userdata);
+ return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL);
+}
+
AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_conn_is_ssl,
(conn_rec *c), (c), DECLINED)
AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,ssl_var_lookup,
@@ -161,4 +179,9 @@ AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_add_fallback_cert_files,
AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_answer_challenge,
(conn_rec *c, const char *server_name, const char **pcert_pem, const char **pkey_pem),
(c, server_name, pcert_pem, pkey_pem), DECLINED)
-
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_prime_hook,
+ (server_rec *s, apr_pool_t *p, const ap_bytes_t *id, const char *pem),
+ (s, p, id, pem), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_get_resp_hook,
+ (server_rec *s, conn_rec *c, const ap_bytes_t *id, ap_ssl_ocsp_copy_resp *cb, void *userdata),
+ (s, c, id, cb, userdata), DECLINED)