[glib-networking] Use available cached tickets when creating connections



commit 4be360a85761e4b83aa851bc7413698b3155ffb3
Author: Goncalo Gomes <goncalo gomes youview com>
Date:   Tue Aug 9 10:44:44 2022 +0100

    Use available cached tickets when creating connections
    
    Client certificates could be very slow to process in certain platforms.
    Hence the client should reuse sessions, if it can, in order to skip any
    operations that would otherwise slowdown the connection.
    
    We use the same assumptions as the GnuTLS implementation for consistency:
    - Cache maximum size is 50
    - Session validity should be maximum 10 minutes as per
      https://arxiv.org/abs/1810.07304 (section 6)
    - TLSv1.3 tickets should only be used once as per RFC 8446 §C.4 to avoid
      client tracking (https://www.rfc-editor.org/rfc/rfc8446.html#appendix-C.4)
    
    Glib-networking will use the last session present in the cache for each
    connection using the same session id, derived from IP/hostname/port/certificate.
    The server is responsible to provide us with session tickets that we can use.
    
    This commit also disables SSL_OP_NO_TICKET.
    
    Fixes #147
    
    Signed-off-by: Goncalo Gomes <goncalo gomes youview com>
    Part-of: <https://gitlab.gnome.org/GNOME/glib-networking/-/merge_requests/221>

 tls/base/gtlsconnection-base.c             |  19 ++-
 tls/gnutls/gtlsclientconnection-gnutls.c   |  11 +-
 tls/openssl/gtlsbackend-openssl.c          | 168 +++++++++++++++++++
 tls/openssl/gtlsbackend-openssl.h          |   5 +
 tls/openssl/gtlsclientconnection-openssl.c | 107 +++++++++++-
 tls/openssl/gtlsserverconnection-openssl.c |   3 +-
 tls/tests/connection.c                     | 253 +++++++++++++++++++++++++++++
 tls/tests/meson.build                      |   8 +-
 8 files changed, 564 insertions(+), 10 deletions(-)
---
diff --git a/tls/base/gtlsconnection-base.c b/tls/base/gtlsconnection-base.c
index bcbdf499..3b5c6ad3 100644
--- a/tls/base/gtlsconnection-base.c
+++ b/tls/base/gtlsconnection-base.c
@@ -216,7 +216,8 @@ enum
   PROP_ADVERTISED_PROTOCOLS,
   PROP_NEGOTIATED_PROTOCOL,
   PROP_PROTOCOL_VERSION,
-  PROP_CIPHERSUITE_NAME
+  PROP_CIPHERSUITE_NAME,
+  PROP_SESSION_REUSED
 };
 
 gboolean
@@ -366,6 +367,10 @@ g_tls_connection_base_get_property (GObject    *object,
       g_value_set_string (value, priv->ciphersuite_name);
       break;
 
+    case PROP_SESSION_REUSED:
+      g_value_set_boolean (value, FALSE);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -469,6 +474,10 @@ g_tls_connection_base_set_property (GObject      *object,
       priv->advertised_protocols = g_value_dup_boxed (value);
       break;
 
+    case PROP_SESSION_REUSED:
+      g_assert_not_reached ();
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -2795,6 +2804,14 @@ g_tls_connection_base_class_init (GTlsConnectionBaseClass *klass)
   klass->push_io = g_tls_connection_base_real_push_io;
   klass->pop_io = g_tls_connection_base_real_pop_io;
 
+  g_object_class_install_property (gobject_class, PROP_SESSION_REUSED,
+    g_param_spec_boolean ("session-reused",
+                  _("Session Reused"),
+                  _("Indicates whether a session has been reused"),
+                  FALSE,
+                  G_PARAM_READABLE |
+                  G_PARAM_STATIC_STRINGS));
+
   /* For GTlsConnection and GDtlsConnection: */
   g_object_class_override_property (gobject_class, PROP_BASE_IO_STREAM, "base-io-stream");
   g_object_class_override_property (gobject_class, PROP_BASE_SOCKET, "base-socket");
diff --git a/tls/gnutls/gtlsclientconnection-gnutls.c b/tls/gnutls/gtlsclientconnection-gnutls.c
index 9045270b..39b06d77 100644
--- a/tls/gnutls/gtlsclientconnection-gnutls.c
+++ b/tls/gnutls/gtlsclientconnection-gnutls.c
@@ -42,7 +42,8 @@ enum
   PROP_VALIDATION_FLAGS,
   PROP_SERVER_IDENTITY,
   PROP_USE_SSL3,
-  PROP_ACCEPTED_CAS
+  PROP_ACCEPTED_CAS,
+  PROP_SESSION_REUSED
 };
 
 struct _GTlsClientConnectionGnutls
@@ -52,6 +53,7 @@ struct _GTlsClientConnectionGnutls
   GTlsCertificateFlags validation_flags;
   GSocketConnectable *server_identity;
   gboolean use_ssl3;
+  gboolean session_reused;
 
   /* session_data is either the session ticket that was used to resume this
    * connection, or the most recent session ticket received from the server.
@@ -321,6 +323,10 @@ g_tls_client_connection_gnutls_get_property (GObject    *object,
       g_value_set_pointer (value, accepted_cas);
       break;
 
+    case PROP_SESSION_REUSED:
+      g_value_set_boolean (value, gnutls->session_reused);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -485,6 +491,7 @@ g_tls_client_connection_gnutls_prepare_handshake (GTlsConnectionBase  *tls,
                                    g_bytes_get_size (session_data));
           g_clear_pointer (&gnutls->session_data, g_bytes_unref);
           gnutls->session_data = g_steal_pointer (&session_data);
+          gnutls->session_reused = TRUE;
         }
     }
 
@@ -571,6 +578,7 @@ g_tls_client_connection_gnutls_copy_session_state (GTlsClientConnection *conn,
     }
 
   gnutls->session_data_override = !!gnutls->session_data;
+  gnutls->session_reused = gnutls->session_data_override;
 }
 
 static void
@@ -600,6 +608,7 @@ g_tls_client_connection_gnutls_class_init (GTlsClientConnectionGnutlsClass *klas
   g_object_class_override_property (gobject_class, PROP_SERVER_IDENTITY, "server-identity");
   g_object_class_override_property (gobject_class, PROP_USE_SSL3, "use-ssl3");
   g_object_class_override_property (gobject_class, PROP_ACCEPTED_CAS, "accepted-cas");
+  g_object_class_override_property (gobject_class, PROP_SESSION_REUSED, "session-reused");
 }
 
 static void
diff --git a/tls/openssl/gtlsbackend-openssl.c b/tls/openssl/gtlsbackend-openssl.c
index 23cd8ded..aa08bd9d 100644
--- a/tls/openssl/gtlsbackend-openssl.c
+++ b/tls/openssl/gtlsbackend-openssl.c
@@ -243,6 +243,174 @@ g_tls_backend_openssl_interface_init (GTlsBackendInterface *iface)
   iface->get_dtls_server_connection_type = g_tls_server_connection_openssl_get_type;
 }
 
+/* Session cache support. We try to be careful of TLS session tracking
+ * and so have adopted the recommendations of arXiv:1810.07304 section 6
+ * in using a 10-minute cache lifetime and in never updating the
+ * expiration time of cache entries when they are accessed to ensure a
+ * new session gets used after 10 minutes even if the cached one was
+ * resumed more recently.
+ *
+ * https://arxiv.org/abs/1810.07304
+ */
+
+G_LOCK_DEFINE_STATIC (session_cache_lock);
+static GHashTable *client_session_cache; /* (owned) GString -> (owned) GTlsBackendOpensslCacheData */
+
+#define SESSION_CACHE_MAX_SIZE 50
+#define SESSION_CACHE_MAX_AGE (10ll * 60ll * G_USEC_PER_SEC) /* ten minutes */
+
+typedef struct {
+  SSL_SESSION *session_ticket;
+  gint64 expiration_time;
+} GTlsBackendOpensslCacheData;
+
+static void
+session_cache_cleanup (GHashTable *cache)
+{
+  gint64 time;
+  GHashTableIter iter;
+  gpointer key, value;
+  GTlsBackendOpensslCacheData *cache_data;
+  GString *session_id = NULL;
+  gint64 expiration_time = 0;
+  gboolean removed = FALSE;
+
+  time = g_get_monotonic_time ();
+
+  g_hash_table_iter_init (&iter, cache);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      cache_data = value;
+      if (cache_data->expiration_time > expiration_time)
+        {
+          expiration_time = cache_data->expiration_time;
+          session_id = key;
+        }
+
+      if (time > cache_data->expiration_time)
+        {
+          removed = TRUE;
+          g_hash_table_iter_remove (&iter);
+        }
+    }
+
+  if (!removed && session_id)
+    g_hash_table_remove (cache, session_id);
+}
+
+static void
+cache_data_free (GTlsBackendOpensslCacheData *data)
+{
+  SSL_SESSION_free (data->session_ticket);
+  g_free (data);
+}
+
+static void
+string_free (GString *data)
+{
+  g_string_free (data, TRUE);
+}
+
+static GHashTable *
+get_session_cache (gboolean create)
+{
+  if (!client_session_cache && create)
+    {
+      client_session_cache = g_hash_table_new_full ((GHashFunc)g_string_hash,
+                                                    (GEqualFunc)g_string_equal,
+                                                    (GDestroyNotify)string_free,
+                                                    (GDestroyNotify)cache_data_free);
+    }
+  return client_session_cache;
+}
+
+void
+g_tls_backend_openssl_store_session_data (GString *session_id,
+                                         SSL_SESSION *session_data)
+{
+  GTlsBackendOpensslCacheData *cache_data;
+  GHashTable *cache;
+
+  if (!session_id || !session_data)
+    return;
+
+  G_LOCK (session_cache_lock);
+
+  cache = get_session_cache (TRUE);
+  cache_data = g_hash_table_lookup (cache, session_id);
+  if (!cache_data)
+    {
+      if (g_hash_table_size (cache) >= SESSION_CACHE_MAX_SIZE)
+        session_cache_cleanup (cache);
+
+      if (g_hash_table_size (cache) < SESSION_CACHE_MAX_SIZE)
+        {
+          cache_data = g_new (GTlsBackendOpensslCacheData, 1);
+          cache_data->session_ticket = NULL;
+          g_hash_table_insert (cache, g_string_new (session_id->str), cache_data);
+        }
+  }
+
+  if (cache_data)
+    {
+      if (SSL_SESSION_up_ref (session_data))
+        {
+          SSL_SESSION_free (cache_data->session_ticket);
+          cache_data->session_ticket = session_data;
+          cache_data->expiration_time = g_get_monotonic_time () + SESSION_CACHE_MAX_AGE;
+        }
+      else
+        g_warning ("Failed to acquire TLS session, will not be resumeable");
+    }
+
+  G_UNLOCK (session_cache_lock);
+}
+
+SSL_SESSION *
+g_tls_backend_openssl_lookup_session_data (GString *session_id)
+{
+  GTlsBackendOpensslCacheData *cache_data;
+  SSL_SESSION *session_data = NULL;
+  GHashTable *cache;
+
+  if (!session_id)
+    return NULL;
+
+  G_LOCK (session_cache_lock);
+
+  cache = get_session_cache (FALSE);
+  if (cache)
+    {
+      cache_data = g_hash_table_lookup (cache, session_id);
+      if (cache_data)
+        {
+          if (g_get_monotonic_time () > cache_data->expiration_time)
+            {
+              g_hash_table_remove (cache, session_id);
+              G_UNLOCK (session_cache_lock);
+              return NULL;
+            }
+
+          session_data = cache_data->session_ticket;
+          if (!SSL_SESSION_up_ref (session_data))
+            {
+              g_debug ("Failed to acquire cached TLS session, will not try to resume session");
+              session_data = NULL;
+            }
+
+          /* Note that session tickets should be used only once since TLS 1.3,
+           * so we remove from the queue after retrieval. See RFC 8446 §C.4.
+           */
+          if (SSL_SESSION_get_protocol_version (cache_data->session_ticket) == TLS1_3_VERSION)
+            g_hash_table_remove (cache, session_id);
+        }
+    }
+
+  G_UNLOCK (session_cache_lock);
+
+  return session_data;
+}
+
 void
 g_tls_backend_openssl_register (GIOModule *module)
 {
diff --git a/tls/openssl/gtlsbackend-openssl.h b/tls/openssl/gtlsbackend-openssl.h
index 9e53806a..fa18df34 100644
--- a/tls/openssl/gtlsbackend-openssl.h
+++ b/tls/openssl/gtlsbackend-openssl.h
@@ -26,6 +26,7 @@
 #pragma once
 
 #include <gio/gio.h>
+#include "openssl-include.h"
 
 G_BEGIN_DECLS
 
@@ -35,4 +36,8 @@ G_DECLARE_FINAL_TYPE (GTlsBackendOpenssl, g_tls_backend_openssl, G, TLS_BACKEND_
 
 void    g_tls_backend_openssl_register       (GIOModule *module);
 
+void    g_tls_backend_openssl_store_session_data (GString *session_id,
+                                                  SSL_SESSION *session_data);
+SSL_SESSION *g_tls_backend_openssl_lookup_session_data (GString *session_id);
+
 G_END_DECLS
diff --git a/tls/openssl/gtlsclientconnection-openssl.c b/tls/openssl/gtlsclientconnection-openssl.c
index 263596b8..6a5a2884 100644
--- a/tls/openssl/gtlsclientconnection-openssl.c
+++ b/tls/openssl/gtlsclientconnection-openssl.c
@@ -29,8 +29,8 @@
 #include <errno.h>
 #include <string.h>
 
-#include "openssl-include.h"
 #include "gtlsconnection-base.h"
+#include "gtlsbackend-openssl.h"
 #include "gtlsclientconnection-openssl.h"
 #include "gtlsbackend-openssl.h"
 #include "gtlscertificate-openssl.h"
@@ -44,6 +44,8 @@ struct _GTlsClientConnectionOpenssl
   GTlsCertificateFlags validation_flags;
   GSocketConnectable *server_identity;
   gboolean use_ssl3;
+  gboolean session_reused;
+  GString *session_id;
 
   STACK_OF (X509_NAME) *ca_list;
 
@@ -58,7 +60,8 @@ enum
   PROP_VALIDATION_FLAGS,
   PROP_SERVER_IDENTITY,
   PROP_USE_SSL3,
-  PROP_ACCEPTED_CAS
+  PROP_ACCEPTED_CAS,
+  PROP_SESSION_REUSED
 };
 
 static void g_tls_client_connection_openssl_initable_interface_init (GInitableIface  *iface);
@@ -86,6 +89,8 @@ g_tls_client_connection_openssl_finalize (GObject *object)
   SSL_CTX_free (openssl->ssl_ctx);
   SSL_SESSION_free (openssl->session);
 
+  g_string_free (openssl->session_id, TRUE);
+
   G_OBJECT_CLASS (g_tls_client_connection_openssl_parent_class)->finalize (object);
 }
 
@@ -100,6 +105,75 @@ get_server_identity (GTlsClientConnectionOpenssl *openssl)
     return NULL;
 }
 
+static void
+g_tls_client_connection_openssl_constructed (GObject *object)
+{
+  GTlsClientConnectionOpenssl *openssl = G_TLS_CLIENT_CONNECTION_OPENSSL (object);
+  GSocketConnection *base_conn;
+
+  /* Create a TLS "session ID." We base it on the IP address since
+   * different hosts serving the same hostname/service will probably
+   * not share the same session cache. We base it on the
+   * server-identity because at least some servers will fail (rather
+   * than just failing to resume the session) if we don't.
+   * (https://bugs.launchpad.net/bugs/823325)
+   *
+   * Note that our session IDs have no relation to TLS protocol
+   * session IDs.
+   */
+  g_object_get (G_OBJECT (openssl), "base-io-stream", &base_conn, NULL);
+  if (G_IS_SOCKET_CONNECTION (base_conn))
+    {
+      GSocketAddress *remote_addr;
+      remote_addr = g_socket_connection_get_remote_address (base_conn, NULL);
+      if (G_IS_INET_SOCKET_ADDRESS (remote_addr))
+        {
+          guint port;
+          GInetAddress *iaddr;
+          GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
+          const gchar *server_hostname;
+          gchar *addrstr = NULL, *cert_hash = NULL;
+          GTlsCertificate *cert = NULL;
+
+          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 (openssl);
+
+          /* If we have a certificate, make its hash part of the session ID, so
+           * that different connections to the same server can use different
+           * certificates.
+           */
+          g_object_get (G_OBJECT (openssl), "certificate", &cert, NULL);
+          if (cert)
+            {
+              GByteArray *der = NULL;
+              g_object_get (G_OBJECT (cert), "certificate", &der, NULL);
+              if (der)
+                {
+                  cert_hash = g_compute_checksum_for_data (G_CHECKSUM_SHA256, der->data, der->len);
+                  g_byte_array_unref (der);
+                }
+              g_object_unref (cert);
+            }
+
+          openssl->session_id = g_string_new (NULL);
+          g_string_printf (openssl->session_id, "%s/%s/%d/%s", addrstr,
+                           server_hostname ? server_hostname : "",
+                           port,
+                           cert_hash ? cert_hash : "");
+          g_free (addrstr);
+          g_free (cert_hash);
+        }
+      g_object_unref (remote_addr);
+    }
+  g_object_unref (base_conn);
+
+  if (G_OBJECT_CLASS (g_tls_client_connection_openssl_parent_class)->constructed)
+    G_OBJECT_CLASS (g_tls_client_connection_openssl_parent_class)->constructed (object);
+}
+
 static void
 g_tls_client_connection_openssl_get_property (GObject    *object,
                                              guint       prop_id,
@@ -151,6 +225,10 @@ g_tls_client_connection_openssl_get_property (GObject    *object,
       g_value_set_pointer (value, accepted_cas);
       break;
 
+    case PROP_SESSION_REUSED:
+      g_value_set_boolean (value, openssl->session_reused);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -272,6 +350,7 @@ g_tls_client_connection_openssl_class_init (GTlsClientConnectionOpensslClass *kl
   gobject_class->finalize             = g_tls_client_connection_openssl_finalize;
   gobject_class->get_property         = g_tls_client_connection_openssl_get_property;
   gobject_class->set_property         = g_tls_client_connection_openssl_set_property;
+  gobject_class->constructed          = g_tls_client_connection_openssl_constructed;
 
   base_class->complete_handshake      = g_tls_client_connection_openssl_complete_handshake;
   base_class->verify_peer_certificate = g_tls_client_connection_openssl_verify_peer_certificate;
@@ -282,6 +361,7 @@ g_tls_client_connection_openssl_class_init (GTlsClientConnectionOpensslClass *kl
   g_object_class_override_property (gobject_class, PROP_SERVER_IDENTITY, "server-identity");
   g_object_class_override_property (gobject_class, PROP_USE_SSL3, "use-ssl3");
   g_object_class_override_property (gobject_class, PROP_ACCEPTED_CAS, "accepted-cas");
+  g_object_class_override_property (gobject_class, PROP_SESSION_REUSED, "session-reused");
 }
 
 static void
@@ -432,6 +512,13 @@ set_curve_list (GTlsClientConnectionOpenssl *client)
 }
 #endif
 
+static int g_tls_client_connection_openssl_new_session (SSL *s, SSL_SESSION *sess)
+{
+  GTlsClientConnectionOpenssl *client = G_TLS_CLIENT_CONNECTION_OPENSSL 
(g_tls_connection_openssl_get_connection_from_ssl (s));
+  g_tls_backend_openssl_store_session_data (client->session_id, sess);
+  return 0;
+}
+
 static gboolean
 g_tls_client_connection_openssl_initable_init (GInitable       *initable,
                                                GCancellable    *cancellable,
@@ -442,7 +529,11 @@ g_tls_client_connection_openssl_initable_init (GInitable       *initable,
   const char *hostname;
   char error_buffer[256];
 
-  client->session = SSL_SESSION_new ();
+  client->session = g_tls_backend_openssl_lookup_session_data (client->session_id);
+  if (!client->session)
+    client->session = SSL_SESSION_new ();
+  else
+    client->session_reused = TRUE;
 
   client->ssl_ctx = SSL_CTX_new (g_tls_connection_base_is_dtls (G_TLS_CONNECTION_BASE (client))
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
@@ -468,8 +559,7 @@ g_tls_client_connection_openssl_initable_init (GInitable       *initable,
     return FALSE;
 
   /* Only TLS 1.2 or higher */
-  options = SSL_OP_NO_TICKET |
-            SSL_OP_NO_COMPRESSION |
+  options = SSL_OP_NO_COMPRESSION |
 #ifdef SSL_OP_NO_TLSv1_1
             SSL_OP_NO_TLSv1_1 |
 #endif
@@ -496,6 +586,11 @@ g_tls_client_connection_openssl_initable_init (GInitable       *initable,
 
   SSL_CTX_set_client_cert_cb (client->ssl_ctx, handshake_thread_retrieve_certificate);
 
+  SSL_CTX_set_session_cache_mode (client->ssl_ctx,
+                                  SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);
+
+  SSL_CTX_sess_set_new_cb (client->ssl_ctx, g_tls_client_connection_openssl_new_session);
+
 #ifdef SSL_CTX_set1_sigalgs_list
   set_signature_algorithm_list (client);
 #endif
@@ -514,6 +609,8 @@ g_tls_client_connection_openssl_initable_init (GInitable       *initable,
       return FALSE;
     }
 
+  SSL_set_session (client->ssl, client->session);
+
   if (data_index == -1) {
       data_index = SSL_get_ex_new_index (0, (void *)"gtlsclientconnection", NULL, NULL, NULL);
   }
diff --git a/tls/openssl/gtlsserverconnection-openssl.c b/tls/openssl/gtlsserverconnection-openssl.c
index d24de055..3bfe198d 100644
--- a/tls/openssl/gtlsserverconnection-openssl.c
+++ b/tls/openssl/gtlsserverconnection-openssl.c
@@ -399,8 +399,7 @@ g_tls_server_connection_openssl_initable_init (GInitable       *initable,
     return FALSE;
 
   /* Only TLS 1.2 or higher */
-  options = SSL_OP_NO_TICKET |
-            SSL_OP_NO_COMPRESSION |
+  options = SSL_OP_NO_COMPRESSION |
             SSL_OP_CIPHER_SERVER_PREFERENCE |
             SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
             SSL_OP_SINGLE_ECDH_USE |
diff --git a/tls/tests/connection.c b/tls/tests/connection.c
index 0f8aa2d4..7d514cb1 100644
--- a/tls/tests/connection.c
+++ b/tls/tests/connection.c
@@ -40,6 +40,11 @@
 #include "openssl-include.h"
 #endif
 
+#if defined(G_OS_UNIX)
+#include <dlfcn.h>
+static struct timespec offset;
+#endif
+
 static const gchar *
 tls_test_file_path (const char *name)
 {
@@ -104,6 +109,10 @@ setup_connection (TestConnection *test, gconstpointer data)
   test->context = g_main_context_default ();
   test->loop = g_main_loop_new (test->context, FALSE);
   test->auth_mode = G_TLS_AUTHENTICATION_NONE;
+#if defined(G_OS_UNIX)
+  offset.tv_sec = 0;
+  offset.tv_nsec = 0;
+#endif
 }
 
 /* Waits about 10 seconds for @var to be NULL/FALSE */
@@ -145,6 +154,11 @@ wait_until_server_finished (TestConnection *test)
 static void
 teardown_connection (TestConnection *test, gconstpointer data)
 {
+#if defined(G_OS_UNIX)
+  offset.tv_sec = 0;
+  offset.tv_nsec = 0;
+#endif
+
   if (test->service)
     {
       g_socket_service_stop (test->service);
@@ -552,6 +566,241 @@ read_test_data_async (TestConnection *test)
   g_object_unref (stream);
 }
 
+#if defined(G_OS_UNIX)
+typedef int (*clock_gettime_fnptr)(clockid_t clk_id, struct timespec *tp);
+static __thread clock_gettime_fnptr original_clock_gettime = NULL;
+
+int
+clock_gettime (clockid_t        clk_id,
+               struct timespec *tp)
+{
+  int ret = -1;
+  if (!original_clock_gettime)
+    {
+      original_clock_gettime = dlsym (RTLD_NEXT, "clock_gettime");
+      if (!original_clock_gettime)
+        {
+          errno = EINVAL;
+          return -1;
+        }
+    }
+
+  ret = original_clock_gettime (clk_id, tp);
+  if (ret == 0 && tp)
+    {
+      tp->tv_sec += offset.tv_sec;
+      tp->tv_nsec += offset.tv_nsec;
+    }
+
+  return ret;
+}
+#endif
+
+static void
+test_connection_session_resume_ten_minute_expiry (TestConnection *test,
+                                                 gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+  GTlsCertificate *cert;
+  GSocketClient *client;
+  gboolean reused = FALSE;
+
+#if !defined(G_OS_UNIX)
+  g_test_skip ("test_connection_session_resume_ten_minute_expiry requires interposing clock_gettime which is 
only available in UNIX platforms");
+  return;
+#endif
+
+  test->database = g_tls_file_database_new (tls_test_file_path ("ca-roots.pem"), &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (test->database);
+
+  connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_REQUIRED);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (test->client_connection);
+  g_object_unref (connection);
+
+  cert = g_tls_certificate_new_from_file (tls_test_file_path ("client-and-key.pem"), &error);
+  g_assert_no_error (error);
+  g_tls_connection_set_certificate (G_TLS_CONNECTION (test->client_connection), cert);
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  g_object_get (G_OBJECT (test->client_connection),
+               "session-reused", &reused,
+               NULL);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+  wait_until_server_finished (test);
+
+  g_assert_no_error (test->read_error);
+  g_assert_no_error (test->server_error);
+
+  g_object_unref (cert);
+  g_object_unref (test->client_connection);
+  g_clear_object (&test->server_connection);
+
+  /* First connection was not reused */
+  g_assert_false (reused);
+
+#if defined(G_OS_UNIX)
+  /* Expiry should be 10 min */
+  offset.tv_sec = 11 * 60;
+#endif
+
+  /* Now start a new connection to the same server */
+  client = g_socket_client_new ();
+  connection = G_IO_STREAM (g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address),
+                                                     NULL, &error));
+  g_assert_no_error (error);
+  g_object_unref (client);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (test->client_connection);
+  g_object_unref (connection);
+
+  cert = g_tls_certificate_new_from_file (tls_test_file_path ("client-and-key.pem"), &error);
+  g_assert_no_error (error);
+  g_tls_connection_set_certificate (G_TLS_CONNECTION (test->client_connection), cert);
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  g_object_get (G_OBJECT (test->client_connection),
+               "session-reused", &reused,
+               NULL);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+  wait_until_server_finished (test);
+
+  g_assert_no_error (test->read_error);
+  g_assert_no_error (test->server_error);
+
+  g_object_unref (cert);
+
+  /* Second connection *DID NOT* reuse the first connection */
+#if !defined(BACKEND_IS_GNUTLS)
+  // FIXME: https://gitlab.gnome.org/GNOME/glib-networking/issues/194
+  g_assert_false (reused);
+#endif
+}
+
+static void
+test_connection_session_resume_multiple_times (TestConnection *test,
+                                              gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+  GTlsCertificate *cert;
+  GSocketClient *client;
+  gboolean reused = FALSE;
+
+  test->database = g_tls_file_database_new (tls_test_file_path ("ca-roots.pem"), &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (test->database);
+
+  connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_REQUIRED);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (test->client_connection);
+  g_object_unref (connection);
+
+  cert = g_tls_certificate_new_from_file (tls_test_file_path ("client-and-key.pem"), &error);
+  g_assert_no_error (error);
+  g_tls_connection_set_certificate (G_TLS_CONNECTION (test->client_connection), cert);
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  g_object_get (G_OBJECT (test->client_connection),
+               "session-reused", &reused,
+               NULL);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+  wait_until_server_finished (test);
+
+  g_assert_no_error (test->read_error);
+  g_assert_no_error (test->server_error);
+
+  g_object_unref (cert);
+  g_object_unref (test->client_connection);
+  g_clear_object (&test->server_connection);
+
+  /* First connection was not reused */
+  g_assert_false (reused);
+
+  /* Now start a new connection to the same server */
+  client = g_socket_client_new ();
+  connection = G_IO_STREAM (g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address),
+                                                     NULL, &error));
+  g_assert_no_error (error);
+  g_object_unref (client);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (test->client_connection);
+  g_object_unref (connection);
+
+  cert = g_tls_certificate_new_from_file (tls_test_file_path ("client-and-key.pem"), &error);
+  g_assert_no_error (error);
+  g_tls_connection_set_certificate (G_TLS_CONNECTION (test->client_connection), cert);
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  g_object_get (G_OBJECT (test->client_connection),
+               "session-reused", &reused,
+               NULL);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+  wait_until_server_finished (test);
+
+  g_assert_no_error (test->read_error);
+  g_assert_no_error (test->server_error);
+
+  g_object_unref (cert);
+  g_object_unref (test->client_connection);
+  g_clear_object (&test->server_connection);
+
+  /* Second connection reused the first connection */
+#if !defined(BACKEND_IS_GNUTLS)
+  // FIXME: https://gitlab.gnome.org/GNOME/glib-networking/issues/194
+  g_assert_true (reused);
+#endif
+
+  /* Now start a third connection to the same server */
+  client = g_socket_client_new ();
+  connection = G_IO_STREAM (g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address),
+                                                     NULL, &error));
+  g_assert_no_error (error);
+  g_object_unref (client);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (test->client_connection);
+  g_object_unref (connection);
+
+  cert = g_tls_certificate_new_from_file (tls_test_file_path ("client-and-key.pem"), &error);
+  g_assert_no_error (error);
+  g_tls_connection_set_certificate (G_TLS_CONNECTION (test->client_connection), cert);
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  g_object_get (G_OBJECT (test->client_connection),
+               "session-reused", &reused,
+               NULL);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+  wait_until_server_finished (test);
+
+  g_assert_no_error (test->read_error);
+  g_assert_no_error (test->server_error);
+
+  g_object_unref (cert);
+
+  /* Third connection reused the first connection */
+#if !defined(BACKEND_IS_GNUTLS)
+  // FIXME: https://gitlab.gnome.org/GNOME/glib-networking/issues/194
+  g_assert_true (reused);
+#endif
+}
+
 static void
 test_basic_connection (TestConnection *test,
                        gconstpointer   data)
@@ -3122,6 +3371,10 @@ main (int   argc,
   g_free (module_path);
 #endif
 
+  g_test_add ("/tls/" BACKEND "/connection/session/resume_multiple_times", TestConnection, NULL,
+              setup_connection, test_connection_session_resume_multiple_times, teardown_connection);
+  g_test_add ("/tls/" BACKEND "/connection/session/reuse_ten_minute_expiry", TestConnection, NULL,
+              setup_connection, test_connection_session_resume_ten_minute_expiry, teardown_connection);
   g_test_add ("/tls/" BACKEND "/connection/basic", TestConnection, NULL,
               setup_connection, test_basic_connection, teardown_connection);
   g_test_add ("/tls/" BACKEND "/connection/verified", TestConnection, NULL,
diff --git a/tls/tests/meson.build b/tls/tests/meson.build
index 4afcc815..6c51ac7a 100644
--- a/tls/tests/meson.build
+++ b/tls/tests/meson.build
@@ -48,6 +48,7 @@ test_programs = [
 foreach backend: backends
   foreach program: test_programs
     program_name = program[0] + '-' + backend
+    program_deps = program[2]
 
     test_conf = configuration_data()
     test_conf.set('installed_tests_dir', installed_tests_execdir)
@@ -67,8 +68,13 @@ foreach backend: backends
       '-DBACKEND="@0@"'.format(backend),
       '-DBACKEND_IS_' + backend.to_upper(),
       '-DSIZEOF_TIME_T=@0@'.format(cc.sizeof('time_t', prefix: '#include <time.h>')),
+      '-D_GNU_SOURCE',
     ]
 
+    if not ['windows'].contains(host_system)
+      program_deps += cc.find_library('dl')
+    endif
+
     if backend == 'openssl'
       incs += openssl_inc
     endif
@@ -77,7 +83,7 @@ foreach backend: backends
       program_name,
       [program[0] + '.c'] + program[1],
       include_directories: incs,
-      dependencies: program[2],
+      dependencies: program_deps,
       c_args: test_cflags,
       install: enable_installed_tests,
       install_dir: installed_tests_execdir


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]