273 lines
10 KiB
C
273 lines
10 KiB
C
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <apr_optional.h>
|
|
#include <apr_time.h>
|
|
#include <apr_date.h>
|
|
#include <apr_strings.h>
|
|
|
|
#include <httpd.h>
|
|
#include <http_core.h>
|
|
#include <http_log.h>
|
|
#include <http_ssl.h>
|
|
|
|
#include "mod_watchdog.h"
|
|
|
|
#include "md.h"
|
|
#include "md_crypt.h"
|
|
#include "md_http.h"
|
|
#include "md_json.h"
|
|
#include "md_ocsp.h"
|
|
#include "md_store.h"
|
|
#include "md_log.h"
|
|
#include "md_reg.h"
|
|
#include "md_time.h"
|
|
#include "md_util.h"
|
|
|
|
#include "mod_md.h"
|
|
#include "mod_md_config.h"
|
|
#include "mod_md_private.h"
|
|
#include "mod_md_ocsp.h"
|
|
|
|
static int staple_here(md_srv_conf_t *sc)
|
|
{
|
|
if (!sc || !sc->mc->ocsp) return 0;
|
|
if (sc->assigned
|
|
&& sc->assigned->nelts == 1
|
|
&& APR_ARRAY_IDX(sc->assigned, 0, const md_t*)->stapling) return 1;
|
|
return (md_config_geti(sc, MD_CONFIG_STAPLING)
|
|
&& md_config_geti(sc, MD_CONFIG_STAPLE_OTHERS));
|
|
}
|
|
|
|
int md_ocsp_prime_status(server_rec *s, apr_pool_t *p,
|
|
const char *id, apr_size_t id_len, const char *pem)
|
|
{
|
|
md_srv_conf_t *sc;
|
|
const md_t *md;
|
|
apr_array_header_t *chain;
|
|
apr_status_t rv = APR_ENOENT;
|
|
|
|
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(10268) "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(10269) "init stapling for: %s, "
|
|
"need at least 2 certificates in PEM data", md? md->name : s->server_hostname);
|
|
rv = APR_EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
rv = md_ocsp_prime(sc->mc->ocsp, id, id_len,
|
|
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;
|
|
|
|
int md_ocsp_provide_status(server_rec *s, conn_rec *c,
|
|
const char *id, apr_size_t id_len,
|
|
ap_ssl_ocsp_copy_resp *cb, void *userdata)
|
|
{
|
|
md_srv_conf_t *sc;
|
|
const md_t *md;
|
|
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);
|
|
|
|
rv = md_ocsp_get_status(cb, userdata, sc->mc->ocsp, id, id_len, c->pool, md);
|
|
if (APR_STATUS_IS_ENOENT(rv)) goto declined;
|
|
return OK;
|
|
|
|
declined:
|
|
return DECLINED;
|
|
}
|
|
|
|
|
|
/**************************************************************************************************/
|
|
/* watchdog based impl. */
|
|
|
|
#define MD_OCSP_WATCHDOG_NAME "_md_ocsp_"
|
|
|
|
static APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *wd_get_instance;
|
|
static APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *wd_register_callback;
|
|
static APR_OPTIONAL_FN_TYPE(ap_watchdog_set_callback_interval) *wd_set_interval;
|
|
|
|
typedef struct md_ocsp_ctx_t md_ocsp_ctx_t;
|
|
|
|
struct md_ocsp_ctx_t {
|
|
apr_pool_t *p;
|
|
server_rec *s;
|
|
md_mod_conf_t *mc;
|
|
ap_watchdog_t *watchdog;
|
|
};
|
|
|
|
static apr_time_t next_run_default(void)
|
|
{
|
|
/* we'd like to run at least hourly */
|
|
return apr_time_now() + apr_time_from_sec(MD_SECS_PER_HOUR);
|
|
}
|
|
|
|
static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
|
|
{
|
|
md_ocsp_ctx_t *octx = baton;
|
|
apr_time_t next_run, wait_time;
|
|
|
|
/* mod_watchdog invoked us as a single thread inside the whole server (on this machine).
|
|
* This might be a repeated run inside the same child (mod_watchdog keeps affinity as
|
|
* long as the child lives) or another/new child.
|
|
*/
|
|
switch (state) {
|
|
case AP_WATCHDOG_STATE_STARTING:
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10197)
|
|
"md ocsp watchdog start, ocsp stapling %d certificates",
|
|
(int)md_ocsp_count(octx->mc->ocsp));
|
|
break;
|
|
|
|
case AP_WATCHDOG_STATE_RUNNING:
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10198)
|
|
"md ocsp watchdog run, ocsp stapling %d certificates",
|
|
(int)md_ocsp_count(octx->mc->ocsp));
|
|
|
|
/* Process all drive jobs. They will update their next_run property
|
|
* and we schedule ourself at the earliest of all. A job may specify 0
|
|
* as next_run to indicate that it wants to participate in the normal
|
|
* regular runs. */
|
|
next_run = next_run_default();
|
|
|
|
md_ocsp_renew(octx->mc->ocsp, octx->p, ptemp, &next_run);
|
|
|
|
wait_time = next_run - apr_time_now();
|
|
if (APLOGdebug(octx->s)) {
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10199)
|
|
"md ocsp watchdog next run in %s",
|
|
md_duration_print(ptemp, wait_time));
|
|
}
|
|
wd_set_interval(octx->watchdog, wait_time, octx, run_watchdog);
|
|
break;
|
|
|
|
case AP_WATCHDOG_STATE_STOPPING:
|
|
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10200)
|
|
"md ocsp watchdog stopping");
|
|
break;
|
|
}
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
static apr_status_t ocsp_remove_old_responses(md_mod_conf_t *mc, apr_pool_t *p)
|
|
{
|
|
md_timeperiod_t keep_norm, keep;
|
|
|
|
keep_norm.end = apr_time_now();
|
|
keep_norm.start = keep_norm.end - MD_TIME_OCSP_KEEP_NORM;
|
|
keep = md_timeperiod_slice_before_end(&keep_norm, mc->ocsp_keep_window);
|
|
/* remove any ocsp response older than keep.start */
|
|
return md_ocsp_remove_responses_older_than(mc->ocsp, p, keep.start);
|
|
}
|
|
|
|
apr_status_t md_ocsp_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
|
|
{
|
|
apr_allocator_t *allocator;
|
|
md_ocsp_ctx_t *octx;
|
|
apr_pool_t *octxp;
|
|
apr_status_t rv;
|
|
|
|
wd_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
|
|
wd_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
|
|
wd_set_interval = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_set_callback_interval);
|
|
|
|
if (!wd_get_instance || !wd_register_callback || !wd_set_interval) {
|
|
ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(10201)
|
|
"mod_watchdog is required for OCSP stapling");
|
|
return APR_EGENERAL;
|
|
}
|
|
|
|
/* We want our own pool with own allocator to keep data across watchdog invocations.
|
|
* Since we'll run in a single watchdog thread, using our own allocator will prevent
|
|
* any confusion in the parent pool. */
|
|
apr_allocator_create(&allocator);
|
|
apr_allocator_max_free_set(allocator, 1);
|
|
rv = apr_pool_create_ex(&octxp, p, NULL, allocator);
|
|
if (rv != APR_SUCCESS) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10205) "md_ocsp_watchdog: create pool");
|
|
return rv;
|
|
}
|
|
apr_allocator_owner_set(allocator, octxp);
|
|
apr_pool_tag(octxp, "md_ocsp_watchdog");
|
|
|
|
octx = apr_pcalloc(octxp, sizeof(*octx));
|
|
octx->p = octxp;
|
|
octx->s = s;
|
|
octx->mc = mc;
|
|
|
|
/* Time for some house keeping, before the server goes live (again):
|
|
* - we store OCSP responses for each certificate individually by its SHA-1 id
|
|
* - this means, as long as certificate do not change, the number of response
|
|
* files remains stable.
|
|
* - But when a certificate changes (is replaced), the response is obsolete
|
|
* - we do not get notified when a certificate is no longer used. An admin
|
|
* might just reconfigure or change the content of a file (backup/restore etc.)
|
|
* - also, certificates might be added by some openssl config commands or other
|
|
* modules that we do not immediately see right at startup. We cannot assume
|
|
* that any OCSP response we cannot relate to a certificate RIGHT NOW, is no
|
|
* longer needed.
|
|
* - since the response files are relatively small, we have no problem with
|
|
* keeping them around for a while. We just do not want an ever growing store.
|
|
* - The simplest and effective way seems to be to just remove files older
|
|
* a certain amount of time. Take a 7 day default and let the admin configure
|
|
* it for very special setups.
|
|
*/
|
|
ocsp_remove_old_responses(mc, octx->p);
|
|
|
|
rv = wd_get_instance(&octx->watchdog, MD_OCSP_WATCHDOG_NAME, 0, 1, octx->p);
|
|
if (APR_SUCCESS != rv) {
|
|
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10202)
|
|
"create md ocsp watchdog(%s)", MD_OCSP_WATCHDOG_NAME);
|
|
return rv;
|
|
}
|
|
rv = wd_register_callback(octx->watchdog, 0, octx, run_watchdog);
|
|
ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10203)
|
|
"register md ocsp watchdog(%s)", MD_OCSP_WATCHDOG_NAME);
|
|
return rv;
|
|
}
|
|
|
|
|
|
|