Implemented a TLS session cache
authorMartin Willi <martin@revosec.ch>
Sat, 31 Dec 2011 00:39:17 +0000 (01:39 +0100)
committerMartin Willi <martin@revosec.ch>
Sat, 31 Dec 2011 12:14:49 +0000 (13:14 +0100)
src/libtls/Makefile.am
src/libtls/tls_cache.c [new file with mode: 0644]
src/libtls/tls_cache.h [new file with mode: 0644]

index 36335e8..4cc1a1b 100644 (file)
@@ -11,6 +11,7 @@ libtls_la_SOURCES = \
        tls_prf.h tls_prf.c \
        tls_socket.h tls_socket.c \
        tls_eap.h tls_eap.c \
+       tls_cache.h tls_cache.c \
        tls_peer.h tls_peer.c \
        tls_server.h tls_server.c \
        tls_handshake.h tls_application.h tls.h tls.c
diff --git a/src/libtls/tls_cache.c b/src/libtls/tls_cache.c
new file mode 100644 (file)
index 0000000..a89201a
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2011 Martin Willi
+ * Copyright (C) 2011 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+#include "tls_cache.h"
+
+#include <debug.h>
+#include <utils/linked_list.h>
+#include <utils/hashtable.h>
+#include <threading/rwlock.h>
+
+typedef struct private_tls_cache_t private_tls_cache_t;
+
+/**
+ * Private data of an tls_cache_t object.
+ */
+struct private_tls_cache_t {
+
+       /**
+        * Public tls_cache_t interface.
+        */
+       tls_cache_t public;
+
+       /**
+        * Mapping session => entry_t, fast lookup by session
+        */
+       hashtable_t *table;
+
+       /**
+        * List containing all entries
+        */
+       linked_list_t *list;
+
+       /**
+        * Lock to list and table
+        */
+       rwlock_t *lock;
+
+       /**
+        * Session limit
+        */
+       u_int max_sessions;
+
+       /**
+        * maximum age of a session, in seconds
+        */
+       u_int max_age;
+};
+
+/**
+ * Hashtable entry
+ */
+typedef struct {
+       /** session identifier */
+       chunk_t session;
+       /** master secret */
+       chunk_t master;
+       /** TLS cipher suite */
+       tls_cipher_suite_t suite;
+       /** optional identity this entry is bound to */
+       identification_t *id;
+       /** time of add */
+       time_t t;
+} entry_t;
+
+/**
+ * Destroy an entry
+ */
+static void entry_destroy(entry_t *entry)
+{
+       chunk_clear(&entry->session);
+       chunk_clear(&entry->master);
+       DESTROY_IF(entry->id);
+       free(entry);
+}
+
+/**
+ * Hashtable hash function
+ */
+static u_int hash(chunk_t *key)
+{
+       return chunk_hash(*key);
+}
+
+/**
+ * Hashtable equals function
+ */
+static bool equals(chunk_t *a, chunk_t *b)
+{
+       return chunk_equals(*a, *b);
+}
+
+METHOD(tls_cache_t, create_, void,
+       private_tls_cache_t *this, chunk_t session, identification_t *id,
+       chunk_t master, tls_cipher_suite_t suite)
+{
+       entry_t *entry;
+
+       INIT(entry,
+               .session = chunk_clone(session),
+               .master = chunk_clone(master),
+               .suite = suite,
+               .id = id ? id->clone(id) : NULL,
+               .t = time_monotonic(NULL),
+       );
+
+       this->lock->write_lock(this->lock);
+       this->list->insert_first(this->list, entry);
+       this->table->put(this->table, &entry->session, entry);
+       if (this->list->get_count(this->list) > this->max_sessions &&
+               this->list->remove_last(this->list, (void**)&entry) == SUCCESS)
+       {
+               DBG2(DBG_TLS, "session limit of %u reached, deleting %#B",
+                        this->max_sessions, &entry->session);
+               this->table->remove(this->table, &entry->session);
+               entry_destroy(entry);
+       }
+       this->lock->unlock(this->lock);
+
+       DBG2(DBG_TLS, "created TLS session %#B, %d sessions",
+                &session, this->list->get_count(this->list));
+}
+
+METHOD(tls_cache_t, lookup, tls_cipher_suite_t,
+       private_tls_cache_t *this, chunk_t session, identification_t *id,
+       chunk_t* master)
+{
+       tls_cipher_suite_t suite = 0;
+       entry_t *entry;
+       time_t now;
+       u_int age;
+
+       now = time_monotonic(NULL);
+
+       this->lock->write_lock(this->lock);
+       entry = this->table->get(this->table, &session);
+       if (entry)
+       {
+               age = now - entry->t;
+               if (age <= this->max_age)
+               {
+                       if (!id || !entry->id || id->equals(id, entry->id))
+                       {
+                               *master = chunk_clone(entry->master);
+                               suite = entry->suite;
+                       }
+               }
+               else
+               {
+                       DBG2(DBG_TLS, "TLS session %#B expired: %u seconds", &session, age);
+               }
+       }
+       this->lock->unlock(this->lock);
+
+       if (suite)
+       {
+               DBG2(DBG_TLS, "resuming TLS session %#B, age %u seconds", &session, age);
+       }
+       return suite;
+}
+
+METHOD(tls_cache_t, check, chunk_t,
+       private_tls_cache_t *this, identification_t *id)
+{
+       chunk_t session = chunk_empty;
+       enumerator_t *enumerator;
+       entry_t *entry;
+       time_t now;
+
+       now = time_monotonic(NULL);
+       this->lock->read_lock(this->lock);
+       enumerator = this->list->create_enumerator(this->list);
+       while (enumerator->enumerate(enumerator, &entry))
+       {
+               if (entry->t + this->max_age >= now &&
+                       entry->id && id->equals(id, entry->id))
+               {
+                       session = chunk_clone(entry->session);
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+       this->lock->unlock(this->lock);
+
+       return session;
+}
+
+METHOD(tls_cache_t, destroy, void,
+       private_tls_cache_t *this)
+{
+       entry_t *entry;
+
+       while (this->list->remove_last(this->list, (void**)&entry) == SUCCESS)
+       {
+               entry_destroy(entry);
+       }
+       this->list->destroy(this->list);
+       this->table->destroy(this->table);
+       this->lock->destroy(this->lock);
+       free(this);
+}
+
+/**
+ * See header
+ */
+tls_cache_t *tls_cache_create(u_int max_sessions, u_int max_age)
+{
+       private_tls_cache_t *this;
+
+       INIT(this,
+               .public = {
+                       .create = _create_,
+                       .lookup = _lookup,
+                       .check = _check,
+                       .destroy = _destroy,
+               },
+               .table = hashtable_create((hashtable_hash_t)hash,
+                                                                 (hashtable_equals_t)equals, 8),
+               .list = linked_list_create(),
+               .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
+               .max_sessions = max_sessions,
+               .max_age = max_age,
+       );
+
+       return &this->public;
+}
diff --git a/src/libtls/tls_cache.h b/src/libtls/tls_cache.h
new file mode 100644 (file)
index 0000000..ea4e201
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 Martin Willi
+ * Copyright (C) 2011 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+/**
+ * @defgroup tls_cache tls_cache
+ * @{ @ingroup libtls
+ */
+
+#ifndef TLS_CACHE_H_
+#define TLS_CACHE_H_
+
+typedef struct tls_cache_t tls_cache_t;
+
+#include "tls_crypto.h"
+
+/**
+ * TLS session cache facility.
+ */
+struct tls_cache_t {
+
+       /**
+        * Create a new TLS session entry.
+        *
+        * @param session               session identifier
+        * @param id                    identity the session is bound to
+        * @param master                TLS master secret
+        * @param suite                 TLS cipher suite of the session
+        */
+       void (*create)(tls_cache_t *this, chunk_t session, identification_t *id,
+                                  chunk_t master, tls_cipher_suite_t suite);
+
+       /**
+        * Look up a TLS session entry.
+        *
+        * @param session               session ID to find
+        * @param id                    identity the session is bound to
+        * @param master                gets allocated master secret, if session found
+        * @return                              TLS suite of session, 0 if none found
+        */
+       tls_cipher_suite_t (*lookup)(tls_cache_t *this, chunk_t session,
+                                                                identification_t *id, chunk_t* master);
+
+       /**
+        * Check if we have a session for a given identity.
+        *
+        * @param id                    identity to check
+        * @return                              allocated session ID, or chunk_empty
+        */
+       chunk_t (*check)(tls_cache_t *this, identification_t *id);
+
+       /**
+        * Destroy a tls_cache_t.
+        */
+       void (*destroy)(tls_cache_t *this);
+};
+
+/**
+ * Create a tls_cache instance.
+ *
+ * @param max_sessions         maximum number of sessions to store
+ * @param max_age                      maximum age of a session, in seconds
+ * @return                                     tls cache
+ */
+tls_cache_t *tls_cache_create(u_int max_sessions, u_int max_age);
+
+#endif /** TLS_CACHE_H_ @}*/