[glib-networking] gnutls: add a session cache
- From: Dan Winship <danw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib-networking] gnutls: add a session cache
- Date: Fri, 10 Dec 2010 15:00:46 +0000 (UTC)
commit 7cc24829d2ae2f35d32f7cdf37871ee77409860d
Author: Dan Winship <danw gnome org>
Date: Fri Dec 10 15:55:21 2010 +0100
gnutls: add a session cache
TLS allows a client and server to bypass the full handshake on the
second and successive connections. Implement that, for the client
side, if the server is willing.
tls/gnutls/gtlsbackend-gnutls.c | 120 ++++++++++++++++++++++++++++++
tls/gnutls/gtlsbackend-gnutls.h | 6 ++
tls/gnutls/gtlsclientconnection-gnutls.c | 79 ++++++++++++++++++++
3 files changed, 205 insertions(+), 0 deletions(-)
---
diff --git a/tls/gnutls/gtlsbackend-gnutls.c b/tls/gnutls/gtlsbackend-gnutls.c
index 9511ae8..da15b01 100644
--- a/tls/gnutls/gtlsbackend-gnutls.c
+++ b/tls/gnutls/gtlsbackend-gnutls.c
@@ -211,6 +211,126 @@ g_tls_backend_gnutls_get_system_ca_list_gnutls (gnutls_x509_crt_t **cas,
#endif
}
+/* Session cache support; all the details are sort of arbitrary. Note
+ * that having session_cache_cleanup() be a little bit slow isn't the
+ * end of the world, since it will still be faster than the network
+ * is. (NSS uses a linked list for its cache...)
+ */
+
+G_LOCK_DEFINE_STATIC (session_cache_lock);
+GHashTable *session_cache;
+
+#define SESSION_CACHE_MAX_SIZE 50
+#define SESSION_CACHE_MAX_AGE (60 * 60) /* one hour */
+
+typedef struct {
+ gchar *session_id;
+ GByteArray *session_data;
+ time_t last_used;
+} GTlsBackendGnutlsCacheData;
+
+static void
+session_cache_cleanup (void)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ GTlsBackendGnutlsCacheData *cache_data;
+ time_t expired = time (NULL) - SESSION_CACHE_MAX_AGE;
+
+ g_hash_table_iter_init (&iter, session_cache);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ cache_data = value;
+ if (cache_data->last_used < expired)
+ g_hash_table_iter_remove (&iter);
+ }
+}
+
+static void
+cache_data_free (gpointer data)
+{
+ GTlsBackendGnutlsCacheData *cache_data = data;
+
+ g_free (cache_data->session_id);
+ g_byte_array_unref (cache_data->session_data);
+ g_slice_free (GTlsBackendGnutlsCacheData, cache_data);
+}
+
+void
+g_tls_backend_gnutls_cache_session_data (const gchar *session_id,
+ guchar *session_data,
+ gsize session_data_length)
+{
+ GTlsBackendGnutlsCacheData *cache_data;
+
+ G_LOCK (session_cache_lock);
+
+ if (!session_cache)
+ session_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, cache_data_free);
+
+ cache_data = g_hash_table_lookup (session_cache, session_id);
+ if (cache_data)
+ {
+ if (cache_data->session_data->len == session_data_length &&
+ memcmp (cache_data->session_data->data,
+ session_data, session_data_length) == 0)
+ {
+ cache_data->last_used = time (NULL);
+ G_UNLOCK (session_cache_lock);
+ return;
+ }
+
+ g_byte_array_set_size (cache_data->session_data, 0);
+ }
+ else
+ {
+ if (g_hash_table_size (session_cache) >= SESSION_CACHE_MAX_SIZE)
+ session_cache_cleanup ();
+
+ cache_data = g_slice_new (GTlsBackendGnutlsCacheData);
+ cache_data->session_id = g_strdup (session_id);
+ cache_data->session_data = g_byte_array_sized_new (session_data_length);
+
+ g_hash_table_insert (session_cache, cache_data->session_id, cache_data);
+ }
+
+ g_byte_array_append (cache_data->session_data,
+ session_data, session_data_length);
+ cache_data->last_used = time (NULL);
+ G_UNLOCK (session_cache_lock);
+}
+
+void
+g_tls_backend_gnutls_uncache_session_data (const gchar *session_id)
+{
+ G_LOCK (session_cache_lock);
+ if (session_cache)
+ g_hash_table_remove (session_cache, session_id);
+ G_UNLOCK (session_cache_lock);
+}
+
+GByteArray *
+g_tls_backend_gnutls_lookup_session_data (const gchar *session_id)
+{
+ GTlsBackendGnutlsCacheData *cache_data;
+ GByteArray *session_data = NULL;
+
+ G_LOCK (session_cache_lock);
+ if (session_cache)
+ {
+ cache_data = g_hash_table_lookup (session_cache, session_id);
+ if (cache_data)
+ {
+ cache_data->last_used = time (NULL);
+ session_data = g_byte_array_ref (cache_data->session_data);
+ }
+ }
+ G_UNLOCK (session_cache_lock);
+
+ return session_data;
+}
+
void
g_tls_backend_gnutls_register (GIOModule *module)
{
diff --git a/tls/gnutls/gtlsbackend-gnutls.h b/tls/gnutls/gtlsbackend-gnutls.h
index 04e664b..97ebd90 100644
--- a/tls/gnutls/gtlsbackend-gnutls.h
+++ b/tls/gnutls/gtlsbackend-gnutls.h
@@ -45,6 +45,12 @@ const GList *g_tls_backend_gnutls_get_system_ca_list_gtls (void) G_GNUC_CONST;
void g_tls_backend_gnutls_get_system_ca_list_gnutls (gnutls_x509_crt_t **cas,
int *num_cas);
+void g_tls_backend_gnutls_cache_session_data (const gchar *session_id,
+ guchar *session_data,
+ gsize session_data_length);
+void g_tls_backend_gnutls_uncache_session_data (const gchar *session_id);
+GByteArray *g_tls_backend_gnutls_lookup_session_data (const gchar *session_id);
+
G_END_DECLS
#endif /* __G_TLS_BACKEND_GNUTLS_H___ */
diff --git a/tls/gnutls/gtlsclientconnection-gnutls.c b/tls/gnutls/gtlsclientconnection-gnutls.c
index 5b2fb95..888f7e7 100644
--- a/tls/gnutls/gtlsclientconnection-gnutls.c
+++ b/tls/gnutls/gtlsclientconnection-gnutls.c
@@ -27,6 +27,7 @@
#include <string.h>
#include "gtlsclientconnection-gnutls.h"
+#include "gtlsbackend-gnutls.h"
#include "gtlscertificate-gnutls.h"
#include <glib/gi18n-lib.h>
@@ -47,6 +48,7 @@ static void g_tls_client_connection_gnutls_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
+static void g_tls_client_connection_gnutls_constructed (GObject *object);
static void g_tls_client_connection_gnutls_finalize (GObject *object);
static void g_tls_client_connection_gnutls_begin_handshake (GTlsConnectionGnutls *conn);
@@ -75,6 +77,8 @@ struct _GTlsClientConnectionGnutlsPrivate
GSocketConnectable *server_identity;
gboolean use_ssl3;
+ char *session_id;
+
gboolean cert_requested;
char **accepted_cas;
};
@@ -89,6 +93,7 @@ g_tls_client_connection_gnutls_class_init (GTlsClientConnectionGnutlsClass *klas
gobject_class->get_property = g_tls_client_connection_gnutls_get_property;
gobject_class->set_property = g_tls_client_connection_gnutls_set_property;
+ gobject_class->constructed = g_tls_client_connection_gnutls_constructed;
gobject_class->finalize = g_tls_client_connection_gnutls_finalize;
connection_gnutls_class->begin_handshake = g_tls_client_connection_gnutls_begin_handshake;
@@ -118,6 +123,47 @@ g_tls_client_connection_gnutls_init (GTlsClientConnectionGnutls *gnutls)
}
static void
+g_tls_client_connection_gnutls_constructed (GObject *object)
+{
+ GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
+ GSocketConnection *base_conn;
+ GSocketAddress *remote_addr;
+ GInetAddress *iaddr;
+ guint port;
+
+ /* We base the session ID on the IP address rather than on
+ * server-identity, because it's likely that different virtual
+ * servers on the same host will have access to the same session
+ * cache, whereas different hosts serving the same hostname/service
+ * likely won't. Note that session IDs are opaque, and transmitted
+ * in the clear anyway, so there are no security issues if we send
+ * one to the "wrong" server; we'll just fail to get a resumed
+ * session.
+ */
+ g_object_get (G_OBJECT (gnutls), "base-io-stream", &base_conn, NULL);
+ if (G_IS_SOCKET_CONNECTION (base_conn))
+ {
+ remote_addr = g_socket_connection_get_remote_address (base_conn, NULL);
+ if (G_IS_INET_SOCKET_ADDRESS (remote_addr))
+ {
+ GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
+ gchar *addrstr;
+
+ iaddr = g_inet_socket_address_get_address (isaddr);
+ port = g_inet_socket_address_get_port (isaddr);
+
+ addrstr = g_inet_address_to_string (iaddr);
+ gnutls->priv->session_id = g_strdup_printf ("%s/%d", addrstr, port);
+ g_free (addrstr);
+ }
+ }
+ g_object_unref (base_conn);
+
+ if (G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->constructed)
+ G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->constructed (object);
+}
+
+static void
g_tls_client_connection_gnutls_finalize (GObject *object)
{
GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
@@ -126,6 +172,8 @@ g_tls_client_connection_gnutls_finalize (GObject *object)
g_object_unref (gnutls->priv->server_identity);
if (gnutls->priv->accepted_cas)
g_strfreev (gnutls->priv->accepted_cas);
+ if (gnutls->priv->session_id)
+ g_free (gnutls->priv->session_id);
G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->finalize (object);
}
@@ -245,6 +293,20 @@ g_tls_client_connection_gnutls_begin_handshake (GTlsConnectionGnutls *conn)
{
GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
+ /* Try to get a cached session */
+ if (gnutls->priv->session_id)
+ {
+ GByteArray *session_data;
+
+ session_data = g_tls_backend_gnutls_lookup_session_data (gnutls->priv->session_id);
+ if (session_data)
+ {
+ gnutls_session_set_data (g_tls_connection_gnutls_get_session (conn),
+ session_data->data, session_data->len);
+ g_byte_array_unref (session_data);
+ }
+ }
+
gnutls->priv->cert_requested = FALSE;
}
@@ -283,4 +345,21 @@ g_tls_client_connection_gnutls_finish_handshake (GTlsConnectionGnutls *conn,
g_set_error_literal (inout_error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
_("Server required TLS certificate"));
}
+
+ if (gnutls->priv->session_id)
+ {
+ gnutls_datum session_data;
+
+ if (!*inout_error &&
+ gnutls_session_get_data2 (g_tls_connection_gnutls_get_session (conn),
+ &session_data) == 0)
+ {
+ g_tls_backend_gnutls_cache_session_data (gnutls->priv->session_id,
+ session_data.data,
+ session_data.size);
+ gnutls_free (session_data.data);
+ }
+ else
+ g_tls_backend_gnutls_uncache_session_data (gnutls->priv->session_id);
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]