]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
*) core/mod_ssl/mod_md: adding OCSP response provisioning as core feature. This
authorStefan Eissing <icing@apache.org>
Tue, 13 Apr 2021 11:12:00 +0000 (11:12 +0000)
committerStefan Eissing <icing@apache.org>
Tue, 13 Apr 2021 11:12:00 +0000 (11:12 +0000)
     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

16 files changed:
changes-entries/ocsp_stapling_core.txt [new file with mode: 0644]
include/ap_mmn.h
include/http_ssl.h
include/httpd.h
modules/md/md_acme_drive.c
modules/md/md_crypt.c
modules/md/md_crypt.h
modules/md/md_ocsp.c
modules/md/md_ocsp.h
modules/md/mod_md.c
modules/md/mod_md_ocsp.c
modules/md/mod_md_ocsp.h
modules/ssl/ssl_util_ssl.c
modules/ssl/ssl_util_ssl.h
modules/ssl/ssl_util_stapling.c
server/ssl.c

diff --git a/changes-entries/ocsp_stapling_core.txt b/changes-entries/ocsp_stapling_core.txt
new file mode 100644 (file)
index 0000000..4830b23
--- /dev/null
@@ -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]
index d1435ff9a7a4d08a59f9ef6018103d958d04a3e1..c1a0c9c8d551fbad8954e92c711e74e58349850e 100644 (file)
  * 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" */
 #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
index 90d672eda2f7292678184deb93275ef730deab01..d238439e9a0e717a412bd0d1b0e98b428e058203 100644 (file)
@@ -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
 }
index 5e4c036d8a6cd76f5242912184eeb93ca0adbf55..397c80b2906270688bb77e06b57760b4e7d3f6b2 100644 (file)
@@ -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 */
@@ -1482,6 +1484,15 @@ struct ap_loadavg_t {
     float loadavg15;
 };
 
+/**
+ * @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
index bc5eb2b2a088dcb5f990f001949cbf9d3eb5b8bd..4bdaf6bf65243c42421a5466cc22ad85a918d9a1 100644 (file)
@@ -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; 
index 4c97f4375ba44df7d4c48f13f4c185d3d0652c27..5c4d9f047e13b5a5a6b93fcd47b59c9d2f6d66ad 100644 (file)
@@ -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;
 }
 
index 4706207da9f9db86737fe3376cc33dab29ca40f2..cd1db294419f27174e8a6c7c9d2e6e081fe4c862 100644 (file)
@@ -153,6 +153,12 @@ apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p,
 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
index ea9366b84a0bfdc29f06c203e820c701cba13456..c6301a0f0ce360bad441cda015e2bf862981f089 100644 (file)
@@ -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) {
index 61c387ec1e79af8109336cfe25ccab4b571eedb8..7f2e356e50610123fadb27788df33bc6a9f81f43 100644 (file)
@@ -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,
index ac3ff6fb35d30c13766a927bd706e3200a88e5f0..2263fdb29297db5558293e26b0c4148606180190 100644 (file)
@@ -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() */
 }
 
index 2a01d5a846729dfa572433aa1d985278c460d52d..080065044309d63bb0b88f8a943f283927da8c74 100644 (file)
@@ -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. */
 
index ee58df678a7a746b412eb8cef81eb55c04060ff0..48f0db34aa591d4c253a62663e5f6292b498a7c5 100644 (file)
@@ -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.
  */
index d6448957f691151c85377a1c616c2b4a3416a51a..df25c4992561f43f6aa26eedb724c2eee70310ac 100644 (file)
@@ -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;
+}
index 335d6fdde716cdd335e59af6e3241be79cf8dc0b..0f01128528a7b18221771f2b9fdb7050d0877beb 100644 (file)
@@ -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__ */
 /** @} */
 
index a6ee6e8e6c3871a3a953bbb9526369dc5316ded8..54fb4e0415656a110e052d12b60b78851f28d378 100644 (file)
@@ -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;
     }
 
index eddd5303dc415a18b32589c0efe7cabd3294161d..65112ca9da35ea8f626c561899dd1b8aa4d56046 100644 (file)
@@ -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)