*) 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
diff --git a/changes-entries/ocsp_stapling_core.txt b/changes-entries/ocsp_stapling_core.txt
new file mode 100644
index 0000000..4830b23
--- /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 d1435ff..c1a0c9c 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 90d672e..d238439 100644
--- a/include/http_ssl.h
+++ b/include/http_ssl.h
@@ -190,6 +190,86 @@
  */
 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 5e4c036..397c80b 100644
--- a/include/httpd.h
+++ b/include/httpd.h
@@ -830,6 +830,8 @@
 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_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 bc5eb2b..4bdaf6b 100644
--- a/modules/md/md_acme_drive.c
+++ b/modules/md/md_acme_drive.c
@@ -201,6 +201,8 @@
     
     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 4c97f43..5c4d9f0 100644
--- a/modules/md/md_crypt.c
+++ b/modules/md/md_crypt.c
@@ -1359,22 +1359,44 @@
 {
     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 @@
 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 4706207..cd1db29 100644
--- a/modules/md/md_crypt.h
+++ b/modules/md/md_crypt.h
@@ -154,6 +154,12 @@
                                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 ea9366b..c6301a0 100644
--- a/modules/md/md_ocsp.c
+++ b/modules/md/md_ocsp.c
@@ -59,7 +59,8 @@
     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 @@
     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 @@
     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 @@
         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 @@
     ostat->next_run = md_timeperiod_slice_before_end(
         &ostat->resp_valid, &ostat->reg->renew_window).start;
     
-leave:
+cleanup:
     return rv;
 }
 
@@ -213,12 +221,12 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
         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 @@
         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 @@
     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 @@
         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 @@
             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 @@
                               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 @@
     
     (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 @@
 
 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 @@
                               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 @@
         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 @@
             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 @@
         }
         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 @@
         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 @@
     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 @@
                      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 @@
         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 @@
              
             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 @@
             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 @@
     
     /* 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 @@
     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 @@
     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 61c387e..7f2e356 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 @@
                               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 ac3ff6f..2263fdb 100644
--- a/modules/md/mod_md.c
+++ b/modules/md/mod_md.c
@@ -1497,10 +1497,16 @@
     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 2a01d5a..0800650 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 @@
 }
 
 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 @@
 
     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 @@
     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 @@
           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 ee58df6..48f0db3 100644
--- a/modules/md/mod_md_ocsp.h
+++ b/modules/md/mod_md_ocsp.h
@@ -24,6 +24,12 @@
 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 d644895..df25c49 100644
--- a/modules/ssl/ssl_util_ssl.c
+++ b/modules/ssl/ssl_util_ssl.c
@@ -578,3 +578,26 @@
     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 335d6fd..0f01128 100644
--- a/modules/ssl/ssl_util_ssl.h
+++ b/modules/ssl/ssl_util_ssl.h
@@ -90,7 +90,18 @@
                               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 a6ee6e8..54fb4e0 100644
--- a/modules/ssl/ssl_util_stapling.c
+++ b/modules/ssl/ssl_util_stapling.c
@@ -130,6 +130,8 @@
     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 @@
         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 @@
         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 @@
     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 @@
     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 @@
     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 @@
         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 @@
         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 eddd530..65112ca 100644
--- a/server/ssl.c
+++ b/server/ssl.c
@@ -56,6 +56,8 @@
     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 @@
     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_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)