diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index 25f238d..f75caac 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -5,7 +5,7 @@ common_cflags += $(GTHREAD_CFLAGS) $(LIBEV_CFLAGS) $(LUA_CFLAGS) common_libs = $(GTHREAD_LIBS) $(LIBEV_LIBS) $(LUA_LIBS) common_ldflags = -module -export-dynamic -avoid-version -no-undefined $(common_libs) common_libadd = ../common/liblighttpd2-common.la ../main/liblighttpd2-shared.la -EXTRA_DIST= +EXTRA_DIST=ssl-session-db.h luadir = $(datarootdir)/lighttpd2/lua diff --git a/src/modules/mod_gnutls.c b/src/modules/mod_gnutls.c index 7b53fbd..3e3245c 100644 --- a/src/modules/mod_gnutls.c +++ b/src/modules/mod_gnutls.c @@ -3,6 +3,7 @@ #include #include "gnutls_filter.h" +#include "ssl-session-db.h" #include #include @@ -34,6 +35,8 @@ struct mod_connection_ctx { struct mod_context { gint refcount; + liSSLSessionDB *session_db; + gnutls_certificate_credentials_t server_cert; gnutls_dh_params_t dh_params; gnutls_priority_t server_priority; @@ -61,6 +64,8 @@ static void mod_gnutls_context_release(mod_context *ctx) { ctx->ticket_key.size = 0; } #endif + li_ssl_session_db_free(ctx->session_db); + ctx->session_db = NULL; g_slice_free(mod_context, ctx); } @@ -211,13 +216,36 @@ static int post_client_hello_cb(liGnuTLSFilter *f, gpointer data) { return GNUTLS_E_SUCCESS; } +static int session_db_store_cb(void *_sdb, gnutls_datum_t key, gnutls_datum_t data) { + liSSLSessionDB *sdb = _sdb; + li_ssl_session_db_store(sdb, key.data, key.size, data.data, data.size); + return 0; +} +static int session_db_remove_cb(void *_sdb, gnutls_datum_t key) { + liSSLSessionDB *sdb = _sdb; + li_ssl_session_db_remove(sdb, key.data, key.size); + return 0; +} +static gnutls_datum_t session_db_retrieve_cb(void *_sdb, gnutls_datum_t key) { + liSSLSessionDB *sdb = _sdb; + liSSLSessionDBData *data = li_ssl_session_db_lookup(sdb, key.data, key.size); + gnutls_datum_t result = { NULL, 0 }; + if (NULL != data) { + result.size = data->size; + result.data = gnutls_malloc(result.size); + memcpy(result.data, data->data, result.size); + li_ssl_session_db_data_release(data); + } + return result; +} + static const liGnuTLSFilterCallbacks filter_callbacks = { handshake_cb, close_cb, post_client_hello_cb }; -static void gnutlc_tcp_finished(liConnection *con, gboolean aborted) { +static void gnutls_tcp_finished(liConnection *con, gboolean aborted) { mod_connection_ctx *conctx = con->con_sock.data; UNUSED(aborted); @@ -253,7 +281,7 @@ static liThrottleState* gnutls_tcp_throttle_in(liConnection *con) { } static const liConnectionSocketCallbacks gnutls_tcp_cbs = { - gnutlc_tcp_finished, + gnutls_tcp_finished, gnutls_tcp_throttle_out, gnutls_tcp_throttle_in }; @@ -285,6 +313,13 @@ static gboolean mod_gnutls_con_new(liConnection *con, int fd) { goto fail; } + if (NULL != ctx->session_db) { + gnutls_db_set_ptr(session, ctx->session_db); + gnutls_db_set_remove_function(session, session_db_remove_cb); + gnutls_db_set_retrieve_function(session, session_db_retrieve_cb); + gnutls_db_set_store_function(session, session_db_store_cb); + } + #ifdef HAVE_SESSION_TICKET if (GNUTLS_E_SUCCESS != (r = gnutls_session_ticket_enable_server(session, &ctx->ticket_key))) { ERROR(srv, "gnutls_session_ticket_enable_server (%s): %s", @@ -358,6 +393,7 @@ static gboolean gnutls_setup(liServer *srv, liPlugin* p, liValue *val, gpointer *pemfile = NULL, *ca_file = NULL; gboolean protect_against_beast = TRUE; + gint64 session_db_size = 256; UNUSED(p); UNUSED(userdata); @@ -406,6 +442,12 @@ static gboolean gnutls_setup(liServer *srv, liPlugin* p, liValue *val, gpointer return FALSE; } protect_against_beast = htval->data.boolean; + } else if (g_str_equal(htkey->str, "session-db-size")) { + if (htval->type != LI_VALUE_NUMBER) { + ERROR(srv, "%s", "gnutls session-db-size expects an integer as parameter"); + return FALSE; + } + session_db_size = htval->data.number; } } @@ -430,6 +472,8 @@ static gboolean gnutls_setup(liServer *srv, liPlugin* p, liValue *val, gpointer goto error_free_ctx; } + if (session_db_size > 0) ctx->session_db = li_ssl_session_db_new(session_db_size); + if (NULL != dh_params_file) { gchar *contents = NULL; gsize length = 0; diff --git a/src/modules/ssl-session-db.h b/src/modules/ssl-session-db.h new file mode 100644 index 0000000..6464841 --- /dev/null +++ b/src/modules/ssl-session-db.h @@ -0,0 +1,137 @@ +#ifndef _LIGHTTPD_SSL_SESSION_DB_H_ +#define _LIGHTTPD_SSL_SESSION_DB_H_ + +#include + +typedef struct liSSLSessionDBKey liSSLSessionDBKey; +struct liSSLSessionDBKey { + GList keys_link; + size_t size; + unsigned char data[]; +}; + +typedef struct liSSLSessionDBData liSSLSessionDBData; +struct liSSLSessionDBData { + gint refcount; + size_t size; + unsigned char data[]; +}; + +typedef struct liSSLSessionDB liSSLSessionDB; +struct liSSLSessionDB { + size_t max_entries; + GQueue keys; /* move recently added/used entries to end */ + GMutex *mutex; + GHashTable *db; /* liSSLSessionDBData -> liSSLSessionDBData */ +}; + +INLINE void li_ssl_session_db_data_release(liSSLSessionDBData *d) { + if (NULL == d) return; + assert(g_atomic_int_get(&d->refcount) > 0); + if (g_atomic_int_dec_and_test(&d->refcount)) { + g_slice_free1(d->size + sizeof(liSSLSessionDBData), d); + } +} + +INLINE void li_ssl_session_db_data_free_cb(gpointer data) { + li_ssl_session_db_data_release(data); +} + +INLINE liSSLSessionDBData* li_ssl_session_db_data_new(const unsigned char *data, size_t size) { + liSSLSessionDBData *d = g_slice_alloc0(size + sizeof(liSSLSessionDBData)); + d->refcount = 1; + d->size = size; + memcpy(d->data, data, size); + return d; +} + +INLINE void li_ssl_session_db_key_free_cb(gpointer data) { + liSSLSessionDBKey *d = data; + liSSLSessionDB *sdb; + if (NULL == d) return; + sdb = d->keys_link.data; + if (NULL != sdb) g_queue_unlink(&sdb->keys, &d->keys_link); + g_slice_free1(d->size + sizeof(liSSLSessionDBKey), d); +} + +INLINE guint li_ssl_session_db_key_hash(gconstpointer data) { + const liSSLSessionDBKey *d = data; + const GString s = li_const_gstring((const gchar*)d->data, d->size); + return g_string_hash(&s); +} + +INLINE gboolean li_ssl_session_db_key_equal(gconstpointer a, gconstpointer b) { + const liSSLSessionDBKey *da = a, *db = b; + if (da->size != db->size) return FALSE; + return 0 == memcmp(da->data, db->data, da->size); +} + +INLINE liSSLSessionDBKey* li_ssl_session_db_key_new(const unsigned char *data, size_t size) { + liSSLSessionDBKey *d = g_slice_alloc0(size + sizeof(liSSLSessionDBKey)); + d->size = size; + memcpy(d->data, data, size); + return d; +} + + +INLINE liSSLSessionDB* li_ssl_session_db_new(size_t max_entries) { + liSSLSessionDB *sdb = g_slice_new0(liSSLSessionDB); + sdb->max_entries = max_entries; + sdb->mutex = g_mutex_new(); + sdb->db = g_hash_table_new_full(li_ssl_session_db_key_hash, li_ssl_session_db_key_equal, + li_ssl_session_db_key_free_cb, li_ssl_session_db_data_free_cb); + return sdb; +} + +INLINE void li_ssl_session_db_free(liSSLSessionDB* sdb) { + if (NULL == sdb) return; + g_mutex_free(sdb->mutex); + sdb->mutex = NULL; + g_hash_table_destroy(sdb->db); + sdb->db = NULL; + g_slice_free(liSSLSessionDB, sdb); +} + +INLINE void li_ssl_session_db_store(liSSLSessionDB *sdb, const unsigned char *key, size_t keylen, const unsigned char *value, size_t valuelen) { + liSSLSessionDBData *dvalue = li_ssl_session_db_data_new(value, valuelen); + liSSLSessionDBKey *dkey = li_ssl_session_db_key_new(key, keylen); + g_mutex_lock(sdb->mutex); + dkey->keys_link.data = sdb; + g_queue_push_tail_link(&sdb->keys, &dkey->keys_link); + g_hash_table_replace(sdb->db, dkey, dvalue); + while (sdb->keys.length > sdb->max_entries) { + liSSLSessionDBKey *purge_key = LI_CONTAINER_OF(sdb->keys.head, liSSLSessionDBKey, keys_link); + g_hash_table_remove(sdb->db, purge_key); + } + g_mutex_unlock(sdb->mutex); +} + +INLINE liSSLSessionDBData* li_ssl_session_db_lookup(liSSLSessionDB *sdb, const unsigned char *key, size_t keylen) { + liSSLSessionDBData *dvalue = NULL; + liSSLSessionDBKey *dkey = li_ssl_session_db_key_new(key, keylen); + gpointer orig_key, value; + + g_mutex_lock(sdb->mutex); + if (g_hash_table_lookup_extended(sdb->db, dkey, &orig_key, &value)) { + liSSLSessionDBKey *k = orig_key; + g_queue_unlink(&sdb->keys, &k->keys_link); + g_queue_push_tail_link(&sdb->keys, &k->keys_link); + + dvalue = value; + assert(g_atomic_int_get(&dvalue->refcount) > 0); + g_atomic_int_inc(&dvalue->refcount); + } + g_mutex_unlock(sdb->mutex); + li_ssl_session_db_key_free_cb(dkey); + return dvalue; +} + +INLINE void li_ssl_session_db_remove(liSSLSessionDB *sdb, const unsigned char *key, size_t keylen) { + liSSLSessionDBKey *dkey = li_ssl_session_db_key_new(key, keylen); + g_mutex_lock(sdb->mutex); + g_hash_table_remove(sdb->db, dkey); + g_mutex_unlock(sdb->mutex); + li_ssl_session_db_key_free_cb(dkey); +} + +#endif