diff --git a/src/libtls/Makefile.am b/src/libtls/Makefile.am index 36335e84b..4cc1a1bdb 100644 --- a/src/libtls/Makefile.am +++ b/src/libtls/Makefile.am @@ -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 index 000000000..a89201ad7 --- /dev/null +++ b/src/libtls/tls_cache.c @@ -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 . + * + * 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 +#include +#include +#include + +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 index 000000000..ea4e2013e --- /dev/null +++ b/src/libtls/tls_cache.h @@ -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 . + * + * 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_ @}*/