diff options
-rw-r--r-- | changes-entries/ocsp_stapling_core.txt | 20 | ||||
-rw-r--r-- | include/ap_mmn.h | 4 | ||||
-rw-r--r-- | include/http_ssl.h | 80 | ||||
-rw-r--r-- | include/httpd.h | 11 | ||||
-rw-r--r-- | modules/md/md_acme_drive.c | 2 | ||||
-rw-r--r-- | modules/md/md_crypt.c | 94 | ||||
-rw-r--r-- | modules/md/md_crypt.h | 6 | ||||
-rw-r--r-- | modules/md/md_ocsp.c | 209 | ||||
-rw-r--r-- | modules/md/md_ocsp.h | 13 | ||||
-rw-r--r-- | modules/md/mod_md.c | 6 | ||||
-rw-r--r-- | modules/md/mod_md_ocsp.c | 116 | ||||
-rw-r--r-- | modules/md/mod_md_ocsp.h | 6 | ||||
-rw-r--r-- | modules/ssl/ssl_util_ssl.c | 23 | ||||
-rw-r--r-- | modules/ssl/ssl_util_ssl.h | 13 | ||||
-rw-r--r-- | modules/ssl/ssl_util_stapling.c | 70 | ||||
-rw-r--r-- | server/ssl.c | 25 |
16 files changed, 515 insertions, 183 deletions
diff --git a/changes-entries/ocsp_stapling_core.txt b/changes-entries/ocsp_stapling_core.txt new file mode 100644 index 0000000000..4830b23d6a --- /dev/null +++ b/changes-entries/ocsp_stapling_core.txt @@ -0,0 +1,20 @@ + *) core/mod_ssl/mod_md: adding OCSP response provisioning as core feature. This + allows modules to access and provide OCSP response data without being tied + of each other. The data is exchanged in standard, portable formats (PEM encoded + certificates and DER encoded responses), so that the actual SSL/crypto + implementations used by the modules are independant of each other. + Registration and retrieval happen in the context of a server (server_rec) + which modules may use to decide if they are configured for this or not. + The area of changes: + 1. core: defines 2 functions in include/http_ssl.h, so that modules may + register a certificate, together with its issuer certificate for OCSP + response provisioning and ask for current response data (DER bytes) later. + Also, 2 hooks are defined that allow modules to implement this OCSP + provisioning. + 2. mod_ssl uses the new functions, in addition to what it did already, to + register its certificates this way. If no one is interested in providing + OCSP, it falls back to its own (if configured) stapling implementation. + 3. mod_md registers itself at the core hooks for OCSP provisioning. Depending + on configuration, it will accept registrations of its own certificates only, + all certficates or none. + [Stefan Eissing] diff --git a/include/ap_mmn.h b/include/ap_mmn.h index d1435ff9a7..c1a0c9c8d5 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -667,6 +667,8 @@ * 20201214.1 (2.5.1-dev) Add ap_ssl_conn_is_ssl()/ap_ssl_var_lookup() and hooks * 20201214.2 (2.5.1-dev) Add ap_ssl_add_cert_files, ap_ssl_add_fallback_cert_files * 20201214.3 (2.5.1-dev) Move ap_ssl_* into new http_ssl.h header file + * 20201214.4 (2.5.1-dev) Add `ap_bytes_t` to httpd.h. + * Add ap_ssl_ocsp* hooks and functions to http_ssl.h. */ #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */ @@ -674,7 +676,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20201214 #endif -#define MODULE_MAGIC_NUMBER_MINOR 3 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 4 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/http_ssl.h b/include/http_ssl.h index 90d672eda2..d238439e9a 100644 --- a/include/http_ssl.h +++ b/include/http_ssl.h @@ -190,6 +190,86 @@ AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name, */ AP_DECLARE(void) ap_setup_ssl_optional_fns(apr_pool_t *pool); +/** + * Providers of OCSP status responses register at this hook. Installed hooks returning OK + * are expected to provide later OCSP responses via a 'ap_ssl_ocsp_get_resp_hook'. + * @param s the server being configured + * @params p a memory pool to use + * @param id opaque data uniquely identifying the certificate, provided by caller + * @param pem PEM data of certificate first, followed by PEM of issuer cert + * @return OK iff stapling is being provided + */ +AP_DECLARE_HOOK(int, ssl_ocsp_prime_hook, (server_rec *s, apr_pool_t *p, + const ap_bytes_t *id, const char *pem)) + +/** + * Registering a certificate for Provisioning of OCSP responses. It is the caller's + * responsibility to provide a global (apache instance) unique id for the certificate + * that is then used later in retrieving the OCSP response. + * A certificate can be primed this way more than once, however the same identifier + * has to be provided each time (byte-wise same, not pointer same). + * The memory pointed to by `id` and `pem` is only valid for the duration of the call. + * + * @param s the server being configured + * @params p a memory pool to use + * @param id opaque data uniquely identifying the certificate, provided by caller + * @param pem PEM data of certificate first, followed by chain certs, at least the issuer + * @return APR_SUCCESS iff OCSP responses will be provided. + * APR_ENOENT when no provided was found or took responsibility. + */ +AP_DECLARE(apr_status_t) ap_ssl_ocsp_prime(server_rec *s, apr_pool_t *p, + const ap_bytes_t *id, + const char *pem); + +/** + * Callback to copy over the OCSP response data. If OCSP response data is not + * available, this will be called with NULL, 0 parameters! + * + * Memory allocation methods and lifetime of data will vary per module and + * SSL library used. The caller requesting OCSP data will need to make a copy + * for his own use. + * Any passed data may only be valid for the duration of the call. + */ +typedef void ap_ssl_ocsp_copy_resp(const unsigned char *der, apr_size_t der_len, void *userdata); + +/** + * Asking for OCSP response DER data for a certificate formerly primed. + * @param s the (SNI selected) server of the connection + * @param c the connection + * @param id identifier for the certifate, as used in ocsp_stapling_prime() + * @param cb callback to invoke when response data is available + * @param userdata caller supplied data passed to callback + * @return OK iff response data has been provided, DECLINED otherwise + */ +AP_DECLARE_HOOK(int, ssl_ocsp_get_resp_hook, + (server_rec *s, conn_rec *c, const ap_bytes_t *id, + ap_ssl_ocsp_copy_resp *cb, void *userdata)) + +/** + * Retrieve the OCSP response data for a previously primed certificate. The id needs + * to be byte-wise identical to the one used on priming. If the call return ARP_SUCCESS, + * the callback has been invoked with the OCSP response DER data. + * Otherwise, a different status code must be returned. Callers in SSL connection + * handshakes are encouraged to continue the handshake without OCSP data for + * server reliability. The decision to accept or reject a handshake with missing + * OCSP stapling data needs to be done by the client. + * For similar reasons, providers of responses might return seemingly expired ones + * if they were unable to refresh a response in time. + * + * The memory pointed to by `id` is only valid for the duration of the call. + * Also, the DER data passed to the callback is only valid for the duration + * of the call. + * + * @param s the (SNI selected) server of the connection + * @param c the connection + * @param id identifier for the certifate, as used in ocsp_stapling_prime() + * @param cb callback to invoke when response data is available + * @param userdata caller supplied data passed to callback + * @return APR_SUCCESS iff data has been provided + */ +AP_DECLARE(apr_status_t) ap_ssl_ocsp_get_resp(server_rec *s, conn_rec *c, + const ap_bytes_t *id, + ap_ssl_ocsp_copy_resp *cb, void *userdata); #ifdef __cplusplus } diff --git a/include/httpd.h b/include/httpd.h index 5e4c036d8a..397c80b290 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -830,6 +830,8 @@ typedef struct conn_slave_rec conn_slave_rec; typedef struct request_rec request_rec; /** A structure that represents the status of the current connection */ typedef struct conn_state_t conn_state_t; +/** A structure that represents a number of bytes */ +typedef struct ap_bytes_t ap_bytes_t; /* ### would be nice to not include this from httpd.h ... */ /* This comes after we have defined the request_rec type */ @@ -1483,6 +1485,15 @@ struct ap_loadavg_t { }; /** + * @struct ap_bytes_t + * @brief A structure to hold a number of bytes + */ +struct ap_bytes_t { + unsigned char *data; + apr_size_t len; +}; + +/** * Get the context_document_root for a request. This is a generalization of * the document root, which is too limited in the presence of mappers like * mod_userdir and mod_alias. The context_document_root is the directory diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c index bc5eb2b2a0..4bdaf6bf65 100644 --- a/modules/md/md_acme_drive.c +++ b/modules/md/md_acme_drive.c @@ -201,6 +201,8 @@ static apr_status_t add_http_certs(apr_array_header_t *chain, apr_pool_t *p, ct = apr_table_get(res->headers, "Content-Type"); ct = md_util_parse_ct(res->req->pool, ct); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, p, + "parse certs from %s -> %d (%s)", res->req->url, res->status, ct); if (ct && !strcmp("application/x-pkcs7-mime", ct)) { /* this looks like a root cert and we do not want those in our chain */ goto out; diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c index 4c97f4375b..5c4d9f047e 100644 --- a/modules/md/md_crypt.c +++ b/modules/md/md_crypt.c @@ -1359,22 +1359,44 @@ static int md_cert_read_pem(BIO *bf, apr_pool_t *p, md_cert_t **pcert) { md_cert_t *cert; X509 *x509; - apr_status_t rv; + apr_status_t rv = APR_ENOENT; ERR_clear_error(); x509 = PEM_read_bio_X509(bf, NULL, NULL, NULL); - if (x509 == NULL) { - rv = APR_ENOENT; - goto out; - } + if (x509 == NULL) goto cleanup; cert = md_cert_make(p, x509); rv = APR_SUCCESS; - -out: +cleanup: *pcert = (APR_SUCCESS == rv)? cert : NULL; return rv; } +apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p, + const char *pem, apr_size_t pem_len) +{ + BIO *bf = NULL; + apr_status_t rv = APR_SUCCESS; + md_cert_t *cert; + int added = 0; + + if (NULL == (bf = BIO_new_mem_buf(pem, (int)pem_len))) { + rv = APR_ENOMEM; + goto cleanup; + } + while (APR_SUCCESS == (rv = md_cert_read_pem(bf, chain->pool, &cert))) { + APR_ARRAY_PUSH(chain, md_cert_t *) = cert; + added = 1; + } + if (APR_ENOENT == rv && added) { + rv = APR_SUCCESS; + } + +cleanup: + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "read chain with %d certs", chain->nelts); + if (bf) BIO_free(bf); + return rv; +} + apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p, const md_http_response_t *res) { @@ -1420,64 +1442,40 @@ out: apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain, apr_pool_t *p, const struct md_http_response_t *res) { - const char *ct; + const char *ct = NULL; apr_off_t blen; - apr_size_t data_len; + apr_size_t data_len = 0; char *data; - BIO *bf = NULL; - apr_status_t rv; + md_cert_t *cert; + apr_status_t rv = APR_ENOENT; - if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto out; + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, + "chain_read, processing %d response", res->status); + if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto cleanup; if (blen > 1024*1024) { /* certs usually are <2k each */ rv = APR_EINVAL; - goto out; + goto cleanup; } data_len = (apr_size_t)blen; ct = apr_table_get(res->headers, "Content-Type"); - if (!res->body || !ct) { - rv = APR_ENOENT; - goto out; - } + if (!res->body || !ct) goto cleanup; ct = md_util_parse_ct(res->req->pool, ct); if (!strcmp("application/pem-certificate-chain", ct) || !strncmp("text/plain", ct, sizeof("text/plain")-1)) { /* Some servers seem to think 'text/plain' is sufficient, see #232 */ - if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool))) { - int added = 0; - md_cert_t *cert; - - if (NULL == (bf = BIO_new_mem_buf(data, (int)data_len))) { - rv = APR_ENOMEM; - goto out; - } - - while (APR_SUCCESS == (rv = md_cert_read_pem(bf, p, &cert))) { - APR_ARRAY_PUSH(chain, md_cert_t *) = cert; - added = 1; - } - if (APR_ENOENT == rv && added) { - rv = APR_SUCCESS; - } - } - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, - "parsing cert from content-type=%s, content-length=%ld", ct, (long)data_len); + rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool); + if (APR_SUCCESS != rv) goto cleanup; + rv = md_cert_read_chain(chain, res->req->pool, data, data_len); } else if (!strcmp("application/pkix-cert", ct)) { - md_cert_t *cert; - rv = md_cert_read_http(&cert, p, res); - if (APR_SUCCESS == rv) { - APR_ARRAY_PUSH(chain, md_cert_t *) = cert; - } + if (APR_SUCCESS != rv) goto cleanup; + APR_ARRAY_PUSH(chain, md_cert_t *) = cert; } - else { - /* unrecongized content type */ - rv = APR_ENOENT; - goto out; - } -out: - if (bf) BIO_free(bf); +cleanup: + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, + "parsed certs from content-type=%s, content-length=%ld", ct, (long)data_len); return rv; } diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h index 4706207da9..cd1db29441 100644 --- a/modules/md/md_crypt.h +++ b/modules/md/md_crypt.h @@ -154,6 +154,12 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *pool, const struct md_http_response_t *res); /** + * Read at least one certificate from the given PEM data. + */ +apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p, + const char *pem, apr_size_t pem_len); + +/** * Read one or even a chain of certificates from a http response. * Will return APR_ENOENT if content-type is not recognized (currently * supports only "application/pem-certificate-chain" and "application/pkix-cert"). diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c index ea9366b84a..c6301a0f0c 100644 --- a/modules/md/md_ocsp.c +++ b/modules/md/md_ocsp.c @@ -59,7 +59,8 @@ struct md_ocsp_reg_t { md_store_t *store; const char *user_agent; const char *proxy_url; - apr_hash_t *hash; + apr_hash_t *id_by_external_id; + apr_hash_t *ostat_by_id; apr_thread_mutex_t *mutex; md_timeslice_t renew_window; md_job_notify_cb *notify; @@ -92,6 +93,12 @@ struct md_ocsp_status_t { apr_time_t resp_last_check; }; +typedef struct md_ocsp_id_map_t md_ocsp_id_map_t; +struct md_ocsp_id_map_t { + md_data_t id; + md_data_t external_id; +}; + const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat) { switch (stat) { @@ -108,16 +115,17 @@ md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name) return MD_OCSP_CERT_ST_UNKNOWN; } -static apr_status_t init_cert_id(md_data_t *data, const md_cert_t *cert) +apr_status_t md_ocsp_init_id(md_data_t *id, apr_pool_t *p, const md_cert_t *cert) { + unsigned char iddata[SHA_DIGEST_LENGTH]; X509 *x = md_cert_get_X509(cert); unsigned int ulen = 0; - assert(data->len == SHA_DIGEST_LENGTH); - if (X509_digest(x, EVP_sha1(), (unsigned char*)data->data, &ulen) != 1) { + if (X509_digest(x, EVP_sha1(), iddata, &ulen) != 1) { return APR_EGENERAL; } - data->len = ulen; + id->len = ulen; + id->data = apr_pmemdup(p, iddata, id->len); return APR_SUCCESS; } @@ -173,7 +181,7 @@ static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat, s = OPENSSL_malloc(der->len); if (!s) { rv = APR_ENOMEM; - goto leave; + goto cleanup; } memcpy((char*)s, der->data, der->len); } @@ -194,7 +202,7 @@ static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat, ostat->next_run = md_timeperiod_slice_before_end( &ostat->resp_valid, &ostat->reg->renew_window).start; -leave: +cleanup: return rv; } @@ -213,12 +221,12 @@ static apr_status_t ostat_from_json(md_ocsp_cert_stat_t *pstat, s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_UNTIL, NULL); if (s && *s) valid.end = apr_date_parse_rfc(s); s = md_json_dups(p, json, MD_KEY_RESPONSE, NULL); - if (!s || !*s) goto leave; + if (!s || !*s) goto cleanup; md_util_base64url_decode(resp_der, s, p); *pstat = md_ocsp_cert_stat_value(md_json_gets(json, MD_KEY_STATUS, NULL)); *resp_valid = valid; rv = APR_SUCCESS; -leave: +cleanup: return rv; } @@ -247,14 +255,14 @@ static apr_status_t ocsp_status_refresh(md_ocsp_status_t *ostat, apr_pool_t *pte md_ocsp_cert_stat_t resp_stat; /* Check if the store holds a newer response than the one we have */ mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp); - if (mtime <= ostat->resp_mtime) goto leave; + if (mtime <= ostat->resp_mtime) goto cleanup; rv = md_store_load_json(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, &jprops, ptemp); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; rv = ostat_from_json(&resp_stat, &resp_der, &resp_valid, jprops, ptemp); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; rv = ostat_set(ostat, resp_stat, &resp_der, &resp_valid, mtime); - if (APR_SUCCESS != rv) goto leave; -leave: + if (APR_SUCCESS != rv) goto cleanup; +cleanup: return rv; } @@ -271,10 +279,10 @@ static apr_status_t ocsp_status_save(md_ocsp_cert_stat_t stat, const md_data_t * jprops = md_json_create(ptemp); ostat_to_json(jprops, stat, resp_der, resp_valid, ptemp); rv = md_store_save_json(store, ptemp, MD_SG_OCSP, ostat->md_name, ostat->file_name, jprops, 0); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp); if (mtime) ostat->resp_mtime = mtime; -leave: +cleanup: return rv; } @@ -283,7 +291,7 @@ static apr_status_t ocsp_reg_cleanup(void *data) md_ocsp_reg_t *reg = data; /* free all OpenSSL structures that we hold */ - apr_hash_do(ostat_cleanup, reg, reg->hash); + apr_hash_do(ostat_cleanup, reg, reg->ostat_by_id); return APR_SUCCESS; } @@ -297,53 +305,53 @@ apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, md_store_t *s reg = apr_palloc(p, sizeof(*reg)); if (!reg) { rv = APR_ENOMEM; - goto leave; + goto cleanup; } reg->p = p; reg->store = store; reg->user_agent = user_agent; reg->proxy_url = proxy_url; - reg->hash = apr_hash_make(p); + reg->id_by_external_id = apr_hash_make(p); + reg->ostat_by_id = apr_hash_make(p); reg->renew_window = *renew_window; rv = apr_thread_mutex_create(®->mutex, APR_THREAD_MUTEX_NESTED, p); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; apr_pool_cleanup_register(p, reg, ocsp_reg_cleanup, apr_pool_cleanup_null); -leave: +cleanup: *preg = (APR_SUCCESS == rv)? reg : NULL; return rv; } -apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issuer, const md_t *md) +apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const md_data_t *external_id, + md_cert_t *cert, md_cert_t *issuer, const md_t *md) { - char iddata[MD_OCSP_ID_LENGTH]; md_ocsp_status_t *ostat; STACK_OF(OPENSSL_STRING) *ssk = NULL; const char *name, *s; md_data_t id; - apr_status_t rv; + apr_status_t rv = APR_SUCCESS; /* Called during post_config. no mutex protection needed */ name = md? md->name : MD_OTHER; - id.data = iddata; id.len = sizeof(iddata); - - md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p, + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p, "md[%s]: priming OCSP status", name); - rv = init_cert_id(&id, cert); - if (APR_SUCCESS != rv) goto leave; - - ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len); - if (ostat) goto leave; /* already seen it, cert is used in >1 server_rec */ - + + rv = md_ocsp_init_id(&id, reg->p, cert); + if (APR_SUCCESS != rv) goto cleanup; + + ostat = apr_hash_get(reg->ostat_by_id, id.data, (apr_ssize_t)id.len); + if (ostat) goto cleanup; /* already seen it, cert is used in >1 server_rec */ + ostat = apr_pcalloc(reg->p, sizeof(*ostat)); - md_data_assign_pcopy(&ostat->id, &id, reg->p); + ostat->id = id; ostat->reg = reg; ostat->md_name = name; md_data_to_hex(&ostat->hexid, 0, reg->p, &ostat->id); ostat->file_name = apr_psprintf(reg->p, "ocsp-%s.json", ostat->hexid); rv = md_cert_to_sha256_fingerprint(&ostat->hex_sha256, cert, reg->p); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, "md[%s]: getting ocsp responder from cert", name); @@ -353,7 +361,7 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p, "md[%s]: certificate with serial %s has not OCSP responder URL", name, md_cert_get_serial_number(cert, reg->p)); - goto leave; + goto cleanup; } s = sk_OPENSSL_STRING_value(ssk, 0); md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, @@ -367,7 +375,7 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p, "md[%s]: unable to create OCSP certid for certificate with serial %s", name, md_cert_get_serial_number(cert, reg->p)); - goto leave; + goto cleanup; } /* See, if we have something in store */ @@ -375,38 +383,46 @@ apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issue md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, reg->p, "md[%s]: adding ocsp info (responder=%s)", name, ostat->responder_url); - apr_hash_set(reg->hash, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat); + apr_hash_set(reg->ostat_by_id, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat); + if (external_id) { + md_ocsp_id_map_t *id_map; + + id_map = apr_pcalloc(reg->p, sizeof(*id_map)); + id_map->id = id; + md_data_assign_pcopy(&id_map->external_id, external_id, reg->p); + /* check for collision/uniqness? */ + apr_hash_set(reg->id_by_external_id, id_map->external_id.data, + (apr_ssize_t)id_map->external_id.len, id_map); + } rv = APR_SUCCESS; -leave: +cleanup: return rv; } -apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen, - md_ocsp_reg_t *reg, const md_cert_t *cert, +apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata, + md_ocsp_reg_t *reg, const md_data_t *external_id, apr_pool_t *p, const md_t *md) { - char iddata[MD_OCSP_ID_LENGTH]; md_ocsp_status_t *ostat; const char *name; - apr_status_t rv; + apr_status_t rv = APR_SUCCESS; + md_ocsp_id_map_t *id_map; + const md_data_t *id; int locked = 0; - md_data_t id; - + (void)p; (void)md; - id.data = iddata; id.len = sizeof(iddata); - *pder = NULL; - *pderlen = 0; name = md? md->name : MD_OTHER; md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, "md[%s]: OCSP, get_status", name); - rv = init_cert_id(&id, cert); - if (APR_SUCCESS != rv) goto leave; - - ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len); + + id_map = apr_hash_get(reg->id_by_external_id, + external_id->data, (apr_ssize_t)external_id->len); + id = id_map? &id_map->id : external_id; + ostat = apr_hash_get(reg->ostat_by_id, id->data, (apr_ssize_t)id->len); if (!ostat) { rv = APR_ENOENT; - goto leave; + goto cleanup; } /* While the ostat instance itself always exists, the response data it holds @@ -420,7 +436,8 @@ apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen, if (ostat->resp_der.len <= 0) { md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, "md[%s]: OCSP, no response available", name); - goto leave; + cb(NULL, 0, userdata); + goto cleanup; } } /* We have a response */ @@ -441,18 +458,12 @@ apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen, ocsp_status_refresh(ostat, p); } } - - *pder = OPENSSL_malloc(ostat->resp_der.len); - if (*pder == NULL) { - rv = APR_ENOMEM; - goto leave; - } - memcpy(*pder, ostat->resp_der.data, ostat->resp_der.len); - *pderlen = (int)ostat->resp_der.len; - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, - "md[%s]: OCSP, returning %ld bytes of response", + + cb((const unsigned char*)ostat->resp_der.data, ostat->resp_der.len, userdata); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, + "md[%s]: OCSP, provided %ld bytes of response", name, (long)ostat->resp_der.len); -leave: +cleanup: if (locked) apr_thread_mutex_unlock(reg->mutex); return rv; } @@ -475,7 +486,6 @@ apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvali md_ocsp_reg_t *reg, const md_cert_t *cert, apr_pool_t *p, const md_t *md) { - char iddata[MD_OCSP_ID_LENGTH]; md_ocsp_status_t *ostat; const char *name; apr_status_t rv; @@ -485,23 +495,22 @@ apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvali (void)p; (void)md; - id.data = iddata; id.len = sizeof(iddata); name = md? md->name : MD_OTHER; memset(&valid, 0, sizeof(valid)); stat = MD_OCSP_CERT_ST_UNKNOWN; md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, "md[%s]: OCSP, get_status", name); - rv = init_cert_id(&id, cert); - if (APR_SUCCESS != rv) goto leave; + rv = md_ocsp_init_id(&id, p, cert); + if (APR_SUCCESS != rv) goto cleanup; - ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len); + ostat = apr_hash_get(reg->ostat_by_id, id.data, (apr_ssize_t)id.len); if (!ostat) { rv = APR_ENOENT; - goto leave; + goto cleanup; } ocsp_get_meta(&stat, &valid, reg, ostat, p); -leave: +cleanup: *pstat = stat; *pvalid = valid; return rv; @@ -509,7 +518,7 @@ leave: apr_size_t md_ocsp_count(md_ocsp_reg_t *reg) { - return apr_hash_count(reg->hash); + return apr_hash_count(reg->ostat_by_id); } static const char *certid_as_hex(const OCSP_CERTID *certid, apr_pool_t *p) @@ -618,14 +627,14 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) ostat->hexid); if (APR_SUCCESS != (rv = apr_brigade_pflatten(resp->body, (char**)&der.data, &der.len, req->pool))) { - goto leave; + goto cleanup; } if (NULL == (ocsp_resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char**)&der.data, (long)der.len))) { rv = APR_EINVAL; md_result_set(update->result, rv, "response body does not parse as OCSP response"); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } /* got a response! but what does it say? */ n = OCSP_response_status(ocsp_resp); @@ -633,14 +642,14 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) rv = APR_EINVAL; md_result_printf(update->result, rv, "OCSP response status is, unsuccessfully, %d", n); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } basic_resp = OCSP_response_get1_basic(ocsp_resp); if (!basic_resp) { rv = APR_EINVAL; md_result_set(update->result, rv, "OCSP response has no basicresponse"); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } /* The notion of nonce enabled freshness in OCSP responses, e.g. that the response * contains the signed nonce we sent to the responder, does not scale well. Responders @@ -656,7 +665,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) rv = APR_EINVAL; md_result_printf(update->result, rv, "OCSP nonce mismatch in response", n); md_result_log(update->result, MD_LOG_WARNING); - goto leave; + goto cleanup; case -1: md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool, @@ -682,19 +691,19 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) } md_result_printf(update->result, rv, "%s, status list [%s]", prefix, slist); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } if (V_OCSP_CERTSTATUS_UNKNOWN == bstatus) { rv = APR_ENOENT; md_result_set(update->result, rv, "OCSP basicresponse says cert is unknown"); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } if (!bnextup) { rv = APR_EINVAL; md_result_set(update->result, rv, "OCSP basicresponse reports not valid dates"); md_result_log(update->result, MD_LOG_DEBUG); - goto leave; + goto cleanup; } /* Coming here, we have a response for our certid and it is either GOOD @@ -704,7 +713,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) rv = APR_EGENERAL; md_result_set(update->result, rv, "error DER encoding OCSP response"); md_result_log(update->result, MD_LOG_WARNING); - goto leave; + goto cleanup; } nstat = (bstatus == V_OCSP_CERTSTATUS_GOOD)? MD_OCSP_CERT_ST_GOOD : MD_OCSP_CERT_ST_REVOKED; new_der.len = (apr_size_t)n; @@ -721,7 +730,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) if (APR_SUCCESS != rv) { md_result_set(update->result, rv, "error saving OCSP status"); md_result_log(update->result, MD_LOG_ERR); - goto leave; + goto cleanup; } md_result_printf(update->result, rv, "certificate status is %s, status valid %s", @@ -729,7 +738,7 @@ static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton) md_timeperiod_print(req->pool, &ostat->resp_valid)); md_result_log(update->result, MD_LOG_DEBUG); -leave: +cleanup: if (new_der.data) OPENSSL_free((void*)new_der.data); if (basic_resp) OCSP_BASICRESP_free(basic_resp); if (ocsp_resp) OCSP_RESPONSE_free(ocsp_resp); @@ -753,11 +762,11 @@ static apr_status_t ostat_on_req_status(const md_http_request_t *req, apr_status md_job_log_append(update->job, "ocsp-error", update->result->problem, update->result->detail); md_event_holler("ocsp-errored", update->job->mdomain, update->job, update->result, update->p); - goto leave; + goto cleanup; } md_event_holler("ocsp-renewed", update->job->mdomain, update->job, update->result, update->p); -leave: +cleanup: md_job_save(update->job, update->result, update->p); ostat_req_cleanup(ostat); return APR_SUCCESS; @@ -795,16 +804,16 @@ static apr_status_t next_todo(md_http_request_t **preq, void *baton, if (!ostat->ocsp_req) { ostat->ocsp_req = OCSP_REQUEST_new(); - if (!ostat->ocsp_req) goto leave; + if (!ostat->ocsp_req) goto cleanup; certid = OCSP_CERTID_dup(ostat->certid); - if (!certid) goto leave; - if (!OCSP_request_add0_id(ostat->ocsp_req, certid)) goto leave; + if (!certid) goto cleanup; + if (!OCSP_request_add0_id(ostat->ocsp_req, certid)) goto cleanup; OCSP_request_add1_nonce(ostat->ocsp_req, 0, -1); certid = NULL; } if (0 == ostat->req_der.len) { len = i2d_OCSP_REQUEST(ostat->ocsp_req, (unsigned char**)&ostat->req_der.data); - if (len < 0) goto leave; + if (len < 0) goto cleanup; ostat->req_der.len = (apr_size_t)len; } md_result_activity_printf(update->result, "status of certid %s, " @@ -813,13 +822,13 @@ static apr_status_t next_todo(md_http_request_t **preq, void *baton, apr_table_set(headers, "Expect", ""); rv = md_http_POSTd_create(&req, http, ostat->responder_url, headers, "application/ocsp-request", &ostat->req_der); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; md_http_set_on_status_cb(req, ostat_on_req_status, update); md_http_set_on_response_cb(req, ostat_on_resp, update); rv = APR_SUCCESS; } } -leave: +cleanup: *preq = (APR_SUCCESS == rv)? req : NULL; if (certid) OCSP_CERTID_free(certid); return rv; @@ -873,21 +882,21 @@ void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_tim /* Create a list of update tasks that are needed now or in the next minute */ ctx.time = apr_time_now() + apr_time_from_sec(60);; - apr_hash_do(select_updates, &ctx, reg->hash); + apr_hash_do(select_updates, &ctx, reg->ostat_by_id); md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "OCSP status updates due: %d", ctx.todos->nelts); - if (!ctx.todos->nelts) goto leave; + if (!ctx.todos->nelts) goto cleanup; rv = md_http_create(&http, ptemp, reg->user_agent, reg->proxy_url); - if (APR_SUCCESS != rv) goto leave; + if (APR_SUCCESS != rv) goto cleanup; rv = md_http_multi_perform(http, next_todo, &ctx); -leave: +cleanup: /* When do we need to run next? *pnext_run contains the planned schedule from * the watchdog. We can make that earlier if we need it. */ ctx.time = *pnext_run; - apr_hash_do(select_next_run, &ctx, reg->hash); + apr_hash_do(select_next_run, &ctx, reg->ostat_by_id); /* sanity check and return */ if (ctx.time < apr_time_now()) ctx.time = apr_time_now() + apr_time_from_sec(1); @@ -940,7 +949,7 @@ void md_ocsp_get_summary(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p) memset(&ctx, 0, sizeof(ctx)); ctx.p = p; ctx.reg = reg; - apr_hash_do(add_to_summary, &ctx, reg->hash); + apr_hash_do(add_to_summary, &ctx, reg->ostat_by_id); json = md_json_create(p); md_json_setl(ctx.good+ctx.revoked+ctx.unknown, json, MD_KEY_TOTAL, NULL); @@ -1020,10 +1029,10 @@ void md_ocsp_get_status_all(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p memset(&ctx, 0, sizeof(ctx)); ctx.p = p; ctx.reg = reg; - ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->hash), sizeof(md_ocsp_status_t*)); + ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->ostat_by_id), sizeof(md_ocsp_status_t*)); json = md_json_create(p); - apr_hash_do(add_ostat, &ctx, reg->hash); + apr_hash_do(add_ostat, &ctx, reg->ostat_by_id); qsort(ctx.ostats->elts, (size_t)ctx.ostats->nelts, sizeof(md_json_t*), md_ostat_cmp); for (i = 0; i < ctx.ostats->nelts; ++i) { diff --git a/modules/md/md_ocsp.h b/modules/md/md_ocsp.h index 61c387ec1e..7f2e356e50 100644 --- a/modules/md/md_ocsp.h +++ b/modules/md/md_ocsp.h @@ -17,6 +17,7 @@ #ifndef md_ocsp_h #define md_ocsp_h +struct md_data_t; struct md_job_t; struct md_json_t; struct md_result_t; @@ -39,11 +40,15 @@ apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, const md_timeslice_t *renew_window, const char *user_agent, const char *proxy_url); -apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *x, - md_cert_t *issuer, const md_t *md); +apr_status_t md_ocsp_init_id(struct md_data_t *id, apr_pool_t *p, const md_cert_t *cert); -apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen, - md_ocsp_reg_t *reg, const md_cert_t *cert, +apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const struct md_data_t *external_id, + md_cert_t *x, md_cert_t *issuer, const md_t *md); + +typedef void md_ocsp_copy_der(const unsigned char *der, apr_size_t der_len, void *userdata); + +apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata, + md_ocsp_reg_t *reg, const struct md_data_t *external_id, apr_pool_t *p, const md_t *md); apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid, diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c index ac3ff6fb35..2263fdb292 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -1497,10 +1497,16 @@ static void md_hooks(apr_pool_t *pool) ap_hook_ssl_add_cert_files(md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_ssl_add_fallback_cert_files(md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE); +#if AP_MODULE_MAGIC_AT_LEAST(20201214, 4) + ap_hook_ssl_ocsp_prime_hook(md_ocsp_prime_status, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_ocsp_get_resp_hook(md_ocsp_provide_status, NULL, NULL, APR_HOOK_MIDDLE); +#else + #ifndef SSL_CERT_HOOKS #error "This version of mod_md requires Apache httpd 2.4.41 or newer." #endif APR_OPTIONAL_HOOK(ssl, init_stapling_status, md_ocsp_init_stapling_status, NULL, NULL, APR_HOOK_MIDDLE); APR_OPTIONAL_HOOK(ssl, get_stapling_status, md_ocsp_get_stapling_status, NULL, NULL, APR_HOOK_MIDDLE); +#endif /* AP_MODULE_MAGIC_AT_LEAST() */ } diff --git a/modules/md/mod_md_ocsp.c b/modules/md/mod_md_ocsp.c index 2a01d5a846..0800650443 100644 --- a/modules/md/mod_md_ocsp.c +++ b/modules/md/mod_md_ocsp.c @@ -23,6 +23,7 @@ #include <httpd.h> #include <http_core.h> #include <http_log.h> +#include <http_ssl.h> #include "mod_watchdog.h" @@ -53,7 +54,7 @@ static int staple_here(md_srv_conf_t *sc) } int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, - X509 *cert, X509 *issuer) + X509 *cert, X509 *issuer) { md_srv_conf_t *sc; const md_t *md; @@ -61,10 +62,10 @@ int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, sc = md_config_get(s); if (!staple_here(sc)) goto declined; - md = ((sc->assigned && sc->assigned->nelts == 1)? APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL); - rv = md_ocsp_prime(sc->mc->ocsp, md_cert_wrap(p, cert), + + rv = md_ocsp_prime(sc->mc->ocsp, NULL, md_cert_wrap(p, cert), md_cert_wrap(p, issuer), md); ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s", md? md->name : s->server_hostname); @@ -75,13 +76,75 @@ declined: return DECLINED; } -int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, - conn_rec *c, server_rec *s, X509 *cert) +int md_ocsp_prime_status(server_rec *s, apr_pool_t *p, + const ap_bytes_t *external_id, const char *pem) +{ + md_srv_conf_t *sc; + const md_t *md; + apr_array_header_t *chain; + apr_status_t rv = APR_ENOENT; + md_data_t eid; + + sc = md_config_get(s); + if (!staple_here(sc)) goto cleanup; + + md = ((sc->assigned && sc->assigned->nelts == 1)? + APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL); + chain = apr_array_make(p, 5, sizeof(md_cert_t*)); + rv = md_cert_read_chain(chain, p, pem, strlen(pem)); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO() "init stapling for: %s, " + "unable to parse PEM data", md? md->name : s->server_hostname); + goto cleanup; + } + else if (chain->nelts < 2) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO() "init stapling for: %s, " + "need at least 2 certificates in PEM data", md? md->name : s->server_hostname); + rv = APR_EINVAL; + goto cleanup; + } + + eid.data = (char*)external_id->data; + eid.len = external_id->len; + rv = md_ocsp_prime(sc->mc->ocsp, &eid, + APR_ARRAY_IDX(chain, 0, md_cert_t*), + APR_ARRAY_IDX(chain, 1, md_cert_t*), md); + ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s", + md? md->name : s->server_hostname); + +cleanup: + return (APR_SUCCESS == rv)? OK : DECLINED; +} + +typedef struct { + unsigned char *der; + apr_size_t der_len; +} ocsp_copy_ctx_t; + +static void ocsp_copy_der(const unsigned char *der, apr_size_t der_len, void *userdata) +{ + ocsp_copy_ctx_t *ctx = userdata; + + memset(ctx, 0, sizeof(*ctx)); + if (der && der_len > 0) { + ctx->der = OPENSSL_malloc(der_len); + if (ctx->der != NULL) { + ctx->der_len = der_len; + memcpy(ctx->der, der, der_len); + } + } +} + +int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, + conn_rec *c, server_rec *s, X509 *x) { md_srv_conf_t *sc; const md_t *md; + md_cert_t *cert; + md_data_t id; apr_status_t rv; - + ocsp_copy_ctx_t ctx; + sc = md_config_get(s); if (!staple_here(sc)) goto declined; @@ -89,15 +152,48 @@ int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s", md? md->name : s->server_hostname); - rv = md_ocsp_get_status(pder, pderlen, sc->mc->ocsp, - md_cert_wrap(c->pool, cert), c->pool, md); + cert = md_cert_wrap(c->pool, x); + rv = md_ocsp_init_id(&id, c->pool, cert); + if (APR_SUCCESS != rv) goto declined; + + rv = md_ocsp_get_status(ocsp_copy_der, &ctx, sc->mc->ocsp, &id, c->pool, md); if (APR_STATUS_IS_ENOENT(rv)) goto declined; - return rv; + *pder = ctx.der; + *pderlen = ctx.der_len; + return OK; declined: return DECLINED; } - + +int md_ocsp_provide_status(server_rec *s, conn_rec *c, + const ap_bytes_t *external_id, + ap_ssl_ocsp_copy_resp *cb, void *userdata) +{ + md_srv_conf_t *sc; + const md_t *md; + md_data_t eid; + apr_status_t rv; + + sc = md_config_get(s); + if (!staple_here(sc)) goto declined; + + md = ((sc->assigned && sc->assigned->nelts == 1)? + APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s", + md? md->name : s->server_hostname); + + eid.data = (const char *)external_id->data; + eid.len = external_id->len; + rv = md_ocsp_get_status(cb, userdata, sc->mc->ocsp, &eid, c->pool, md); + if (APR_STATUS_IS_ENOENT(rv)) goto declined; + return OK; + +declined: + return DECLINED; +} + + /**************************************************************************************************/ /* watchdog based impl. */ diff --git a/modules/md/mod_md_ocsp.h b/modules/md/mod_md_ocsp.h index ee58df678a..48f0db34aa 100644 --- a/modules/md/mod_md_ocsp.h +++ b/modules/md/mod_md_ocsp.h @@ -24,6 +24,12 @@ int md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, int md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, conn_rec *c, server_rec *s, X509 *cert); +int md_ocsp_prime_status(server_rec *s, apr_pool_t *p, + const ap_bytes_t *id, const char *pem); + +int md_ocsp_provide_status(server_rec *s, conn_rec *c, const ap_bytes_t *id, + ap_ssl_ocsp_copy_resp *cb, void *userdata); + /** * Start watchdog for retrieving/updating ocsp status. */ diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c index d6448957f6..df25c49925 100644 --- a/modules/ssl/ssl_util_ssl.c +++ b/modules/ssl/ssl_util_ssl.c @@ -578,3 +578,26 @@ cleanup: if (in != NULL) BIO_free(in); return rv; } + +apr_status_t modssl_cert_get_pem(apr_pool_t *p, + X509 *cert1, X509 *cert2, + const char **ppem) +{ + apr_status_t rv = APR_ENOMEM; + BIO *bio; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) goto cleanup; + if (PEM_write_bio_X509(bio, cert1) != 1) goto cleanup; + if (cert2 && PEM_write_bio_X509(bio, cert2) != 1) goto cleanup; + rv = APR_SUCCESS; + +cleanup: + if (rv != APR_SUCCESS) { + *ppem = NULL; + if (bio) BIO_free(bio); + } + else { + *ppem = modssl_bio_free_read(p, bio); + } + return rv; +} diff --git a/modules/ssl/ssl_util_ssl.h b/modules/ssl/ssl_util_ssl.h index 335d6fdde7..0f01128528 100644 --- a/modules/ssl/ssl_util_ssl.h +++ b/modules/ssl/ssl_util_ssl.h @@ -90,7 +90,18 @@ apr_status_t modssl_read_cert(apr_pool_t *p, const char *cert_pem, const char *key_pem, pem_password_cb *cb, void *ud, X509 **pcert, EVP_PKEY **pkey); - + +/* Convert a certificate (and optionally a second) into a PEM string. + * @param p pool for allocations + * @param cert1 the certificate to convert + * @param cert2 a second cert to add to the PEM afterwards or NULL. + * @param ppem the certificate(s) in PEM format, NUL-terminated. + * @return APR_SUCCESS if ppem is valid. + */ +apr_status_t modssl_cert_get_pem(apr_pool_t *p, + X509 *cert1, X509 *cert2, + const char **ppem); + #endif /* __SSL_UTIL_SSL_H__ */ /** @} */ diff --git a/modules/ssl/ssl_util_stapling.c b/modules/ssl/ssl_util_stapling.c index a6ee6e8e6c..54fb4e0415 100644 --- a/modules/ssl/ssl_util_stapling.c +++ b/modules/ssl/ssl_util_stapling.c @@ -130,6 +130,8 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, X509 *issuer = NULL; OCSP_CERTID *cid = NULL; STACK_OF(OPENSSL_STRING) *aia = NULL; + const char *pem = NULL; + ap_bytes_t key; int rv = 1; /* until further notice */ if (x == NULL) @@ -149,7 +151,20 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, return 1; } - if (ssl_run_init_stapling_status(s, p, x, issuer) == OK) { + if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) { + rv = 0; + goto cleanup; + } + + if (modssl_cert_get_pem(ptemp, x, issuer, &pem) != APR_SUCCESS) { + rv = 0; + goto cleanup; + } + + key.data = idx; + key.len = sizeof(idx); + if (ap_ssl_ocsp_prime(s, p, &key, pem) == APR_SUCCESS + || ssl_run_init_stapling_status(s, p, x, issuer) == OK) { /* Someone's taken over or mod_ssl's own implementation is not enabled */ if (mctx->stapling_enabled != TRUE) { SSL_CTX_set_tlsext_status_cb(mctx->ssl_ctx, stapling_cb); @@ -163,11 +178,6 @@ int ssl_stapling_init_cert(server_rec *s, apr_pool_t *p, apr_pool_t *ptemp, goto cleanup; } - if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) { - rv = 0; - goto cleanup; - } - cinf = apr_hash_get(stapling_certinfo, idx, sizeof(idx)); if (cinf) { /* @@ -228,14 +238,11 @@ cleanup: return rv; } -static certinfo *stapling_get_certinfo(server_rec *s, X509 *x, modssl_ctx_t *mctx, - SSL *ssl) +static certinfo *stapling_get_certinfo(server_rec *s, UCHAR *idx, apr_size_t idx_len, + modssl_ctx_t *mctx, SSL *ssl) { certinfo *cinf; - UCHAR idx[SHA_DIGEST_LENGTH]; - if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) - return NULL; - cinf = apr_hash_get(stapling_certinfo, idx, sizeof(idx)); + cinf = apr_hash_get(stapling_certinfo, idx, idx_len); if (cinf && cinf->cid) return cinf; ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01926) @@ -765,6 +772,18 @@ static int get_and_check_cached_response(server_rec *s, modssl_ctx_t *mctx, return 0; } +static void copy_ocsp_resp(const unsigned char *der, apr_size_t der_len, void *userdata) +{ + ap_bytes_t *resp = userdata; + + resp->len = 0; + resp->data = der? OPENSSL_malloc(der_len) : NULL; + if (resp->data) { + memcpy(resp->data, der, der_len); + resp->len = der_len; + } +} + /* Certificate Status callback. This is called when a client includes a * certificate status request extension. * @@ -779,13 +798,14 @@ static int stapling_cb(SSL *ssl, void *arg) SSLSrvConfigRec *sc = mySrvConfig(s); SSLConnRec *sslconn = myConnConfig(conn); modssl_ctx_t *mctx = myCtxConfig(sslconn, sc); + UCHAR idx[SHA_DIGEST_LENGTH]; + ap_bytes_t key, resp; certinfo *cinf = NULL; OCSP_RESPONSE *rsp = NULL; int rv; BOOL ok = TRUE; X509 *x; - unsigned char *rspder = NULL; - int rspderlen; + int rspderlen, provided = 0; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01951) "stapling_cb: OCSP Stapling callback called"); @@ -795,12 +815,26 @@ static int stapling_cb(SSL *ssl, void *arg) return SSL_TLSEXT_ERR_NOACK; } - if (ssl_run_get_stapling_status(&rspder, &rspderlen, conn, s, x) == APR_SUCCESS) { + if (X509_digest(x, EVP_sha1(), idx, NULL) != 1) { + return SSL_TLSEXT_ERR_NOACK; + } + key.data = idx; + key.len = sizeof(idx); + + if (ap_ssl_ocsp_get_resp(s, conn, &key, copy_ocsp_resp, &resp) == APR_SUCCESS) { + provided = 1; + } + else if (ssl_run_get_stapling_status(&resp.data, &rspderlen, conn, s, x) == APR_SUCCESS) { + resp.len = (apr_size_t)rspderlen; + provided = 1; + } + + if (provided) { /* a hook handles stapling for this certificate and determines the response */ - if (rspder == NULL || rspderlen <= 0) { + if (resp.data == NULL || resp.len == 0) { return SSL_TLSEXT_ERR_NOACK; } - SSL_set_tlsext_status_ocsp_resp(ssl, rspder, rspderlen); + SSL_set_tlsext_status_ocsp_resp(ssl, resp.data, (int)resp.len); return SSL_TLSEXT_ERR_OK; } @@ -810,7 +844,7 @@ static int stapling_cb(SSL *ssl, void *arg) return SSL_TLSEXT_ERR_NOACK; } - if ((cinf = stapling_get_certinfo(s, x, mctx, ssl)) == NULL) { + if ((cinf = stapling_get_certinfo(s, idx, sizeof(idx), mctx, ssl)) == NULL) { return SSL_TLSEXT_ERR_NOACK; } diff --git a/server/ssl.c b/server/ssl.c index eddd5303dc..65112ca9da 100644 --- a/server/ssl.c +++ b/server/ssl.c @@ -56,6 +56,8 @@ APR_HOOK_STRUCT( APR_HOOK_LINK(ssl_add_cert_files) APR_HOOK_LINK(ssl_add_fallback_cert_files) APR_HOOK_LINK(ssl_answer_challenge) + APR_HOOK_LINK(ssl_ocsp_prime_hook) + APR_HOOK_LINK(ssl_ocsp_get_resp_hook) ) APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); @@ -145,6 +147,22 @@ AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name, return (ap_run_ssl_answer_challenge(c, server_name, pcert_pem, pkey_pem) == OK); } +AP_DECLARE(apr_status_t) ap_ssl_ocsp_prime(server_rec *s, apr_pool_t *p, + const ap_bytes_t *id, + const char *pem) +{ + int rv = ap_run_ssl_ocsp_prime_hook(s, p, id, pem); + return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL); +} + +AP_DECLARE(apr_status_t) ap_ssl_ocsp_get_resp(server_rec *s, conn_rec *c, + const ap_bytes_t *id, + ap_ssl_ocsp_copy_resp *cb, void *userdata) +{ + int rv = ap_run_ssl_ocsp_get_resp_hook(s, c, id, cb, userdata); + return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL); +} + AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_conn_is_ssl, (conn_rec *c), (c), DECLINED) AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,ssl_var_lookup, @@ -161,4 +179,9 @@ AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_add_fallback_cert_files, AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_answer_challenge, (conn_rec *c, const char *server_name, const char **pcert_pem, const char **pkey_pem), (c, server_name, pcert_pem, pkey_pem), DECLINED) - +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_prime_hook, + (server_rec *s, apr_pool_t *p, const ap_bytes_t *id, const char *pem), + (s, p, id, pem), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_get_resp_hook, + (server_rec *s, conn_rec *c, const ap_bytes_t *id, ap_ssl_ocsp_copy_resp *cb, void *userdata), + (s, c, id, cb, userdata), DECLINED) |