[glib-networking] Implement ALPN support



commit 5e085d7f4fdc3d6a69f6f2ef4dca986b1a9571a1
Author: Scott Hutton <schutton cisco com>
Date:   Wed Dec 19 19:34:30 2018 -0800

    Implement ALPN support
    
    Fixes #47

 tls/gnutls/gtlsconnection-gnutls.c | 109 +++++++++++++++++++++++++++++++++++++
 tls/tests/connection.c             | 100 ++++++++++++++++++++++++++++++++++
 tls/tests/dtls-connection.c        | 101 ++++++++++++++++++++++++++++++++++
 3 files changed, 310 insertions(+)
---
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index 3200079..31ce8cf 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -130,6 +130,10 @@ enum
   PROP_INTERACTION,
   PROP_PEER_CERTIFICATE,
   PROP_PEER_CERTIFICATE_ERRORS,
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  PROP_ADVERTISED_PROTOCOLS,
+  PROP_NEGOTIATED_PROTOCOL,
+#endif
 };
 
 typedef struct
@@ -214,6 +218,11 @@ typedef struct
   GTlsInteraction *interaction;
   gchar *interaction_id;
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  gchar **advertised_protocols;
+  gchar *negotiated_protocol;
+#endif
+
   GMutex        op_mutex;
   GCancellable *waiting_for_op;
 
@@ -456,6 +465,11 @@ g_tls_connection_gnutls_finalize (GObject *object)
   g_free (priv->interaction_id);
   g_clear_object (&priv->interaction);
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  g_clear_pointer (&priv->advertised_protocols, g_strfreev);
+  g_clear_pointer (&priv->negotiated_protocol, g_free);
+#endif
+
   g_clear_error (&priv->handshake_error);
   g_clear_error (&priv->read_error);
   g_clear_error (&priv->write_error);
@@ -534,6 +548,16 @@ g_tls_connection_gnutls_get_property (GObject    *object,
       g_value_set_flags (value, priv->peer_certificate_errors);
       break;
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+    case PROP_ADVERTISED_PROTOCOLS:
+      g_value_set_boxed (value, priv->advertised_protocols);
+      break;
+
+    case PROP_NEGOTIATED_PROTOCOL:
+      g_value_set_string (value, priv->negotiated_protocol);
+      break;
+#endif
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -628,6 +652,13 @@ g_tls_connection_gnutls_set_property (GObject      *object,
       priv->interaction = g_value_dup_object (value);
       break;
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+    case PROP_ADVERTISED_PROTOCOLS:
+      g_clear_pointer (&priv->advertised_protocols, g_strfreev);
+      priv->advertised_protocols = g_value_dup_boxed (value);
+      break;
+#endif
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -2047,9 +2078,56 @@ handshake_thread (GTask        *task,
 static void
 begin_handshake (GTlsConnectionGnutls *gnutls)
 {
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  if (priv->advertised_protocols)
+    {
+      gnutls_datum_t *protocols;
+      int n_protos, i;
+
+      n_protos = g_strv_length (priv->advertised_protocols);
+      protocols = g_new (gnutls_datum_t, n_protos);
+      for (i = 0; priv->advertised_protocols[i]; i++)
+        {
+          protocols[i].size = strlen (priv->advertised_protocols[i]);
+          protocols[i].data = g_memdup (priv->advertised_protocols[i], protocols[i].size);
+        }
+      gnutls_alpn_set_protocols (priv->session, protocols, n_protos, 0);
+      g_free (protocols);
+    }
+#endif
+
   G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->begin_handshake (gnutls);
 }
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+static void
+update_negotiated_protocol (GTlsConnectionGnutls *gnutls)
+{
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+  gchar *orig_negotiated_protocol;
+  gnutls_datum_t protocol;
+
+  /*
+   * Preserve the prior negotiated protocol before clearing it
+   */
+  orig_negotiated_protocol = g_steal_pointer (&priv->negotiated_protocol);
+
+
+  if (gnutls_alpn_get_selected_protocol (priv->session, &protocol) == 0 && protocol.size > 0)
+    priv->negotiated_protocol = g_strndup ((gchar *)protocol.data, protocol.size);
+
+  /*
+   * Notify only if the negotiated protocol changed
+   */
+  if (g_strcmp0 (orig_negotiated_protocol, priv->negotiated_protocol) != 0)
+    g_object_notify (G_OBJECT (gnutls), "negotiated-protocol");
+
+  g_free (orig_negotiated_protocol);
+}
+#endif
+
 static gboolean
 finish_handshake (GTlsConnectionGnutls  *gnutls,
                   GTask                 *task,
@@ -2082,6 +2160,11 @@ finish_handshake (GTlsConnectionGnutls  *gnutls,
                            _("Unacceptable TLS certificate"));
     }
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  if (!*error && priv->advertised_protocols)
+    update_negotiated_protocol (gnutls);
+#endif
+
   if (*error && priv->started_handshake)
     priv->handshake_error = g_error_copy (*error);
 
@@ -3007,6 +3090,24 @@ g_tls_connection_gnutls_dtls_shutdown_finish (GDtlsConnection  *conn,
   return g_task_propagate_boolean (G_TASK (result), error);
 }
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+static void
+g_tls_connection_gnutls_dtls_set_advertised_protocols (GDtlsConnection     *conn,
+                                                       const gchar * const *protocols)
+{
+  g_object_set (conn, "advertised-protocols", protocols, NULL);
+}
+
+const gchar *
+g_tls_connection_gnutls_dtls_get_negotiated_protocol (GDtlsConnection *conn)
+{
+  GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
+  GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+  return priv->negotiated_protocol;
+}
+#endif
+
 static void
 g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
 {
@@ -3039,6 +3140,10 @@ g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
   g_object_class_override_property (gobject_class, PROP_INTERACTION, "interaction");
   g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE, "peer-certificate");
   g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE_ERRORS, "peer-certificate-errors");
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  g_object_class_override_property (gobject_class, PROP_ADVERTISED_PROTOCOLS, "advertised-protocols");
+  g_object_class_override_property (gobject_class, PROP_NEGOTIATED_PROTOCOL, "negotiated-protocol");
+#endif
 }
 
 static void
@@ -3056,6 +3161,10 @@ g_tls_connection_gnutls_dtls_connection_iface_init (GDtlsConnectionInterface *if
   iface->shutdown = g_tls_connection_gnutls_dtls_shutdown;
   iface->shutdown_async = g_tls_connection_gnutls_dtls_shutdown_async;
   iface->shutdown_finish = g_tls_connection_gnutls_dtls_shutdown_finish;
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  iface->set_advertised_protocols = g_tls_connection_gnutls_dtls_set_advertised_protocols;
+  iface->get_negotiated_protocol = g_tls_connection_gnutls_dtls_get_negotiated_protocol;
+#endif
 }
 
 static void
diff --git a/tls/tests/connection.c b/tls/tests/connection.c
index 41d73c8..41b66b7 100644
--- a/tls/tests/connection.c
+++ b/tls/tests/connection.c
@@ -79,6 +79,9 @@ typedef struct {
   gboolean server_should_close;
   gboolean server_running;
   GTlsCertificate *server_certificate;
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  const gchar * const *server_protocols;
+#endif
 
   char buf[128];
   gssize nread, nwrote;
@@ -302,6 +305,14 @@ on_incoming_connection (GSocketService     *service,
   if (test->database)
     g_tls_connection_set_database (G_TLS_CONNECTION (test->server_connection), test->database);
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  if (test->server_protocols)
+    {
+      g_tls_connection_set_advertised_protocols (G_TLS_CONNECTION (test->server_connection),
+                                                 test->server_protocols);
+    }
+#endif
+
   stream = g_io_stream_get_output_stream (test->server_connection);
 
   g_output_stream_write_async (stream, TEST_DATA,
@@ -2068,6 +2079,86 @@ test_readwrite_after_connection_destroyed (TestConnection *test,
   g_object_unref (ostream);
 }
 
+static void
+test_alpn (TestConnection *test,
+           const char * const *client_protocols,
+           const char * const *server_protocols,
+           const char *negotiated_protocol)
+{
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  GIOStream *connection;
+  GError *error = NULL;
+
+  test->server_protocols = server_protocols;
+
+  test->database = g_tls_file_database_new (tls_test_file_path ("ca-roots.pem"), &error);
+  g_assert_no_error (error);
+  g_assert (test->database);
+
+  connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_object_unref (connection);
+
+  if (client_protocols)
+    {
+      g_tls_connection_set_advertised_protocols (G_TLS_CONNECTION (test->client_connection),
+                                                 client_protocols);
+    }
+
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+
+  g_assert_no_error (test->read_error);
+  g_assert_no_error (test->server_error);
+
+  g_assert_cmpstr (g_tls_connection_get_negotiated_protocol (G_TLS_CONNECTION (test->server_connection)), 
==, negotiated_protocol);
+  g_assert_cmpstr (g_tls_connection_get_negotiated_protocol (G_TLS_CONNECTION (test->client_connection)), 
==, negotiated_protocol);
+#else
+  g_test_skip ("no support for ALPN in this GLib version");
+#endif
+}
+
+static void
+test_alpn_match (TestConnection *test,
+                 gconstpointer   data)
+{
+  const char * const client_protocols[] = { "one", "two", "three", NULL };
+  const char * const server_protocols[] = { "four", "seven", "nine", "two", NULL };
+
+  test_alpn (test, client_protocols, server_protocols, "two");
+}
+
+static void
+test_alpn_no_match (TestConnection *test,
+                    gconstpointer   data)
+{
+  const char * const client_protocols[] = { "one", "two", "three", NULL };
+  const char * const server_protocols[] = { "four", "seven", "nine", NULL };
+
+  test_alpn (test, client_protocols, server_protocols, NULL);
+}
+
+static void
+test_alpn_client_only (TestConnection *test,
+                       gconstpointer   data)
+{
+  const char * const client_protocols[] = { "one", "two", "three", NULL };
+
+  test_alpn (test, client_protocols, NULL, NULL);
+}
+
+static void
+test_alpn_server_only (TestConnection *test,
+                       gconstpointer   data)
+{
+  const char * const server_protocols[] = { "four", "seven", "nine", "two", NULL };
+
+  test_alpn (test, NULL, server_protocols, NULL);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -2140,6 +2231,15 @@ main (int   argc,
   g_test_add ("/tls/connection/readwrite-after-connection-destroyed", TestConnection, NULL,
               setup_connection, test_readwrite_after_connection_destroyed, teardown_connection);
 
+  g_test_add ("/tls/connection/alpn/match", TestConnection, NULL,
+              setup_connection, test_alpn_match, teardown_connection);
+  g_test_add ("/tls/connection/alpn/no-match", TestConnection, NULL,
+              setup_connection, test_alpn_no_match, teardown_connection);
+  g_test_add ("/tls/connection/alpn/client-only", TestConnection, NULL,
+              setup_connection, test_alpn_client_only, teardown_connection);
+  g_test_add ("/tls/connection/alpn/server-only", TestConnection, NULL,
+              setup_connection, test_alpn_server_only, teardown_connection);
+
   ret = g_test_run ();
 
   /* for valgrinding */
diff --git a/tls/tests/dtls-connection.c b/tls/tests/dtls-connection.c
index 105b879..a589650 100644
--- a/tls/tests/dtls-connection.c
+++ b/tls/tests/dtls-connection.c
@@ -89,6 +89,9 @@ typedef struct {
   gboolean expect_server_error;
   GError *server_error;
   gboolean server_running;
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  const gchar * const *server_protocols;
+#endif
 
   char buf[128];
   gssize nread, nwrote;
@@ -397,6 +400,14 @@ on_incoming_connection (GSocket       *socket,
   if (test->database)
     g_dtls_connection_set_database (G_DTLS_CONNECTION (test->server_connection), test->database);
 
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  if (test->server_protocols)
+    {
+      g_dtls_connection_set_advertised_protocols (G_DTLS_CONNECTION (test->server_connection),
+                                                  test->server_protocols);
+    }
+#endif
+
   if (test->test_data->server_should_disappear)
     {
       close_server_connection (test, FALSE);
@@ -726,6 +737,83 @@ test_connection_timeouts_read (TestConnection *test,
   g_assert_error (test->read_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT);
 }
 
+static void
+test_alpn (TestConnection *test,
+           const char * const *client_protocols,
+           const char * const *server_protocols,
+           const char *negotiated_protocol)
+{
+#if GLIB_CHECK_VERSION(2, 59, 1)
+  GDatagramBased *connection;
+  GError *error = NULL;
+
+  test->server_protocols = server_protocols;
+
+  test->database = g_tls_file_database_new (tls_test_file_path ("ca-roots.pem"), &error);
+  g_assert_no_error (error);
+  g_assert (test->database);
+
+  connection = start_server_and_connect_to_it (test, FALSE);
+  test->client_connection = g_dtls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_object_unref (connection);
+
+  if (client_protocols)
+    {
+      g_dtls_connection_set_advertised_protocols (G_DTLS_CONNECTION (test->client_connection),
+             client_protocols);
+    }
+
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  read_test_data_async (test);
+  while (!test->loop_finished)
+    g_main_context_iteration (test->client_context, TRUE);
+
+  g_assert_no_error (test->server_error);
+  g_assert_no_error (test->read_error);
+
+  g_assert_cmpstr (g_dtls_connection_get_negotiated_protocol (G_DTLS_CONNECTION (test->server_connection)), 
==, negotiated_protocol);
+  g_assert_cmpstr (g_dtls_connection_get_negotiated_protocol (G_DTLS_CONNECTION (test->client_connection)), 
==, negotiated_protocol);
+#else
+  g_test_skip ("no support for ALPN in this GLib version");
+#endif
+}
+
+static void
+test_alpn_match (TestConnection *test, gconstpointer data)
+{
+  const char * const client_protocols[] = { "one", "two", "three", NULL };
+  const char * const server_protocols[] = { "four", "seven", "nine", "two", NULL };
+
+  test_alpn (test, client_protocols, server_protocols, "two");
+}
+
+static void
+test_alpn_no_match (TestConnection *test, gconstpointer data)
+{
+  const char * const client_protocols[] = { "one", "two", "three", NULL };
+  const char * const server_protocols[] = { "four", "seven", "nine", NULL };
+
+  test_alpn (test, client_protocols, server_protocols, NULL);
+}
+
+static void
+test_alpn_client_only (TestConnection *test, gconstpointer data)
+{
+  const char * const client_protocols[] = { "one", "two", "three", NULL };
+
+  test_alpn (test, client_protocols, NULL, NULL);
+}
+
+static void
+test_alpn_server_only (TestConnection *test, gconstpointer data)
+{
+  const char * const server_protocols[] = { "four", "seven", "nine", "two", NULL };
+
+  test_alpn (test, NULL, server_protocols, NULL);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -807,6 +895,19 @@ main (int   argc,
               setup_connection, test_connection_timeouts_read,
               teardown_connection);
 
+  g_test_add ("/dtls/connection/alpn/match", TestConnection, &blocking,
+              setup_connection, test_alpn_match,
+              teardown_connection);
+  g_test_add ("/dtls/connection/alpn/no-match", TestConnection, &blocking,
+              setup_connection, test_alpn_no_match,
+              teardown_connection);
+  g_test_add ("/dtls/connection/alpn/client-only", TestConnection, &blocking,
+              setup_connection, test_alpn_client_only,
+              teardown_connection);
+  g_test_add ("/dtls/connection/alpn/server-only", TestConnection, &blocking,
+              setup_connection, test_alpn_server_only,
+              teardown_connection);
+
   ret = g_test_run ();
 
   /* for valgrinding */


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