[mod_gnutls] OCSP stapling (#2469)

personal/stbuehler/ci-build
Glenn Strauss 3 years ago
parent 12e5e745b0
commit c0796ee1dd

@ -7,8 +7,6 @@
/*
* GnuTLS manual: https://www.gnutls.org/documentation.html
*
* future possible enhancements: OCSP stapling
*
* Note: If session tickets are -not- disabled with
* ssl.openssl.ssl-conf-cmd = ("Options" => "-SessionTicket")
* mod_gnutls rotates server ticket encryption key (STEK) every 24 hours.
@ -41,6 +39,7 @@
#include <unistd.h>
#include <gnutls/gnutls.h>
#include <gnutls/ocsp.h>
#include <gnutls/x509.h>
#include <gnutls/abstract.h>
@ -60,6 +59,9 @@ typedef struct {
char trust_inited;
gnutls_datum_t *ssl_pemfile_x509;
gnutls_privkey_t ssl_pemfile_pkey;
const buffer *ssl_stapling_file;
time_t ssl_stapling_loadts;
time_t ssl_stapling_nextts;
} plugin_cert;
typedef struct {
@ -700,7 +702,9 @@ mod_gnutls_merge_config_cpv (plugin_config * const pconf, const config_plugin_va
case 12:/* ssl.acme-tls-1 */
pconf->ssl_acme_tls_1 = cpv->v.b;
break;
case 13:/* debug.log-ssl-noise */
case 13:/* ssl.stapling-file */
break;
case 14:/* debug.log-ssl-noise */
pconf->ssl_log_noise = (unsigned char)cpv->v.shrt;
break;
default:/* should not happen */
@ -881,6 +885,128 @@ mod_gnutls_verify_cb (gnutls_session_t ssl)
}
#if GNUTLS_VERSION_NUMBER < 0x030603
static time_t
mod_gnutls_ocsp_next_update (plugin_cert *pc, log_error_st *errh)
{
gnutls_datum_t f = { NULL, 0 };
int rc = mod_gnutls_load_file(pc->ssl_stapling_file->ptr, &f, errh);
if (rc < 0) return (time_t)-1;
gnutls_ocsp_resp_t resp = NULL;
time_t nextupd;
if ( gnutls_ocsp_resp_init(&resp) < 0
|| gnutls_ocsp_resp_import(resp, &f) < 0
|| gnutls_ocsp_resp_get_single(resp, 0, NULL, NULL, NULL, NULL, NULL,
NULL, &nextupd, NULL, NULL) < 0)
nextupd = (time_t)-1;
gnutls_ocsp_resp_deinit(resp);
mod_gnutls_datum_wipe(&f);
return nextupd;
}
#endif
static int
mod_gnutls_reload_stapling_file (server *srv, plugin_cert *pc, const time_t cur_ts)
{
#if GNUTLS_VERSION_NUMBER < 0x030603
/* load file into gnutls_ocsp_resp_t before loading into
* gnutls_certificate_credentials_t for safety. Still ToC-ToU since file
* is loaded twice, but unlikely condition. (GnuTLS limitation does not
* expose access to OCSP response from gnutls_certificate_credentials_t
* before GnuTLS 3.6.3) */
time_t nextupd = mod_gnutls_ocsp_next_update(pc, srv->errh);
#else
UNUSED(srv);
#endif
/* GnuTLS 3.5.6 added the ability to include multiple OCSP responses for
* certificate chain as allowed in TLSv1.3, but that is not utilized here.
* If implemented, it will probably operate on a new directive,
* e.g. ssl.stapling-pemfile
* GnuTLS 3.6.3 added gnutls_certificate_set_ocsp_status_request_file2()
* GnuTLS 3.6.3 added gnutls_certificate_set_ocsp_status_request_mem()
* GnuTLS 3.6.3 added gnutls_certificate_get_ocsp_expiration() */
/* gnutls_certificate_get_ocsp_expiration() code comments:
* Note that the credentials structure should be read-only when in
* use, thus when reloading, either the credentials structure must not
* be in use by any sessions, or a new credentials structure should be
* allocated for new sessions.
* XXX: lighttpd is not threaded, so this is probably not an issue (?)
*/
#if 0
gnutls_certificate_set_flags(pc->ssl_cred,
GNUTLS_CERTIFICATE_SKIP_OCSP_RESPONSE_CHECK);
#endif
const char *fn = pc->ssl_stapling_file->ptr;
int rc = gnutls_certificate_set_ocsp_status_request_file(pc->ssl_cred,fn,0);
if (rc < 0)
return rc;
#if GNUTLS_VERSION_NUMBER >= 0x030603
time_t nextupd =
gnutls_certificate_get_ocsp_expiration(pc->ssl_cred, 0, 0, 0);
if (nextupd == (time_t)-2) nextupd = (time_t)-1;
#endif
pc->ssl_stapling_loadts = cur_ts;
pc->ssl_stapling_nextts = nextupd;
if (pc->ssl_stapling_nextts == (time_t)-1) {
/* "Next Update" might not be provided by OCSP responder
* Use 3600 sec (1 hour) in that case. */
/* retry in 1 hour if unable to determine Next Update */
pc->ssl_stapling_nextts = cur_ts + 3600;
pc->ssl_stapling_loadts = 0;
}
return 0;
}
static int
mod_gnutls_refresh_stapling_file (server *srv, plugin_cert *pc, const time_t cur_ts)
{
if (pc->ssl_stapling_nextts >= 256
&& pc->ssl_stapling_nextts - 256 > cur_ts)
return 0; /* skip check for refresh unless close to expire */
struct stat st;
if (0 != stat(pc->ssl_stapling_file->ptr, &st)
|| st.st_mtime <= pc->ssl_stapling_loadts) {
#if 0
if (pc->ssl_stapling_nextts < cur_ts) {
/* discard expired OCSP stapling response */
/* Does GnuTLS detect expired OCSP response? */
/* or must we rebuild gnutls_certificate_credentials_t ? */
}
#endif
return 0;
}
return mod_gnutls_reload_stapling_file(srv, pc, cur_ts);
}
static void
mod_gnutls_refresh_stapling_files (server *srv, const plugin_data *p, const time_t cur_ts)
{
/* future: might construct array of (plugin_cert *) at startup
* to avoid the need to search for them here */
for (int i = 0, used = p->nconfig; i < used; ++i) {
const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; cpv->k_id != -1; ++cpv) {
if (cpv->k_id != 0) continue; /* k_id == 0 for ssl.pemfile */
if (cpv->vtype != T_CONFIG_LOCAL) continue;
plugin_cert *pc = cpv->v.v;
if (!buffer_string_is_empty(pc->ssl_stapling_file))
mod_gnutls_refresh_stapling_file(srv, pc, cur_ts);
}
}
}
static int
mod_gnutls_construct_crt_chain (plugin_cert *pc, gnutls_datum_t *d, log_error_st *errh)
{
@ -955,7 +1081,7 @@ mod_gnutls_construct_crt_chain (plugin_cert *pc, gnutls_datum_t *d, log_error_st
static void *
network_gnutls_load_pemfile (server *srv, const buffer *pemfile, const buffer *privkey)
network_gnutls_load_pemfile (server *srv, const buffer *pemfile, const buffer *privkey, const buffer *ssl_stapling_file)
{
#if 0 /* see comments in mod_gnutls_construct_crt_chain() above */
@ -1004,6 +1130,9 @@ network_gnutls_load_pemfile (server *srv, const buffer *pemfile, const buffer *p
pc->trust_inited = 0;
pc->ssl_pemfile_x509 = d;
pc->ssl_pemfile_pkey = pkey;
pc->ssl_stapling_file= ssl_stapling_file;
pc->ssl_stapling_loadts = 0;
pc->ssl_stapling_nextts = 0;
if (d->size > 1) { /*(certificate chain provided)*/
int rc = mod_gnutls_construct_crt_chain(pc, d, srv->errh);
@ -1015,6 +1144,12 @@ network_gnutls_load_pemfile (server *srv, const buffer *pemfile, const buffer *p
}
}
if (!buffer_string_is_empty(pc->ssl_stapling_file)) {
if (mod_gnutls_reload_stapling_file(srv, pc, log_epoch_secs) < 0) {
/* continue without OCSP response if there is an error */
}
}
return pc;
#endif
@ -1918,6 +2053,9 @@ SETDEFAULTS_FUNC(mod_gnutls_set_defaults)
,{ CONST_STR_LEN("ssl.acme-tls-1"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("ssl.stapling-file"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("debug.log-ssl-noise"),
T_CONFIG_SHORT,
T_CONFIG_SCOPE_CONNECTION }
@ -1937,6 +2075,7 @@ SETDEFAULTS_FUNC(mod_gnutls_set_defaults)
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
config_plugin_value_t *pemfile = NULL;
config_plugin_value_t *privkey = NULL;
const buffer *ssl_stapling_file = NULL;
for (; -1 != cpv->k_id; ++cpv) {
switch (cpv->k_id) {
case 0: /* ssl.pemfile */
@ -1991,9 +2130,12 @@ SETDEFAULTS_FUNC(mod_gnutls_set_defaults)
break;
case 10:/* ssl.verifyclient.username */
case 11:/* ssl.verifyclient.exportcert */
break;
case 12:/* ssl.acme-tls-1 */
case 13:/* debug.log-ssl-noise */
break;
case 13:/* ssl.stapling-file */
ssl_stapling_file = cpv->v.b;
break;
case 14:/* debug.log-ssl-noise */
break;
default:/* should not happen */
break;
@ -2003,7 +2145,8 @@ SETDEFAULTS_FUNC(mod_gnutls_set_defaults)
if (pemfile) {
if (NULL == privkey) privkey = pemfile;
pemfile->v.v =
network_gnutls_load_pemfile(srv, pemfile->v.b, privkey->v.b);
network_gnutls_load_pemfile(srv, pemfile->v.b, privkey->v.b,
ssl_stapling_file);
if (pemfile->v.v)
pemfile->vtype = T_CONFIG_LOCAL;
else
@ -2758,6 +2901,7 @@ TRIGGER_FUNC(mod_gnutls_handle_trigger) {
if (cur_ts & 0x3f) return HANDLER_GO_ON; /*(continue once each 64 sec)*/
mod_gnutls_session_ticket_key_check(srv, p, cur_ts);
mod_gnutls_refresh_stapling_files(srv, p, cur_ts);
return HANDLER_GO_ON;
}

Loading…
Cancel
Save