2020-05-14 06:03:14 +00:00
|
|
|
/*
|
|
|
|
* mod_gnutls - GnuTLS support for lighttpd
|
|
|
|
*
|
|
|
|
* Copyright(c) 2020 Glenn Strauss gstrauss()gluelogic.com All rights reserved
|
|
|
|
* License: BSD 3-clause (same as lighttpd)
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* GnuTLS manual: https://www.gnutls.org/documentation.html
|
|
|
|
*
|
2020-06-04 20:46:51 +00:00
|
|
|
* Note: If session tickets are -not- disabled with
|
|
|
|
* ssl.openssl.ssl-conf-cmd = ("Options" => "-SessionTicket")
|
2020-05-14 06:03:14 +00:00
|
|
|
* mod_gnutls rotates server ticket encryption key (STEK) every 24 hours.
|
|
|
|
* This is fine for use with a single lighttpd instance, but with multiple
|
|
|
|
* lighttpd workers, no coordinated STEK (server ticket encryption key)
|
2020-06-04 20:46:51 +00:00
|
|
|
* rotation occurs unless ssl.stek-file is defined and maintained (preferred),
|
|
|
|
* or if some external job restarts lighttpd. Restarting lighttpd generates a
|
|
|
|
* new key that is shared by lighttpd workers for the lifetime of the new key.
|
|
|
|
* If the rotation period expires and lighttpd has not been restarted, and if
|
|
|
|
* ssl.stek-file is not in use, then lighttpd workers will generate new
|
2020-05-14 06:03:14 +00:00
|
|
|
* independent keys, making session tickets less effective for session
|
|
|
|
* resumption, since clients have a lower chance for future connections to
|
|
|
|
* reach the same lighttpd worker. However, things will still work, and a new
|
|
|
|
* session will be created if session resumption fails. Admins should plan to
|
|
|
|
* restart lighttpd at least every 24 hours if session tickets are enabled and
|
2020-06-04 20:46:51 +00:00
|
|
|
* multiple lighttpd workers are configured. Since that is likely disruptive,
|
|
|
|
* if multiple lighttpd workers are configured, ssl.stek-file should be
|
|
|
|
* defined and the file maintained externally.
|
2020-05-14 06:03:14 +00:00
|
|
|
*/
|
|
|
|
#include "first.h"
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
2020-06-04 20:46:51 +00:00
|
|
|
#include <sys/stat.h>
|
2020-05-14 06:03:14 +00:00
|
|
|
#include <errno.h>
|
2020-06-04 20:46:51 +00:00
|
|
|
#include <fcntl.h>
|
2020-05-14 06:03:14 +00:00
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h> /* vsnprintf() */
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <gnutls/gnutls.h>
|
2020-06-11 09:15:07 +00:00
|
|
|
#include <gnutls/ocsp.h>
|
2020-05-14 06:03:14 +00:00
|
|
|
#include <gnutls/x509.h>
|
2020-06-14 14:54:52 +00:00
|
|
|
#include <gnutls/x509-ext.h>
|
2020-05-14 06:03:14 +00:00
|
|
|
#include <gnutls/abstract.h>
|
|
|
|
|
|
|
|
#ifdef GNUTLS_SKIP_GLOBAL_INIT
|
|
|
|
GNUTLS_SKIP_GLOBAL_INIT
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "base.h"
|
2020-06-04 20:46:51 +00:00
|
|
|
#include "fdevent.h"
|
2020-05-14 06:03:14 +00:00
|
|
|
#include "http_header.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "plugin.h"
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
/* SNI per host: with COMP_SERVER_SOCKET, COMP_HTTP_SCHEME, COMP_HTTP_HOST */
|
|
|
|
gnutls_certificate_credentials_t ssl_cred;
|
|
|
|
char trust_inited;
|
2020-06-14 14:54:52 +00:00
|
|
|
char must_staple;
|
2020-05-14 06:03:14 +00:00
|
|
|
gnutls_datum_t *ssl_pemfile_x509;
|
|
|
|
gnutls_privkey_t ssl_pemfile_pkey;
|
2020-06-11 09:15:07 +00:00
|
|
|
const buffer *ssl_stapling_file;
|
|
|
|
time_t ssl_stapling_loadts;
|
|
|
|
time_t ssl_stapling_nextts;
|
2020-05-14 06:03:14 +00:00
|
|
|
} plugin_cert;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
int8_t ssl_session_ticket;
|
|
|
|
/*(preserved here for deinit at server shutdown)*/
|
|
|
|
gnutls_priority_t priority_cache;
|
|
|
|
#if GNUTLS_VERSION_NUMBER < 0x030600
|
|
|
|
gnutls_dh_params_t dh_params;
|
|
|
|
#endif
|
|
|
|
} plugin_ssl_ctx;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
plugin_cert *pc;
|
|
|
|
|
|
|
|
/*(used only during startup; not patched)*/
|
|
|
|
unsigned char ssl_enabled; /* only interesting for setting up listening sockets. don't use at runtime */
|
|
|
|
unsigned char ssl_honor_cipher_order; /* determine SSL cipher in server-preferred order, not client-order */
|
|
|
|
unsigned char ssl_empty_fragments;
|
|
|
|
unsigned char ssl_use_sslv2;
|
|
|
|
unsigned char ssl_use_sslv3;
|
|
|
|
const buffer *ssl_cipher_list;
|
|
|
|
const buffer *ssl_dh_file;
|
|
|
|
const buffer *ssl_ec_curve;
|
|
|
|
array *ssl_conf_cmd;
|
|
|
|
|
|
|
|
/*(copied from plugin_data for socket ssl_ctx config)*/
|
|
|
|
gnutls_priority_t priority_cache;
|
|
|
|
unsigned char ssl_session_ticket;
|
|
|
|
unsigned char ssl_verifyclient;
|
|
|
|
unsigned char ssl_verifyclient_enforce;
|
|
|
|
unsigned char ssl_verifyclient_depth;
|
|
|
|
|
|
|
|
const char *priority_base;
|
2020-06-11 02:34:09 +00:00
|
|
|
buffer *priority_override;
|
2020-05-14 06:03:14 +00:00
|
|
|
buffer priority_str;
|
|
|
|
#if GNUTLS_VERSION_NUMBER < 0x030600
|
|
|
|
gnutls_dh_params_t dh_params;
|
|
|
|
#endif
|
|
|
|
} plugin_config_socket; /*(used at startup during configuration)*/
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
/* SNI per host: w/ COMP_SERVER_SOCKET, COMP_HTTP_SCHEME, COMP_HTTP_HOST */
|
|
|
|
plugin_cert *pc;
|
|
|
|
gnutls_datum_t *ssl_ca_file; /* .data is (gnutls_x509_crt_t) */
|
|
|
|
gnutls_datum_t *ssl_ca_dn_file; /* .data is (gnutls_x509_crt_t) */
|
|
|
|
gnutls_datum_t *ssl_ca_crl_file;/* .data is (gnutls_x509_crl_t) */
|
|
|
|
|
|
|
|
unsigned char ssl_verifyclient;
|
|
|
|
unsigned char ssl_verifyclient_enforce;
|
|
|
|
unsigned char ssl_verifyclient_depth;
|
|
|
|
unsigned char ssl_verifyclient_export_cert;
|
|
|
|
unsigned char ssl_read_ahead;
|
|
|
|
unsigned char ssl_log_noise;
|
|
|
|
unsigned char ssl_disable_client_renegotiation;
|
|
|
|
const buffer *ssl_verifyclient_username;
|
|
|
|
const buffer *ssl_acme_tls_1;
|
|
|
|
#if GNUTLS_VERSION_NUMBER < 0x030600
|
|
|
|
gnutls_dh_params_t dh_params;
|
|
|
|
#endif
|
|
|
|
} plugin_config;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
PLUGIN_DATA;
|
|
|
|
plugin_ssl_ctx *ssl_ctxs;
|
|
|
|
plugin_config defaults;
|
|
|
|
server *srv;
|
2020-06-04 20:46:51 +00:00
|
|
|
const char *ssl_stek_file;
|
2020-05-14 06:03:14 +00:00
|
|
|
} plugin_data;
|
|
|
|
|
|
|
|
static int ssl_is_init;
|
|
|
|
/* need assigned p->id for deep access of module handler_ctx for connection
|
|
|
|
* i.e. handler_ctx *hctx = r->plugin_ctx[plugin_data_singleton->id]; */
|
|
|
|
static plugin_data *plugin_data_singleton;
|
|
|
|
#define LOCAL_SEND_BUFSIZE 16384 /* DEFAULT_MAX_RECORD_SIZE */
|
|
|
|
static char *local_send_buffer;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
gnutls_session_t ssl; /* gnutls request/connection context */
|
|
|
|
request_st *r;
|
|
|
|
connection *con;
|
|
|
|
int8_t request_env_patched;
|
|
|
|
int8_t close_notify;
|
|
|
|
uint8_t alpn;
|
|
|
|
int8_t ssl_session_ticket;
|
|
|
|
int handshake;
|
|
|
|
size_t pending_write;
|
|
|
|
plugin_config conf;
|
|
|
|
unsigned int verify_status;
|
|
|
|
buffer *tmp_buf;
|
|
|
|
gnutls_certificate_credentials_t acme_tls_1_cred;
|
|
|
|
} handler_ctx;
|
|
|
|
|
|
|
|
|
|
|
|
static handler_ctx *
|
|
|
|
handler_ctx_init (void)
|
|
|
|
{
|
|
|
|
handler_ctx *hctx = calloc(1, sizeof(*hctx));
|
|
|
|
force_assert(hctx);
|
|
|
|
return hctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
handler_ctx_free (handler_ctx *hctx)
|
|
|
|
{
|
|
|
|
gnutls_deinit(hctx->ssl);
|
|
|
|
if (hctx->acme_tls_1_cred)
|
|
|
|
gnutls_certificate_free_credentials(hctx->acme_tls_1_cred);
|
|
|
|
free(hctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void elog(log_error_st * const errh,
|
|
|
|
const char * const file, const int line,
|
|
|
|
const int rc, const char * const msg)
|
|
|
|
{
|
|
|
|
/* error logging convenience function that decodes gnutls result codes */
|
|
|
|
log_error(errh, file, line, "GnuTLS: %s: (%s) %s",
|
|
|
|
msg, gnutls_strerror_name(rc), gnutls_strerror(rc));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
__attribute_format__((__printf__, 5, 6))
|
|
|
|
static void elogf(log_error_st * const errh,
|
|
|
|
const char * const file, const int line,
|
|
|
|
const int rc, const char * const fmt, ...)
|
|
|
|
{
|
|
|
|
char msg[1024];
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
elog(errh, file, line, rc, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* gnutls func gnutls_load_file() loads file contents into a gnutls_datum_t.
|
|
|
|
* The data loaded could be sensitive, e.g. a private key included in a pemfile.
|
|
|
|
* However, gnutls_load_file() is not careful about zeroizing memory on error,
|
|
|
|
* might use realloc() (which does not guarantee to zeroize memory released),
|
|
|
|
* and silently continues on short read, so provide our own. Updates to
|
|
|
|
* gnutls 3.6.14 may be more careful with private key files, but do not
|
|
|
|
* extend the same care to pemfiles which might contain private keys.
|
|
|
|
* Related, see also mod_gnutls_datum_wipe() below.
|
|
|
|
* https://gitlab.com/gnutls/gnutls/-/issues/1001
|
|
|
|
* https://gitlab.com/gnutls/gnutls/-/issues/1002
|
|
|
|
* https://gitlab.com/gnutls/gnutls/-/merge_requests/1270
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
mod_gnutls_load_file (const char * const fn, gnutls_datum_t * const d, log_error_st *errh)
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
int rc = gnutls_load_file(fn, d);
|
|
|
|
if (rc < 0)
|
|
|
|
elogf(errh, __FILE__, __LINE__, rc, "%s() %s", __func__, fn);
|
|
|
|
return rc;
|
|
|
|
#else
|
2020-07-02 21:20:29 +00:00
|
|
|
off_t dlen = 512*1024*1024;/*(arbitrary limit: 512 MB file; expect < 1 MB)*/
|
|
|
|
char *data = fdevent_load_file(fn, &dlen, errh, gnutls_malloc, gnutls_free);
|
|
|
|
if (NULL == data) return GNUTLS_E_FILE_ERROR;
|
|
|
|
d->data = (unsigned char *)data;
|
|
|
|
d->size = (unsigned int)dlen;
|
|
|
|
return 0;
|
2020-05-14 06:03:14 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* GnuTLS does not expose _gnutls_free_key_datum() so provide our own */
|
|
|
|
static void
|
|
|
|
mod_gnutls_datum_wipe (gnutls_datum_t * const d)
|
|
|
|
{
|
|
|
|
if (NULL == d) return;
|
|
|
|
if (d->data) {
|
|
|
|
if (d->size) gnutls_memset(d->data, 0, d->size);
|
|
|
|
gnutls_free(d->data);
|
|
|
|
d->data = NULL;
|
|
|
|
}
|
|
|
|
d->size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-04 20:46:51 +00:00
|
|
|
/* session tickets
|
|
|
|
*
|
|
|
|
* gnutls expects 64 bytes of random data in gnutls_datum_t passed to
|
|
|
|
* gnutls_session_ticket_enable_server()
|
|
|
|
*
|
|
|
|
* (private) session ticket definitions from lib/gnutls_int.h
|
|
|
|
* #define TICKET_MASTER_KEY_SIZE (TICKET_KEY_NAME_SIZE+TICKET_CIPHER_KEY_SIZE+TICKET_MAC_SECRET_SIZE)
|
|
|
|
* #define TICKET_KEY_NAME_SIZE 16
|
|
|
|
* #define TICKET_CIPHER_KEY_SIZE 32
|
|
|
|
* #define TICKET_MAC_SECRET_SIZE 16
|
|
|
|
*/
|
|
|
|
#define TICKET_MASTER_KEY_SIZE 64
|
|
|
|
|
|
|
|
#define TLSEXT_KEYNAME_LENGTH 16
|
|
|
|
#define TLSEXT_TICK_KEY_LENGTH 32
|
|
|
|
|
|
|
|
/* construct our own session ticket encryption key structure
|
|
|
|
* to store keys that are not yet active
|
|
|
|
* (mirror from mod_openssl, even though not all bits are used here) */
|
|
|
|
typedef struct tlsext_ticket_key_st {
|
|
|
|
time_t active_ts; /* tickets not issued w/ key until activation timestamp */
|
|
|
|
time_t expire_ts; /* key not valid after expiration timestamp */
|
|
|
|
unsigned char tick_key_name[TLSEXT_KEYNAME_LENGTH];
|
|
|
|
unsigned char tick_hmac_key[TLSEXT_TICK_KEY_LENGTH];
|
|
|
|
unsigned char tick_aes_key[TLSEXT_TICK_KEY_LENGTH];
|
|
|
|
} tlsext_ticket_key_t;
|
|
|
|
|
|
|
|
static tlsext_ticket_key_t session_ticket_keys[1]; /* temp store until active */
|
2020-05-14 06:03:14 +00:00
|
|
|
static time_t stek_rotate_ts;
|
2020-06-04 20:46:51 +00:00
|
|
|
|
2020-05-14 06:03:14 +00:00
|
|
|
static gnutls_datum_t session_ticket_key;
|
2020-06-04 20:46:51 +00:00
|
|
|
|
|
|
|
|
2020-05-14 06:03:14 +00:00
|
|
|
static void
|
|
|
|
mod_gnutls_session_ticket_key_free (void)
|
|
|
|
{
|
|
|
|
mod_gnutls_datum_wipe(&session_ticket_key);
|
|
|
|
}
|
|
|
|
static void
|
|
|
|
mod_gnutls_session_ticket_key_init (server *srv)
|
|
|
|
{
|
|
|
|
int rc = gnutls_session_ticket_key_generate(&session_ticket_key);
|
|
|
|
if (rc < 0) {
|
|
|
|
/*(should not happen, but if it does, disable session ticket)*/
|
|
|
|
session_ticket_key.size = 0;
|
|
|
|
elog(srv->errh, __FILE__, __LINE__, rc,
|
|
|
|
"gnutls_session_ticket_key_generate()");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
|
|
mod_gnutls_session_ticket_key_rotate (server *srv)
|
|
|
|
{
|
|
|
|
mod_gnutls_session_ticket_key_free();
|
|
|
|
mod_gnutls_session_ticket_key_init(srv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-04 20:46:51 +00:00
|
|
|
static int
|
|
|
|
mod_gnutls_session_ticket_key_file (const char *fn)
|
|
|
|
{
|
|
|
|
/* session ticket encryption key (STEK)
|
|
|
|
*
|
|
|
|
* STEK file should be stored in non-persistent storage,
|
|
|
|
* e.g. /dev/shm/lighttpd/stek-file (in memory)
|
|
|
|
* with appropriate permissions set to keep stek-file from being
|
|
|
|
* read by other users. Where possible, systems should also be
|
|
|
|
* configured without swap.
|
|
|
|
*
|
|
|
|
* admin should schedule an independent job to periodically
|
|
|
|
* generate new STEK up to 3 times during key lifetime
|
|
|
|
* (lighttpd stores up to 3 keys)
|
|
|
|
*
|
|
|
|
* format of binary file is:
|
|
|
|
* 4-byte - format version (always 0; for use if format changes)
|
|
|
|
* 4-byte - activation timestamp
|
|
|
|
* 4-byte - expiration timestamp
|
|
|
|
* 16-byte - session ticket key name
|
|
|
|
* 32-byte - session ticket HMAC encrpytion key
|
|
|
|
* 32-byte - session ticket AES encrpytion key
|
|
|
|
*
|
|
|
|
* STEK file can be created with a command such as:
|
|
|
|
* dd if=/dev/random bs=1 count=80 status=none | \
|
|
|
|
* perl -e 'print pack("iii",0,time()+300,time()+86400),<>' \
|
|
|
|
* > STEK-file.$$ && mv STEK-file.$$ STEK-file
|
|
|
|
*
|
|
|
|
* The above delays activation time by 5 mins (+300 sec) to allow file to
|
|
|
|
* be propagated to other machines. (admin must handle this independently)
|
|
|
|
* If STEK generation is performed immediately prior to starting lighttpd,
|
|
|
|
* admin should activate keys immediately (without +300).
|
|
|
|
*/
|
|
|
|
int buf[23]; /* 92 bytes */
|
|
|
|
int fd = fdevent_open_cloexec(fn, 1, O_RDONLY, 0);
|
|
|
|
if (fd < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ssize_t rd = read(fd, buf, sizeof(buf));
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
int rc = 0; /*(will retry on next check interval upon any error)*/
|
|
|
|
if (rd == sizeof(buf) && buf[0] == 0) { /*(format version 0)*/
|
|
|
|
session_ticket_keys[0].active_ts = buf[1];
|
|
|
|
session_ticket_keys[0].expire_ts = buf[2];
|
|
|
|
memcpy(&session_ticket_keys[0].tick_key_name, buf+3, 80);
|
|
|
|
rc = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
gnutls_memset(buf, 0, sizeof(buf));
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
mod_gnutls_session_ticket_key_check (server *srv, const plugin_data *p, const time_t cur_ts)
|
|
|
|
{
|
|
|
|
if (p->ssl_stek_file) {
|
|
|
|
struct stat st;
|
|
|
|
if (0 == stat(p->ssl_stek_file, &st) && st.st_mtime > stek_rotate_ts
|
|
|
|
&& mod_gnutls_session_ticket_key_file(p->ssl_stek_file)) {
|
|
|
|
stek_rotate_ts = cur_ts;
|
|
|
|
}
|
|
|
|
|
|
|
|
tlsext_ticket_key_t *stek = session_ticket_keys;
|
|
|
|
if (stek->active_ts != 0 && stek->active_ts - 63 <= cur_ts) {
|
|
|
|
if (NULL == session_ticket_key.data) {
|
|
|
|
session_ticket_key.data = gnutls_malloc(TICKET_MASTER_KEY_SIZE);
|
|
|
|
if (NULL == session_ticket_key.data) return;
|
|
|
|
session_ticket_key.size = TICKET_MASTER_KEY_SIZE;
|
|
|
|
}
|
|
|
|
memcpy(session_ticket_key.data,
|
|
|
|
stek->tick_key_name, TICKET_MASTER_KEY_SIZE);
|
|
|
|
gnutls_memset(stek->tick_key_name, 0, TICKET_MASTER_KEY_SIZE);
|
|
|
|
}
|
|
|
|
if (stek->expire_ts < cur_ts)
|
|
|
|
mod_gnutls_session_ticket_key_free();
|
|
|
|
}
|
|
|
|
else if (cur_ts - 86400 >= stek_rotate_ts) { /*(24 hours)*/
|
|
|
|
mod_gnutls_session_ticket_key_rotate(srv);
|
|
|
|
stek_rotate_ts = cur_ts;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-14 06:03:14 +00:00
|
|
|
INIT_FUNC(mod_gnutls_init)
|
|
|
|
{
|
|
|
|
plugin_data_singleton = (plugin_data *)calloc(1, sizeof(plugin_data));
|
|
|
|
return plugin_data_singleton;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int mod_gnutls_init_once_gnutls (void)
|
|
|
|
{
|
|
|
|
if (ssl_is_init) return 1;
|
|
|
|
ssl_is_init = 1;
|
|
|
|
|
|
|
|
/* Note: on systems with support for weak symbols, GNUTLS_SKIP_GLOBAL_INIT
|
|
|
|
* is set near top of this file to inhibit GnuTLS implicit initialization
|
|
|
|
* in a library constructor. On systems without support for weak symbols,
|
|
|
|
* set GNUTLS_NO_EXPLICIT_INIT=1 in the environment before starting lighttpd
|
|
|
|
* (GnuTLS 3.3.0 or later) */
|
|
|
|
|
|
|
|
if (gnutls_global_init() != GNUTLS_E_SUCCESS)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
local_send_buffer = malloc(LOCAL_SEND_BUFSIZE);
|
|
|
|
force_assert(NULL != local_send_buffer);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void mod_gnutls_free_gnutls (void)
|
|
|
|
{
|
|
|
|
if (!ssl_is_init) return;
|
|
|
|
|
2020-06-04 20:46:51 +00:00
|
|
|
gnutls_memset(session_ticket_keys, 0, sizeof(session_ticket_keys));
|
2020-05-14 06:03:14 +00:00
|
|
|
mod_gnutls_session_ticket_key_free();
|
2020-06-04 20:46:51 +00:00
|
|
|
stek_rotate_ts = 0;
|
2020-05-14 06:03:14 +00:00
|
|
|
|
|
|
|
gnutls_global_deinit();
|
|
|
|
|
|
|
|
free(local_send_buffer);
|
|
|
|
ssl_is_init = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
mod_gnutls_free_config_crts (gnutls_datum_t *d)
|
|
|
|
{
|
|
|
|
if (NULL == d) return;
|
|
|
|
gnutls_x509_crt_t *crts = (gnutls_x509_crt_t *)(void *)d->data;
|
|
|
|
unsigned int u = d->size;
|
|
|
|
for (unsigned int i = 0; i < u; ++i)
|
|
|
|
gnutls_x509_crt_deinit(crts[i]);
|
|
|
|
gnutls_free(crts);
|
|
|
|
gnutls_free(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
mod_gnutls_free_config_crls (gnutls_datum_t *d)
|
|
|
|
{
|
|
|
|
if (NULL == d) return;
|
|
|
|
gnutls_x509_crl_t *crls = (gnutls_x509_crl_t *)(void *)d->data;
|
|
|
|
unsigned int u = d->size;
|
|
|
|
for (unsigned int i = 0; i < u; ++i)
|
|
|
|
gnutls_x509_crl_deinit(crls[i]);
|
|
|
|
gnutls_free(crls);
|
|
|
|
gnutls_free(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static gnutls_datum_t *
|
|
|
|
mod_gnutls_load_config_crts (const char *fn, log_error_st *errh)
|
|
|
|
{
|
|
|
|
/*(very similar to other mod_gnutls_load_config_*())*/
|
|
|
|
if (!mod_gnutls_init_once_gnutls()) return NULL;
|
|
|
|
|
|
|
|
gnutls_datum_t f = { NULL, 0 };
|
|
|
|
int rc = mod_gnutls_load_file(fn, &f, errh);
|
|
|
|
if (rc < 0) return NULL;
|
|
|
|
gnutls_datum_t *d = gnutls_malloc(sizeof(gnutls_datum_t));
|
|
|
|
if (d == NULL) {
|
|
|
|
mod_gnutls_datum_wipe(&f);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
d->data = NULL;
|
|
|
|
d->size = 0;
|
|
|
|
rc = gnutls_x509_crt_list_import2((gnutls_x509_crt_t **)&d->data, &d->size,
|
|
|
|
&f, GNUTLS_X509_FMT_PEM,
|
|
|
|
GNUTLS_X509_CRT_LIST_SORT);
|
|
|
|
mod_gnutls_datum_wipe(&f);
|
|
|
|
if (rc < 0) {
|
|
|
|
elogf(errh, __FILE__, __LINE__, rc,
|
|
|
|
"gnutls_x509_crt_list_import2() %s", fn);
|
|
|
|
mod_gnutls_free_config_crts(d);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static gnutls_datum_t *
|
|
|
|
mod_gnutls_load_config_crls (const char *fn, log_error_st *errh)
|
|
|
|
{
|
|
|
|
/*(very similar to other mod_gnutls_load_config_*())*/
|
|
|
|
if (!mod_gnutls_init_once_gnutls()) return NULL;
|
|
|
|
|
|
|
|
gnutls_datum_t f = { NULL, 0 };
|
|
|
|
int rc = mod_gnutls_load_file(fn, &f, errh);
|
|
|
|
if (rc < 0) return NULL;
|
|
|
|
gnutls_datum_t *d = gnutls_malloc(sizeof(gnutls_datum_t));
|
|
|
|
if (d == NULL) {
|
|
|
|
mod_gnutls_datum_wipe(&f);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
d->data = NULL;
|
|
|
|
d->size = 0;
|
|
|
|
rc = gnutls_x509_crl_list_import2((gnutls_x509_crl_t **)&d->data, &d->size,
|
|
|
|
&f, GNUTLS_X509_FMT_PEM, 0);
|
|
|
|
mod_gnutls_datum_wipe(&f);
|
|
|
|
if (rc < 0) {
|
|
|
|
elogf(errh, __FILE__, __LINE__, rc,
|
|
|
|
"gnutls_x509_crl_list_import2() %s", fn);
|
|
|
|
mod_gnutls_free_config_crls(d);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static gnutls_privkey_t
|
|
|
|
mod_gnutls_load_config_pkey (const char *fn, log_error_st *errh)
|
|
|
|
{
|
|
|
|
/*(very similar to other mod_gnutls_load_config_*())*/
|
|
|
|
if (!mod_gnutls_init_once_gnutls()) return NULL;
|
|
|
|
|
|
|
|
gnutls_datum_t f = { NULL, 0 };
|
|
|
|
int rc = mod_gnutls_load_file(fn, &f, errh);
|
|
|
|
if (rc < 0) return NULL;
|
|
|
|
gnutls_privkey_t pkey;
|
|
|
|
rc = gnutls_privkey_init(&pkey);
|
|
|
|
if (rc < 0) {
|
|
|
|
mod_gnutls_datum_wipe(&f);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
rc = gnutls_privkey_import_x509_raw(pkey, &f, GNUTLS_X509_FMT_PEM, NULL, 0);
|
|
|
|
mod_gnutls_datum_wipe(&f);
|
|
|
|
if (rc < 0) {
|
|
|
|
elogf(errh, __FILE__, __LINE__, rc,
|
|
|
|
"gnutls_privkey_import_x509_raw() %s", fn);
|
|
|
|
gnutls_privkey_deinit(pkey);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pkey;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
mod_gnutls_free_config (server *srv, plugin_data * const p)
|
|
|
|
{
|
|
|
|
if (NULL != p->ssl_ctxs) {
|
|
|
|
gnutls_priority_t pcache_global_scope = p->ssl_ctxs->priority_cache;
|
|
|
|
/* free from $SERVER["socket"] (if not copy of global scope) */
|
|
|
|
for (uint32_t i = 1; i < srv->config_context->used; ++i) {
|
|
|
|
plugin_ssl_ctx * const s = p->ssl_ctxs + i;
|
|
|
|
if (s->priority_cache && s->priority_cache != pcache_global_scope) {
|
|
|
|
if (s->priority_cache)
|
|
|
|
gnutls_priority_deinit(s->priority_cache);
|
|
|
|
#if GNUTLS_VERSION_NUMBER < 0x030600
|
|
|
|
if (s->dh_params)
|
|
|
|
gnutls_dh_params_deinit(s->dh_params);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* free from global scope */
|
|
|
|
if (pcache_global_scope) {
|
|
|
|
if (p->ssl_ctxs[0].priority_cache)
|
|
|
|
gnutls_priority_deinit(p->ssl_ctxs[0].priority_cache);
|
|
|
|
#if GNUTLS_VERSION_NUMBER < 0x030600
|
|
|
|
if (p->ssl_ctxs[0].dh_params)
|
|
|
|
gnutls_dh_params_deinit(p->ssl_ctxs[0].dh_params);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
free(p->ssl_ctxs);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NULL == p->cvlist) return;
|
|
|
|
/* (init i to 0 if global context; to 1 to skip empty global context) */
|
|
|
|
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
|
|
|
|
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
|
|
|
|
for (; -1 != cpv->k_id; ++cpv) {
|
|
|
|
switch (cpv->k_id) {
|
|
|
|
case 0: /* ssl.pemfile */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL) {
|
|
|
|
plugin_cert *pc = cpv->v.v;
|
|
|
|
gnutls_certificate_free_credentials(pc->ssl_cred);
|
|
|
|
mod_gnutls_free_config_crts(pc->ssl_pemfile_x509);
|
|
|
|
gnutls_privkey_deinit(pc->ssl_pemfile_pkey);
|
|
|
|
free(pc);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 2: /* ssl.ca-file */
|
|
|
|
case 3: /* ssl.ca-dn-file */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL)
|
|
|
|
mod_gnutls_free_config_crts(cpv->v.v);
|
|
|
|
break;
|
|
|
|
case 4: /* ssl.ca-crl-file */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL)
|
|
|
|
mod_gnutls_free_config_crls(cpv->v.v);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FREE_FUNC(mod_gnutls_free)
|
|
|
|
{
|
|
|
|
plugin_data *p = p_d;
|
|
|
|
if (NULL == p->srv) return;
|
|
|
|
mod_gnutls_free_config(p->srv, p);
|
|
|
|
mod_gnutls_free_gnutls();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
mod_gnutls_merge_config_cpv (plugin_config * const pconf, const config_plugin_value_t * const cpv)
|
|
|
|
{
|
|
|
|
switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
|
|
|
|
case 0: /* ssl.pemfile */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL)
|
|
|
|
pconf->pc = cpv->v.v;
|
|
|
|
break;
|
|
|
|
case 1: /* ssl.privkey */
|
|
|
|
break;
|
|
|
|
case 2: /* ssl.ca-file */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL)
|
|
|
|
pconf->ssl_ca_file = cpv->v.v;
|
|
|
|
break;
|
|
|
|
case 3: /* ssl.ca-dn-file */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL)
|
|
|
|
pconf->ssl_ca_dn_file = cpv->v.v;
|
|
|
|
break;
|
|
|
|
case 4: /* ssl.ca-crl-file */
|
|
|
|
if (cpv->vtype == T_CONFIG_LOCAL)
|
|
|
|
pconf->ssl_ca_crl_file = cpv->v.v;
|
|
|
|
break;
|
|
|
|
case 5: /* ssl.read-ahead */
|
|
|
|
pconf->ssl_read_ahead = (0 != cpv->v.u);
|
|
|
|
break;
|
|
|
|
case 6: /* ssl.disable-client-renegotiation */
|
|
|
|
pconf->ssl_disable_client_renegotiation = (0 != cpv->v.u);
|
|
|
|
break;
|
|
|
|
case 7: /* ssl.verifyclient.activate */
|
|
|
|
pconf->ssl_verifyclient = (0 != cpv->v.u);
|
|
|
|
break;
|
|
|
|
case 8: /* ssl.verifyclient.enforce */
|
|
|
|
pconf->ssl_verifyclient_enforce = (0 != cpv->v.u);
|
|
|
|
break;
|
|
|
|
case 9: /* ssl.verifyclient.depth */
|
|
|
|
pconf->ssl_verifyclient_depth = (unsigned char)cpv->v.shrt;
|
|
|
|
break;
|
|
|
|
case 10:/* ssl.verifyclient.username */
|
|
|
|
pconf->ssl_verifyclient_username = cpv->v.b;
|
|
|
|
break;
|
|
|
|
case 11:/* ssl.verifyclient.exportcert */
|
|
|
|
pconf->ssl_verifyclient_export_cert = (0 != cpv->v.u);
|
|
|
|
break;
|
|
|
|
case 12:/* ssl.acme-tls-1 */
|
|
|
|
pconf->ssl_acme_tls_1 = cpv->v.b;
|
|
|
|
break;
|
2020-06-11 09:15:07 +00:00
|
|
|
case 13:/* ssl.stapling-file */
|
|
|
|
break;
|
|
|
|
case 14:/* debug.log-ssl-noise */
|
2020-05-14 06:03:14 +00:00
|
|
|
pconf->ssl_log_noise = (unsigned char)cpv->v.shrt;
|
|
|
|
break;
|
|
|
|
default:/* should not happen */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
mod_gnutls_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv)
|
|
|
|
{
|
|
|
|
do {
|
|
|
|
mod_gnutls_merge_config_cpv(pconf, cpv);
|
|
|
|
} while ((++cpv)->k_id != -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
mod_gnutls_patch_config (request_st * const r, plugin_config * const pconf)
|
|
|
|
{
|
|
|
|
plugin_data * const p = plugin_data_singleton;
|
|
|
|
memcpy(pconf, &p->defaults, sizeof(plugin_config));
|
|
|
|
for (int i = 1, used = p->nconfig; i < used; ++i) {
|
|
|
|
if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
|
|
|
|
mod_gnutls_merge_config(pconf, p->cvlist + p->cvlist[i].v.u2[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
mod_gnutls_verify_set_tlist (handler_ctx *hctx, int req)
|
|
|
|
{
|
|
|
|
/* XXX GnuTLS interfaces appear to be better for client-side use than for
|
|
|
|
* server-side use. If gnutls_x509_trust_list_t were available to attach
|
|
|
|
* to a gnutls_session_t (without copying), then there would not be race
|
|
|
|
* conditions swapping trust lists on the credential *shared* between
|
|
|
|
* connections when ssl.ca-dn-file and ssl.ca-file are both set. If both
|
|
|
|
* are set, current code attempts to set ssl.ca-dn-file right before sending
|
|
|
|
* client cert request, and sets ssl.ca-file right before client cert verify
|
|
|
|
*
|
|
|
|
* Architecture would be cleaner if the trust list for verifying client cert
|
|
|
|
* (gnutls_x509_trust_list_t) were available to attach to a gnutls_session_t
|
|
|
|
* instead of attaching to a gnutls_certificate_credentials_t.
|
|
|
|
*/
|
|
|
|
if (hctx->conf.pc->trust_inited) return GNUTLS_E_SUCCESS;
|
|
|
|
|
|
|
|
gnutls_datum_t *d;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* set trust list using ssl_ca_dn_file, if set, for client cert request
|
|
|
|
* (when req is true) (for CAs sent by server to client in cert request)
|
|
|
|
* (trust is later replaced by ssl_ca_file for client cert verification) */
|
|
|
|
d = req && hctx->conf.ssl_ca_dn_file
|
|
|
|
? hctx->conf.ssl_ca_dn_file
|
|
|
|
: hctx->conf.ssl_ca_file;
|
|
|
|
if (NULL == d) {
|
|
|
|
log_error(hctx->r->conf.errh, __FILE__, __LINE__,
|
|
|
|
"GnuTLS: can't verify client without ssl.ca-file "
|
|
|
|
"for TLS server name %s",
|
|
|
|
hctx->r->uri.authority.ptr); /*(might not be set yet if no SNI)*/
|
|
|
|
return GNUTLS_E_INTERNAL_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
gnutls_x509_trust_list_t tlist = NULL;
|
|
|
|
rc = gnutls_x509_trust_list_init(&tlist, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
elog(hctx->r->conf.errh, __FILE__, __LINE__, rc,
|
|
|
|
"gnutls_x509_trust_list_init()");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
gnutls_x509_crt_t *clist = (gnutls_x509_crt_t *)(void *)d->data;
|
|
|
|
rc = gnutls_x509_trust_list_add_cas(tlist, clist, d->size, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
elog(hctx->r->conf.errh, __FILE__, __LINE__, rc,
|
|
|
|
"gnutls_x509_trust_list_add_cas()");
|
|
|
|
gnutls_x509_trust_list_deinit(tlist, 0);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = hctx->conf.ssl_ca_crl_file;
|
|
|
|
if (NULL != d && req && hctx->conf.ssl_ca_dn_file) {
|
|
|
|
/*(check req and ssl_ca_dn_file to see if tlist will be replaced later,
|
|
|
|
* and, if so, defer setting crls until later)*/
|
|
|
|
gnutls_x509_crl_t *crl_list = (gnutls_x509_crl_t *)(void *)d->data;
|
|
|
|
rc = gnutls_x509_trust_list_add_crls(tlist, crl_list, d->size, 0, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
elog(hctx->r->conf.errh, __FILE__, __LINE__, rc,
|
|
|
|
"gnutls_x509_trust_list_add_crls()");
|
|
|
|
gnutls_x509_trust_list_deinit(tlist, 0);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* gnutls limitation; wasteful to have to copy into each cred */
|
|
|
|
/* (would be better to share list with session, instead of with cred) */
|
|
|
|
gnutls_certificate_credentials_t ssl_cred = hctx->conf.pc->ssl_cred;
|
|
|
|
gnutls_certificate_set_trust_list(ssl_cred, tlist, 0); /* transfer tlist */
|
|
|
|
|
|
|
|
/* (must flip trust lists back and forth b/w DN names and verify CAs) */
|
|
|
|
if (NULL == hctx->conf.ssl_ca_dn_file)
|
|
|
|
hctx->conf.pc->trust_inited = 1;
|
|
|
|
|
|
|
|
return GNUTLS_E_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
mod_gnutls_verify_cb (gnutls_session_t ssl)
|
|
|
|
{
|
|
|
|
handler_ctx * const hctx = gnutls_session_get_ptr(ssl);
|
|
|
|
if (!hctx->conf.ssl_verifyclient) return 0;
|
|
|
|
|
|
|
|
if (gnutls_auth_client_get_type(ssl) != GNUTLS_CRD_CERTIFICATE)
|
|
|
|
return GNUTLS_E_SUCCESS;
|
|
|
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* gnutls limitation; wasteful to have to copy into each cred */
|
|
|
|
/* (would be better to share list with session, instead of with cred) */
|
|
|
|
if (hctx->conf.ssl_ca_dn_file) {
|
|
|
|
rc = mod_gnutls_verify_set_tlist(hctx, 0); /* for client cert verify */
|
|
|
|
if (rc < 0) return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* gnutls_certificate_verify_peers2() includes processing OCSP staping,
|
|
|
|
* as well as certificate depth verification before getting internal
|
|
|
|
* flags and calling gnutls_x509_trust_list_verify_crt2()
|
|
|
|
* advanced reference:
|
|
|
|
* gnutls lib/cert-sessions.c:_gnutls_x509_cert_verify_peers()
|
|
|
|
* XXX: if GnuTLS provided a more advanced interface which permitted
|
|
|
|
* providing trust list, verify depth, and flags, we could avoid copying
|
|
|
|
* ca chain and crls into each credential, using
|
|
|
|
* gnutls_x509_trust_list_add_cas()
|
|
|
|
* gnutls_x509_trust_list_add_crls()
|
|
|
|
* gnutls_x509_trust_list_verify_crt2()
|
|
|
|
* See also GnuTLS manual Section 7.3.4 Advanced certificate verification
|
|
|
|
*/
|
|
|
|
|
|
|
|
rc = gnutls_certificate_verify_peers2(ssl, &hctx->verify_status);
|
|
|
|
if (rc < 0) return rc;
|
|
|
|
|
|
|
|
if (hctx->verify_status == 0 && hctx->conf.ssl_ca_dn_file) {
|
|
|
|
/* verify that client cert is issued by CA in ssl.ca-dn-file
|
|
|
|
* if both ssl.ca-dn-file and ssl.ca-file were configured */
|
|
|
|
gnutls_x509_crt_t *CA_list =
|
|
|
|
(gnutls_x509_crt_t *)&hctx->conf.ssl_ca_dn_file->data;
|
|
|
|
unsigned int len = hctx->conf.ssl_ca_dn_file->size;
|
|
|
|
unsigned int i;
|
|
|
|
gnutls_x509_dn_t issuer, subject;
|
|
|
|
unsigned int crt_size = 0;
|
|
|
|
const gnutls_datum_t *crts;
|
|
|
|
gnutls_x509_crt_t crt = NULL;
|
|
|
|
crts = gnutls_certificate_get_peers(ssl, &crt_size);
|
|
|
|
if (0 == crt_size
|
|
|
|
|| gnutls_x509_crt_init(&crt) < 0
|
|
|
|
|| gnutls_x509_crt_import(crt, &crts[0], GNUTLS_X509_FMT_DER) < 0
|
|
|
|
|| gnutls_x509_crt_get_subject(crt, &issuer) < 0)
|
|
|
|
len = 0; /*(trigger failure path below)*/
|
|
|
|
for (i = 0; i < len; ++i) {
|
|
|
|
if (gnutls_x509_crt_get_subject(CA_list[i], &subject) >= 0
|
|
|
|
&& 0 == memcmp(&subject, &issuer, sizeof(issuer)))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (i == len)
|
|
|
|
hctx->verify_status |= GNUTLS_CERT_SIGNER_NOT_CA;
|
|
|
|
if (crt) gnutls_x509_crt_deinit(crt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hctx->verify_status != 0 && hctx->conf.ssl_verifyclient_enforce) {
|
|
|
|
/* (not looping on GNUTLS_E_INTERRUPTED or GNUTLS_E_AGAIN
|
|
|
|
* since we do not want to block here (and not expecting to have to))*/
|
|
|
|
(void)gnutls_alert_send(ssl, GNUTLS_AL_FATAL, GNUTLS_A_ACCESS_DENIED);
|
|
|
|
return GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return GNUTLS_E_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-11 09:15:07 +00:00
|
|
|
#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 (pc->ssl_stapling_nextts < cur_ts) {
|
2020-06-14 14:54:52 +00:00
|
|
|
#if 0
|
2020-06-11 09:15:07 +00:00
|
|
|
/* discard expired OCSP stapling response */
|
|
|
|
/* Does GnuTLS detect expired OCSP response? */
|
|
|
|
/* or must we rebuild gnutls_certificate_credentials_t ? */
|
2020-06-14 14:54:52 +00:00
|
|
|
#endif
|
|
|
|
if (pc->must_staple) {
|
|
|
|
log_error(srv->errh, __FILE__, __LINE__,
|
|
|
|
"certificate marked OCSP Must-Staple, "
|
|
|
|
"but OCSP response expired from ssl.stapling-file %s",
|
|
|
|
pc->ssl_stapling_file->ptr);
|
|
|
|
}
|
2020-06-11 09:15:07 +00:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-14 14:54:52 +00:00
|
|
|
static int
|
|
|
|
mod_gnutls_crt_must_staple (gnutls_x509_crt_t crt)
|
|
|
|
{
|
|
|
|
/* Look for TLS features X.509 extension with value 5
|
|
|
|
* RFC 7633 https://tools.ietf.org/html/rfc7633#appendix-A
|
|
|
|
* 5 = OCSP Must-Staple (security mechanism) */
|
|
|
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
#if GNUTLS_VERSION_NUMBER < 0x030501
|
|
|
|
|
|
|
|
unsigned int i;
|
|
|
|
char oid[128];
|
|
|
|
size_t oidsz;
|
|
|
|
for (i = 0; ; ++i) {
|
|
|
|
oidsz = sizeof(oid);
|
|
|
|
rc = gnutls_x509_crt_get_extension_info(crt, i, oid, &oidsz, NULL);
|
|
|
|
if (rc < 0 || 0 == strcmp(oid, GNUTLS_X509EXT_OID_TLSFEATURES)) break;
|
|
|
|
}
|
|
|
|
/* ext not found if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) */
|
|
|
|
if (rc < 0) return rc;
|
|
|
|
|
|
|
|
gnutls_datum_t der = { NULL, 0 };
|
|
|
|
rc = gnutls_x509_crt_get_extension_data2(crt, i, &der);
|
|
|
|
if (rc < 0) return rc;
|
|
|
|
|
|
|
|
/* DER encoding (Tag, Length, Value (TLV)) expecting: 30:03:02:01:05
|
|
|
|
* [TL[TLV]] (30=Sequence, Len=03, (02=Integer, Len=01, Value=05))
|
|
|
|
* XXX: This is not future-proof if TLS feature list values are extended */
|
|
|
|
/*const char must_staple[] = { 0x30, 0x03, 0x02, 0x01, 0x05 };*/
|
|
|
|
/*rc = (der.size == 5 && 0 == memcmp(der.data, must_staple, 5))*/
|
|
|
|
rc = (der.size >= 5 && der.data[0] == 0x30 && der.data[1] >= 0x03
|
|
|
|
&& der.data[2] == 0x2 && der.data[3] == 0x1 && der.data[4] == 0x5)
|
|
|
|
? 1
|
|
|
|
: 0;
|
|
|
|
|
|
|
|
gnutls_free(der.data);
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
gnutls_x509_tlsfeatures_t f;
|
|
|
|
rc = gnutls_x509_tlsfeatures_init(&f);
|
|
|
|
if (rc < 0) return rc;
|
|
|
|
rc = gnutls_x509_tlsfeatures_add(f, 5); /* 5 = OCSP Must-Staple */
|
|
|
|
if (rc < 0) return rc;
|
|
|
|
rc = (0 != gnutls_x509_tlsfeatures_check_crt(f, crt));
|
|
|
|
gnutls_x509_tlsfeatures_deinit(f);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return rc; /* 1 if OCSP Must-Staple found; 0 if not */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-14 06:03:14 +00:00
|
|
|
static int
|
|
|
|
mod_gnutls_construct_crt_chain (plugin_cert *pc, gnutls_datum_t *d, log_error_st *errh)
|
|
|
|
{
|
|
|
|
/* Historically, openssl will use the cert chain in (SSL_CTX *) if a cert
|
|
|
|
* does not have a chain configured in (SSL *). Attempt to provide
|
|
|
|
* compatible behavior here. This may be called after startup since
|
|
|
|
* ssl.pemfile might be defined in a config scope without ssl.ca-file,
|
|
|
|
* and at runtime is when ssl.ca-file (e.g. from matching $SERVER["socket"])
|
|
|
|
* would be known along with ssl.pemfile (e.g. from $HTTP["host"]) */
|
|
|
|
|
|
|
|
gnutls_certificate_credentials_t ssl_cred;
|
|
|
|
int rc = gnutls_certificate_allocate_credentials(&ssl_cred);
|
|
|
|
if (rc < 0) return rc;
|
|
|
|
unsigned int ncrt = (d ? d->size : 0);
|
|
|
|
unsigned int off = (d == pc->ssl_pemfile_x509) ? 0 : 1;
|
|
|
|
gnutls_pcert_st * const pcert_list =
|
|
|
|
gnutls_malloc(sizeof(gnutls_pcert_st) * (off+ncrt));
|
|
|
|
if (NULL == pcert_list) {
|
|
|
|
gnutls_certificate_free_credentials(ssl_cred);
|
|
|
|
return GNUTLS_E_MEMORY_ERROR;
|
|
|
|
}
|
|
|
|
memset(pcert_list, 0, sizeof(gnutls_pcert_st) * (off+ncrt));
|
|
|
|
rc = 0;
|
|
|
|
|
|
|
|
if (off) { /*(add crt to chain if different from d)*/
|
|
|
|
/*assert(pc->ssl_pemfile_x509->size == 1)*/
|
|
|
|
gnutls_x509_crt_t *crts =
|
|
|
|
(gnutls_x509_crt_t *)(void *)pc->ssl_pemfile_x509->data;
|
|
|
|
rc = gnutls_pcert_import_x509(pcert_list, crts[0], 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 == rc && ncrt) {
|
|
|
|
gnutls_x509_crt_t *crts = (gnutls_x509_crt_t *)(void *)d->data;
|
|
|
|
#if GNUTLS_VERSION_NUMBER < 0x030400
|
|
|
|
/*(GNUTLS_X509_CRT_LIST_SORT not needed; crts sorted when file read)*/
|
|
|
|
rc = gnutls_pcert_import_x509_list(pcert_list+off, crts, &ncrt, 0);
|
|
|
|
#else /* manually import list, but note that sorting is not implemented */
|
|
|
|
rc = 0;
|
|
|
|
for (unsigned int i = 0; i < ncrt; ++i) {
|
|
|
|
rc = gnutls_pcert_import_x509(pcert_list+off+i, crts[i], 0);
|
|
|
|
if (rc < 0) break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
ncrt += off;
|
|
|
|
if (0 == rc)
|
|
|
|
rc = gnutls_certificate_set_key(ssl_cred, NULL, 0, pcert_list, ncrt,
|
|
|
|
pc->ssl_pemfile_pkey);
|
|
|
|
if (rc < 0) {
|
|
|
|
for (unsigned int i = 0; i < ncrt; ++i)
|
|
|
|
gnutls_pcert_deinit(pcert_list+i);
|
|
|
|
gnutls_free(pcert_list);
|
|
|
|
gnutls_certificate_free_credentials(ssl_cred);
|
|
|
|
elog(errh, __FILE__, __LINE__, rc, "gnutls_certificate_set_key()");
|
|
|
|
return rc; |