gnutls: add server-side session cache support
authorDan Winship <danw@gnome.org>
Sat, 3 Dec 2011 18:32:01 +0000 (19:32 +0100)
committerDan Winship <danw@gnome.org>
Sat, 3 Dec 2011 18:33:06 +0000 (19:33 +0100)
https://bugzilla.gnome.org/show_bug.cgi?id=636574

configure.ac
tls/gnutls/gtlsbackend-gnutls.c
tls/gnutls/gtlsbackend-gnutls.h
tls/gnutls/gtlsclientconnection-gnutls.c
tls/gnutls/gtlsconnection-gnutls.c
tls/gnutls/gtlsconnection-gnutls.h
tls/gnutls/gtlsserverconnection-gnutls.c

index a408a54..0964b60 100644 (file)
@@ -226,7 +226,7 @@ if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then
                -Wall -Wstrict-prototypes -Werror=missing-prototypes \
                -Werror=implicit-function-declaration \
                -Werror=pointer-arith -Werror=init-self -Werror=format=2 \
-               -Werror=missing-include-dirs -Werror=aggregate-return \
+               -Werror=missing-include-dirs \
                -Werror=declaration-after-statement"
 fi
 
index 6db3fec..3597753 100644 (file)
@@ -166,26 +166,26 @@ g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface)
  */
 
 G_LOCK_DEFINE_STATIC (session_cache_lock);
-GHashTable *session_cache;
+GHashTable *client_session_cache, *server_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;
+  GBytes *session_id;
+  GBytes *session_data;
+  time_t  last_used;
 } GTlsBackendGnutlsCacheData;
 
 static void
-session_cache_cleanup (void)
+session_cache_cleanup (GHashTable *cache)
 {
   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);
+  g_hash_table_iter_init (&iter, cache);
   while (g_hash_table_iter_next (&iter, &key, &value))
     {
       cache_data = value;
@@ -199,81 +199,98 @@ cache_data_free (gpointer data)
 {
   GTlsBackendGnutlsCacheData *cache_data = data;
 
-  g_free (cache_data->session_id);
-  g_byte_array_unref (cache_data->session_data);
+  g_bytes_unref (cache_data->session_id);
+  g_bytes_unref (cache_data->session_data);
   g_slice_free (GTlsBackendGnutlsCacheData, cache_data);
 }
 
+static GHashTable *
+get_session_cache (gnutls_connection_end_t type,
+                  gboolean                create)
+{
+  GHashTable **cache_p;
+
+  cache_p = (type == GNUTLS_CLIENT) ? &client_session_cache : &server_session_cache;
+  if (!*cache_p && create)
+    {
+      *cache_p = g_hash_table_new_full (g_bytes_hash, g_bytes_equal,
+                                       NULL, cache_data_free);
+    }
+  return *cache_p;
+}
+
 void
-g_tls_backend_gnutls_cache_session_data (const gchar *session_id,
-                                        guchar      *session_data,
-                                        gsize        session_data_length)
+g_tls_backend_gnutls_store_session (gnutls_connection_end_t  type,
+                                   GBytes                  *session_id,
+                                   GBytes                  *session_data)
 {
   GTlsBackendGnutlsCacheData *cache_data;
+  GHashTable *cache;
 
   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);
+  cache = get_session_cache (type, TRUE);
+  cache_data = g_hash_table_lookup (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)
+      if (!g_bytes_equal (cache_data->session_data, session_data))
        {
-         cache_data->last_used = time (NULL);
-         G_UNLOCK (session_cache_lock);
-         return;
+         g_bytes_unref (cache_data->session_data);
+         cache_data->session_data = g_bytes_ref (session_data);
        }
-
-      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 ();
+      if (g_hash_table_size (cache) >= SESSION_CACHE_MAX_SIZE)
+       session_cache_cleanup (cache);
 
       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);
+      cache_data->session_id = g_bytes_ref (session_id);
+      cache_data->session_data = g_bytes_ref (session_data);
 
-      g_hash_table_insert (session_cache, cache_data->session_id, cache_data);
+      g_hash_table_insert (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_tls_backend_gnutls_remove_session (gnutls_connection_end_t  type,
+                                    GBytes                  *session_id)
 {
+  GHashTable *cache;
+
   G_LOCK (session_cache_lock);
-  if (session_cache)
-    g_hash_table_remove (session_cache, session_id);
+
+  cache = get_session_cache (type, FALSE);
+  if (cache)
+    g_hash_table_remove (cache, session_id);
+
   G_UNLOCK (session_cache_lock);
 }
 
-GByteArray *
-g_tls_backend_gnutls_lookup_session_data (const gchar *session_id)
+GBytes *
+g_tls_backend_gnutls_lookup_session (gnutls_connection_end_t  type,
+                                    GBytes                  *session_id)
 {
   GTlsBackendGnutlsCacheData *cache_data;
-  GByteArray *session_data = NULL;
+  GBytes *session_data = NULL;
+  GHashTable *cache;
 
   G_LOCK (session_cache_lock);
-  if (session_cache)
+
+  cache = get_session_cache (type, FALSE);
+  if (cache)
     {
-      cache_data = g_hash_table_lookup (session_cache, session_id);
+      cache_data = g_hash_table_lookup (cache, session_id);
       if (cache_data)
        {
          cache_data->last_used = time (NULL);
-         session_data = g_byte_array_ref (cache_data->session_data);
+         session_data = g_bytes_ref (cache_data->session_data);
        }
     }
+
   G_UNLOCK (session_cache_lock);
 
   return session_data;
index 3a75dfe..ceb686a 100644 (file)
@@ -46,11 +46,13 @@ struct _GTlsBackendGnutls
 GType g_tls_backend_gnutls_get_type (void) G_GNUC_CONST;
 void  g_tls_backend_gnutls_register (GIOModule *module);
 
-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);
+void    g_tls_backend_gnutls_store_session  (gnutls_connection_end_t  type,
+                                            GBytes                  *session_id,
+                                            GBytes                  *session_data);
+void    g_tls_backend_gnutls_remove_session (gnutls_connection_end_t  type,
+                                            GBytes                  *session_id);
+GBytes *g_tls_backend_gnutls_lookup_session (gnutls_connection_end_t  type,
+                                            GBytes                  *session_id);
 
 G_END_DECLS
 
index da435bf..2292d05 100644 (file)
@@ -58,7 +58,7 @@ struct _GTlsClientConnectionGnutlsPrivate
   GSocketConnectable *server_identity;
   gboolean use_ssl3;
 
-  char *session_id;
+  GBytes *session_id;
 
   gboolean cert_requested;
   GPtrArray *accepted_cas;
@@ -111,17 +111,17 @@ g_tls_client_connection_gnutls_constructed (GObject *object)
        {
          GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
          const gchar *server_hostname;
-         gchar *addrstr;
+         gchar *addrstr, *session_id;
 
          iaddr = g_inet_socket_address_get_address (isaddr);
          port = g_inet_socket_address_get_port (isaddr);
 
          addrstr = g_inet_address_to_string (iaddr);
          server_hostname = get_server_identity (gnutls);
-         gnutls->priv->session_id =
-           g_strdup_printf ("%s/%s/%d", addrstr,
-                            server_hostname ? server_hostname : "",
-                            port);
+         session_id = g_strdup_printf ("%s/%s/%d", addrstr,
+                                       server_hostname ? server_hostname : "",
+                                       port);
+         gnutls->priv->session_id = g_bytes_new_take (session_id, strlen (session_id));
          g_free (addrstr);
        }
       g_object_unref (remote_addr);
@@ -142,7 +142,7 @@ g_tls_client_connection_gnutls_finalize (GObject *object)
   if (gnutls->priv->accepted_cas)
     g_ptr_array_unref (gnutls->priv->accepted_cas);
   if (gnutls->priv->session_id)
-    g_free (gnutls->priv->session_id);
+    g_bytes_unref (gnutls->priv->session_id);
 
   G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->finalize (object);
 }
@@ -262,6 +262,15 @@ g_tls_client_connection_gnutls_retrieve_function (gnutls_session_t             s
 }
 
 static void
+g_tls_client_connection_gnutls_failed (GTlsConnectionGnutls *conn)
+{
+  GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
+
+  if (gnutls->priv->session_id)
+    g_tls_backend_gnutls_remove_session (GNUTLS_CLIENT, gnutls->priv->session_id);
+}
+
+static void
 g_tls_client_connection_gnutls_begin_handshake (GTlsConnectionGnutls *conn)
 {
   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
@@ -269,14 +278,15 @@ g_tls_client_connection_gnutls_begin_handshake (GTlsConnectionGnutls *conn)
   /* Try to get a cached session */
   if (gnutls->priv->session_id)
     {
-      GByteArray *session_data;
+      GBytes *session_data;
 
-      session_data = g_tls_backend_gnutls_lookup_session_data (gnutls->priv->session_id);
+      session_data = g_tls_backend_gnutls_lookup_session (GNUTLS_CLIENT, 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);
+                                  g_bytes_get_data (session_data),
+                                  g_bytes_get_size (session_data));
+         g_bytes_unref (session_data);
        }
     }
 
@@ -341,19 +351,21 @@ g_tls_client_connection_gnutls_finish_handshake (GTlsConnectionGnutls  *conn,
 
   if (gnutls->priv->session_id)
     {
-      gnutls_datum session_data;
+      gnutls_datum session_datum;
 
       if (success &&
          gnutls_session_get_data2 (g_tls_connection_gnutls_get_session (conn),
-                                   &session_data) == 0)
+                                   &session_datum) == 0)
        {
-         g_tls_backend_gnutls_cache_session_data (gnutls->priv->session_id,
-                                                  session_data.data,
-                                                  session_data.size);
-         gnutls_free (session_data.data);
+         GBytes *session_data = g_bytes_new_with_free_func (session_datum.data, session_datum.size,
+                                                            (GDestroyNotify)gnutls_free, session_datum.data);
+
+         g_tls_backend_gnutls_store_session (GNUTLS_CLIENT, gnutls->priv->session_id,
+                                             session_data);
+         g_bytes_unref (session_data);
        }
       else
-       g_tls_backend_gnutls_uncache_session_data (gnutls->priv->session_id);
+       g_tls_backend_gnutls_remove_session (GNUTLS_CLIENT, gnutls->priv->session_id);
     }
 }
 
@@ -370,6 +382,7 @@ g_tls_client_connection_gnutls_class_init (GTlsClientConnectionGnutlsClass *klas
   gobject_class->constructed  = g_tls_client_connection_gnutls_constructed;
   gobject_class->finalize     = g_tls_client_connection_gnutls_finalize;
 
+  connection_gnutls_class->failed           = g_tls_client_connection_gnutls_failed;
   connection_gnutls_class->begin_handshake  = g_tls_client_connection_gnutls_begin_handshake;
   connection_gnutls_class->verify_peer      = g_tls_client_connection_gnutls_verify_peer;
   connection_gnutls_class->finish_handshake = g_tls_client_connection_gnutls_finish_handshake;
index 36e0df2..f1655d6 100644 (file)
@@ -487,6 +487,8 @@ end_gnutls_io (GTlsConnectionGnutls  *gnutls,
     {
       if (g_error_matches (gnutls->priv->error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
        status = GNUTLS_E_AGAIN;
+      else
+       G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls);
       g_propagate_error (error, gnutls->priv->error);
       gnutls->priv->error = NULL;
       return status;
@@ -515,6 +517,7 @@ end_gnutls_io (GTlsConnectionGnutls  *gnutls,
        {
          g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
                               _("TLS connection closed unexpectedly"));
+         G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls);
          return status;
        }
       else
index fa4f133..b69eca1 100644 (file)
@@ -33,6 +33,8 @@ struct _GTlsConnectionGnutlsClass
 {
   GTlsConnectionClass parent_class;
 
+  void     (*failed)           (GTlsConnectionGnutls  *gnutls);
+
   void     (*begin_handshake)  (GTlsConnectionGnutls  *gnutls);
   gboolean (*verify_peer)      (GTlsConnectionGnutls  *gnutls,
                                GTlsCertificate       *peer_certificate,
index 24d9ac2..807895b 100644 (file)
@@ -25,6 +25,7 @@
 #include <gnutls/x509.h>
 
 #include "gtlsserverconnection-gnutls.h"
+#include "gtlsbackend-gnutls.h"
 #include "gtlscertificate-gnutls.h"
 #include <glib/gi18n-lib.h>
 
@@ -45,6 +46,14 @@ static int g_tls_server_connection_gnutls_retrieve_function (gnutls_session_t
                                                              int                          pk_algos_length,
                                                              gnutls_retr2_st             *st);
 
+static int            g_tls_server_connection_gnutls_db_store    (void            *user_data,
+                                                                 gnutls_datum_t   key,
+                                                                 gnutls_datum_t   data);
+static int            g_tls_server_connection_gnutls_db_remove   (void            *user_data,
+                                                                 gnutls_datum_t   key);
+static gnutls_datum_t g_tls_server_connection_gnutls_db_retrieve (void            *user_data,
+                                                                 gnutls_datum_t   key);
+
 static GInitableIface *g_tls_server_connection_gnutls_parent_initable_iface;
 
 G_DEFINE_TYPE_WITH_CODE (GTlsServerConnectionGnutls, g_tls_server_connection_gnutls, G_TYPE_TLS_CONNECTION_GNUTLS,
@@ -63,11 +72,17 @@ static void
 g_tls_server_connection_gnutls_init (GTlsServerConnectionGnutls *gnutls)
 {
   gnutls_certificate_credentials_t creds;
+  gnutls_session_t session;
 
   gnutls->priv = G_TYPE_INSTANCE_GET_PRIVATE (gnutls, G_TYPE_TLS_SERVER_CONNECTION_GNUTLS, GTlsServerConnectionGnutlsPrivate);
 
   creds = g_tls_connection_gnutls_get_credentials (G_TLS_CONNECTION_GNUTLS (gnutls));
   gnutls_certificate_set_retrieve_function (creds, g_tls_server_connection_gnutls_retrieve_function);
+
+  session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (gnutls));
+  gnutls_db_set_retrieve_function (session, g_tls_server_connection_gnutls_db_retrieve);
+  gnutls_db_set_store_function (session, g_tls_server_connection_gnutls_db_store);
+  gnutls_db_set_remove_function (session, g_tls_server_connection_gnutls_db_remove);
 }
 
 static gboolean
@@ -143,6 +158,12 @@ g_tls_server_connection_gnutls_retrieve_function (gnutls_session_t             s
 }
 
 static void
+g_tls_server_connection_gnutls_failed (GTlsConnectionGnutls *conn)
+{
+  gnutls_db_remove_session (g_tls_connection_gnutls_get_session (conn));
+}
+
+static void
 g_tls_server_connection_gnutls_begin_handshake (GTlsConnectionGnutls *conn)
 {
   GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (conn);
@@ -206,6 +227,64 @@ g_tls_server_connection_gnutls_finish_handshake (GTlsConnectionGnutls  *gnutls,
 {
 }
 
+/* Session cache management */
+
+static int
+g_tls_server_connection_gnutls_db_store (void            *user_data,
+                                        gnutls_datum_t   key,
+                                        gnutls_datum_t   data)
+{
+  GBytes *session_id, *session_data;
+
+  session_id = g_bytes_new (key.data, key.size);
+  session_data = g_bytes_new (data.data, data.size);
+  g_tls_backend_gnutls_store_session (GNUTLS_SERVER, session_id, session_data);
+  g_bytes_unref (session_id);
+  g_bytes_unref (session_data);
+
+  return 0;
+}
+
+static int
+g_tls_server_connection_gnutls_db_remove (void            *user_data,
+                                         gnutls_datum_t   key)
+{
+  GBytes *session_id;
+
+  session_id = g_bytes_new (key.data, key.size);
+  g_tls_backend_gnutls_remove_session (GNUTLS_SERVER, session_id);
+  g_bytes_unref (session_id);
+
+  return 0;
+}
+
+static gnutls_datum_t
+g_tls_server_connection_gnutls_db_retrieve (void            *user_data,
+                                           gnutls_datum_t   key)
+{
+  GBytes *session_id, *session_data;
+  gnutls_datum_t data;
+
+  session_id = g_bytes_new (key.data, key.size);
+  session_data = g_tls_backend_gnutls_lookup_session (GNUTLS_SERVER, session_id);
+  g_bytes_unref (session_id);
+
+  if (session_data)
+    {
+      data.size = g_bytes_get_size (session_data);
+      data.data = gnutls_malloc (data.size);
+      memcpy (data.data, g_bytes_get_data (session_data), data.size);
+      g_bytes_unref (session_data);
+    }
+  else
+    {
+      data.size = 0;
+      data.data = NULL;
+    }
+
+  return data;
+}
+
 static void
 g_tls_server_connection_gnutls_class_init (GTlsServerConnectionGnutlsClass *klass)
 {
@@ -217,6 +296,7 @@ g_tls_server_connection_gnutls_class_init (GTlsServerConnectionGnutlsClass *klas
   gobject_class->get_property = g_tls_server_connection_gnutls_get_property;
   gobject_class->set_property = g_tls_server_connection_gnutls_set_property;
 
+  connection_gnutls_class->failed           = g_tls_server_connection_gnutls_failed;
   connection_gnutls_class->begin_handshake  = g_tls_server_connection_gnutls_begin_handshake;
   connection_gnutls_class->verify_peer      = g_tls_server_connection_gnutls_verify_peer;
   connection_gnutls_class->finish_handshake = g_tls_server_connection_gnutls_finish_handshake;