[glib-networking] gnutls: Add support for requesting certificate via GTlsInteraction



commit b3e2c99f2a84577380fcccd32508433060b796ee
Author: Stef Walter <stefw gnome org>
Date:   Thu Nov 29 09:52:53 2012 +0100

    gnutls: Add support for requesting certificate via GTlsInteraction
    
    When gnutls asks us for to provide a certificate, on the client we
    ask our GTlsInteraction (if available) to provide that certificate.
    
    This allows us to provide a certificate during the handshake, rather
    than completing the handshake and forcing the caller to connect again
    this time with a certificate.
    
    Fix ups by Dan Winship <danw gnome org>
    
    https://bugzilla.gnome.org/show_bug.cgi?id=637257

 tls/gnutls/gtlsclientconnection-gnutls.c |   34 +++++++---
 tls/gnutls/gtlsconnection-gnutls.c       |   21 ++++++
 tls/gnutls/gtlsconnection-gnutls.h       |    8 ++-
 tls/tests/Makefile.am                    |    3 +
 tls/tests/connection.c                   |  102 +++++++++++++++++++++++++++-
 tls/tests/mock-interaction.c             |  109 ++++++++++++++++++++++++++++--
 tls/tests/mock-interaction.h             |   11 +++-
 tls/tests/pkcs11-slot.c                  |    2 +-
 8 files changed, 271 insertions(+), 19 deletions(-)
---
diff --git a/tls/gnutls/gtlsclientconnection-gnutls.c b/tls/gnutls/gtlsclientconnection-gnutls.c
index 07a3a00..69ae6c3 100644
--- a/tls/gnutls/gtlsclientconnection-gnutls.c
+++ b/tls/gnutls/gtlsclientconnection-gnutls.c
@@ -61,6 +61,7 @@ struct _GTlsClientConnectionGnutlsPrivate
   GBytes *session_id;
 
   gboolean cert_requested;
+  GError *cert_error;
   GPtrArray *accepted_cas;
 };
 
@@ -137,12 +138,10 @@ g_tls_client_connection_gnutls_finalize (GObject *object)
 {
   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
 
-  if (gnutls->priv->server_identity)
-    g_object_unref (gnutls->priv->server_identity);
-  if (gnutls->priv->accepted_cas)
-    g_ptr_array_unref (gnutls->priv->accepted_cas);
-  if (gnutls->priv->session_id)
-    g_bytes_unref (gnutls->priv->session_id);
+  g_clear_object (&gnutls->priv->server_identity);
+  g_clear_pointer (&gnutls->priv->accepted_cas, g_ptr_array_unref);
+  g_clear_pointer (&gnutls->priv->session_id, g_bytes_unref);
+  g_clear_error (&gnutls->priv->cert_error);
 
   G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->finalize (object);
 }
@@ -238,6 +237,7 @@ g_tls_client_connection_gnutls_retrieve_function (gnutls_session_t             s
                                                  gnutls_retr2_st             *st)
 {
   GTlsClientConnectionGnutls *gnutls = gnutls_transport_get_ptr (session);
+  GTlsConnectionGnutls *conn = G_TLS_CONNECTION_GNUTLS (gnutls);
   GPtrArray *accepted_cas;
   GByteArray *dn;
   int i;
@@ -257,7 +257,15 @@ g_tls_client_connection_gnutls_retrieve_function (gnutls_session_t             s
   gnutls->priv->accepted_cas = accepted_cas;
   g_object_notify (G_OBJECT (gnutls), "accepted-cas");
 
-  g_tls_connection_gnutls_get_certificate (G_TLS_CONNECTION_GNUTLS (gnutls), st);
+  g_tls_connection_gnutls_get_certificate (conn, st);
+
+  if (st->ncerts == 0)
+    {
+      g_clear_error (&gnutls->priv->cert_error);
+      if (g_tls_connection_gnutls_request_certificate (conn, &gnutls->priv->cert_error))
+        g_tls_connection_gnutls_get_certificate (conn, st);
+    }
+
   return 0;
 }
 
@@ -305,8 +313,16 @@ g_tls_client_connection_gnutls_finish_handshake (GTlsConnectionGnutls  *conn,
       gnutls->priv->cert_requested)
     {
       g_clear_error (inout_error);
-      g_set_error_literal (inout_error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
-                          _("Server required TLS certificate"));
+      if (gnutls->priv->cert_error)
+       {
+         *inout_error = gnutls->priv->cert_error;
+         gnutls->priv->cert_error = NULL;
+       }
+      else
+       {
+         g_set_error_literal (inout_error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
+                              _("Server required TLS certificate"));
+       }
     }
 
   if (gnutls->priv->session_id)
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index 35bcaad..b219416 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -1711,3 +1711,24 @@ g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface)
 {
   iface->init = g_tls_connection_gnutls_initable_init;
 }
+
+gboolean
+g_tls_connection_gnutls_request_certificate (GTlsConnectionGnutls  *self,
+                                            GError               **error)
+{
+  GTlsInteractionResult res = G_TLS_INTERACTION_UNHANDLED;
+  GTlsInteraction *interaction;
+  GTlsConnection *conn;
+
+  g_return_val_if_fail (G_IS_TLS_CONNECTION_GNUTLS (self), FALSE);
+
+  conn = G_TLS_CONNECTION (self);
+
+  interaction = g_tls_connection_get_interaction (conn);
+  if (!interaction)
+    return FALSE;
+
+  res = g_tls_interaction_invoke_request_certificate (interaction, conn, 0,
+                                                     self->priv->read_cancellable, error);
+  return res != G_TLS_INTERACTION_FAILED;
+}
diff --git a/tls/gnutls/gtlsconnection-gnutls.h b/tls/gnutls/gtlsconnection-gnutls.h
index 3aa8473..a1d0b3d 100644
--- a/tls/gnutls/gtlsconnection-gnutls.h
+++ b/tls/gnutls/gtlsconnection-gnutls.h
@@ -50,8 +50,12 @@ GType g_tls_connection_gnutls_get_type (void) G_GNUC_CONST;
 
 gnutls_certificate_credentials_t g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *connection);
 gnutls_session_t                 g_tls_connection_gnutls_get_session     (GTlsConnectionGnutls *connection);
-void                             g_tls_connection_gnutls_get_certificate (GTlsConnectionGnutls *gnutls,
-                                                                        gnutls_retr2_st      *st);
+
+void     g_tls_connection_gnutls_get_certificate     (GTlsConnectionGnutls  *gnutls,
+                                                     gnutls_retr2_st       *st);
+
+gboolean g_tls_connection_gnutls_request_certificate (GTlsConnectionGnutls  *gnutls,
+                                                     GError               **error);
 
 gssize   g_tls_connection_gnutls_read          (GTlsConnectionGnutls  *gnutls,
                                                void                  *buffer,
diff --git a/tls/tests/Makefile.am b/tls/tests/Makefile.am
index df51f91..6287b9c 100644
--- a/tls/tests/Makefile.am
+++ b/tls/tests/Makefile.am
@@ -33,6 +33,9 @@ pkcs11_slot_SOURCES = pkcs11-slot.c \
        mock-pkcs11.c mock-pkcs11.h \
        mock-interaction.c mock-interaction.h
 
+connection_SOURCES = connection.c \
+       mock-interaction.c mock-interaction.h
+
 endif
 
 testfiles_data =                               \
diff --git a/tls/tests/connection.c b/tls/tests/connection.c
index 3c57c38..7f3a328 100644
--- a/tls/tests/connection.c
+++ b/tls/tests/connection.c
@@ -19,6 +19,10 @@
  * Author: Stef Walter <stefw collabora co uk>
  */
 
+#include "config.h"
+
+#include "mock-interaction.h"
+
 #include <gio/gio.h>
 
 #include <sys/types.h>
@@ -51,6 +55,7 @@ tls_test_file_path (const char *name)
 #define TEST_DATA_LENGTH 24
 
 typedef struct {
+  GMainContext *context;
   GMainLoop *loop;
   GSocketService *service;
   GTlsDatabase *database;
@@ -76,7 +81,8 @@ setup_connection (TestConnection *test, gconstpointer data)
   GInetAddress *inet;
   guint16 port;
 
-  test->loop = g_main_loop_new (NULL, FALSE);
+  test->context = g_main_context_default ();
+  test->loop = g_main_loop_new (test->context, FALSE);
 
   test->auth_mode = G_TLS_AUTHENTICATION_NONE;
 
@@ -587,6 +593,96 @@ test_client_auth_failure (TestConnection *test,
 }
 
 static void
+test_client_auth_request_cert (TestConnection *test,
+                               gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+  GTlsCertificate *cert;
+  GTlsCertificate *peer;
+  GTlsInteraction *interaction;
+  gboolean cas_changed;
+
+  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_REQUIRED);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert (test->client_connection);
+  g_object_unref (connection);
+
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  /* Have the interaction return a certificate */
+  cert = g_tls_certificate_new_from_file (tls_test_file_path ("client-and-key.pem"), &error);
+  g_assert_no_error (error);
+  interaction = mock_interaction_new_static_certificate (cert);
+  g_tls_connection_set_interaction (G_TLS_CONNECTION (test->client_connection), interaction);
+  g_object_unref (interaction);
+
+  /* All validation in this test */
+  g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection),
+                                                G_TLS_CERTIFICATE_VALIDATE_ALL);
+
+  cas_changed = FALSE;
+  g_signal_connect (test->client_connection, "notify::accepted-cas",
+                    G_CALLBACK (on_notify_accepted_cas), &cas_changed);
+
+  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);
+
+  peer = g_tls_connection_get_peer_certificate (G_TLS_CONNECTION (test->server_connection));
+  g_assert (peer != NULL);
+  g_assert (g_tls_certificate_is_same (peer, cert));
+  g_assert (cas_changed == TRUE);
+
+  g_object_unref (cert);
+}
+
+static void
+test_client_auth_request_fail (TestConnection *test,
+                               gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+  GTlsInteraction *interaction;
+
+  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_REQUIRED);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert (test->client_connection);
+  g_object_unref (connection);
+
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  /* Have the interaction return an error */
+  interaction = mock_interaction_new_static_error (G_FILE_ERROR, G_FILE_ERROR_ACCES, "Request message");
+  g_tls_connection_set_interaction (G_TLS_CONNECTION (test->client_connection), interaction);
+  g_object_unref (interaction);
+
+  /* All validation in this test */
+  g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection),
+                                                G_TLS_CERTIFICATE_VALIDATE_ALL);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+
+  g_assert_error (test->read_error, G_FILE_ERROR, G_FILE_ERROR_ACCES);
+
+  g_io_stream_close (test->server_connection, NULL, NULL);
+  g_io_stream_close (test->client_connection, NULL, NULL);
+}
+
+static void
 test_connection_no_database (TestConnection *test,
                              gconstpointer   data)
 {
@@ -1104,6 +1200,10 @@ main (int   argc,
               setup_connection, test_client_auth_rehandshake, teardown_connection);
   g_test_add ("/tls/connection/client-auth-failure", TestConnection, NULL,
               setup_connection, test_client_auth_failure, teardown_connection);
+  g_test_add ("/tls/connection/client-auth-request-cert", TestConnection, NULL,
+              setup_connection, test_client_auth_request_cert, teardown_connection);
+  g_test_add ("/tls/connection/client-auth-request-fail", TestConnection, NULL,
+              setup_connection, test_client_auth_request_fail, teardown_connection);
   g_test_add ("/tls/connection/no-database", TestConnection, NULL,
               setup_connection, test_connection_no_database, teardown_connection);
   g_test_add ("/tls/connection/failed", TestConnection, NULL,
diff --git a/tls/tests/mock-interaction.c b/tls/tests/mock-interaction.c
index ee518a5..6a1d7fd 100644
--- a/tls/tests/mock-interaction.c
+++ b/tls/tests/mock-interaction.c
@@ -40,8 +40,12 @@ mock_interaction_ask_password_async (GTlsInteraction    *interaction,
 
   task = g_task_new (interaction, cancellable, callback, user_data);
 
-  g_tls_password_set_value (password, (const guchar *)self->static_password, -1);
+  if (self->static_error)
+    g_task_return_error (task, g_error_copy (self->static_error));
+  else
+    g_tls_password_set_value (password, (const guchar *)self->static_password, -1);
   g_task_return_boolean (task, TRUE);
+  g_object_unref (task);
 }
 
 static GTlsInteractionResult
@@ -72,8 +76,77 @@ mock_interaction_ask_password (GTlsInteraction    *interaction,
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return G_TLS_INTERACTION_FAILED;
 
-  g_tls_password_set_value (password, (const guchar *)self->static_password, -1);
-  return G_TLS_INTERACTION_HANDLED;
+  if (self->static_error)
+    {
+      g_propagate_error (error, g_error_copy (self->static_error));
+      return G_TLS_INTERACTION_FAILED;
+    }
+  else
+    {
+      g_tls_password_set_value (password, (const guchar *)self->static_password, -1);
+      return G_TLS_INTERACTION_HANDLED;
+    }
+}
+
+static void
+mock_interaction_request_certificate_async (GTlsInteraction            *interaction,
+                                            GTlsConnection             *connection,
+                                            GTlsCertificateRequestFlags flags,
+                                            GCancellable               *cancellable,
+                                            GAsyncReadyCallback         callback,
+                                            gpointer                    user_data)
+{
+  MockInteraction *self = MOCK_INTERACTION (interaction);
+  GTask *task;
+
+  task = g_task_new (interaction, cancellable, callback, user_data);
+
+  if (self->static_error)
+    g_task_return_error (task, g_error_copy (self->static_error));
+  else
+    {
+      g_tls_connection_set_certificate (connection, self->static_certificate);
+      g_task_return_boolean (task, TRUE);
+    }
+  g_object_unref (task);
+}
+
+static GTlsInteractionResult
+mock_interaction_request_certificate_finish (GTlsInteraction    *interaction,
+                                             GAsyncResult       *result,
+                                             GError            **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, interaction),
+                        G_TLS_INTERACTION_UNHANDLED);
+
+  if (!g_task_propagate_boolean (G_TASK (result), error))
+    return G_TLS_INTERACTION_FAILED;
+  else
+    return G_TLS_INTERACTION_HANDLED;
+}
+
+static GTlsInteractionResult
+mock_interaction_request_certificate (GTlsInteraction            *interaction,
+                                      GTlsConnection             *connection,
+                                      GTlsCertificateRequestFlags flags,
+                                      GCancellable               *cancellable,
+                                      GError                    **error)
+{
+  MockInteraction *self = MOCK_INTERACTION (interaction);
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return G_TLS_INTERACTION_FAILED;
+
+  if (self->static_error)
+    {
+      g_propagate_error (error, g_error_copy (self->static_error));
+      return G_TLS_INTERACTION_FAILED;
+    }
+  else
+    {
+      g_tls_connection_set_certificate (connection, self->static_certificate);
+      return G_TLS_INTERACTION_HANDLED;
+    }
 }
 
 static void
@@ -103,11 +176,13 @@ mock_interaction_class_init (MockInteractionClass *klass)
   interaction_class->ask_password = mock_interaction_ask_password;
   interaction_class->ask_password_async = mock_interaction_ask_password_async;
   interaction_class->ask_password_finish = mock_interaction_ask_password_finish;
-
+  interaction_class->request_certificate = mock_interaction_request_certificate;
+  interaction_class->request_certificate_async = mock_interaction_request_certificate_async;
+  interaction_class->request_certificate_finish = mock_interaction_request_certificate_finish;
 }
 
 GTlsInteraction *
-mock_interaction_new_static (const gchar *password)
+mock_interaction_new_static_password (const gchar *password)
 {
   MockInteraction *self;
 
@@ -116,3 +191,27 @@ mock_interaction_new_static (const gchar *password)
   self->static_password = g_strdup (password);
   return G_TLS_INTERACTION (self);
 }
+
+GTlsInteraction *
+mock_interaction_new_static_certificate (GTlsCertificate *cert)
+{
+  MockInteraction *self;
+
+  self = g_object_new (MOCK_TYPE_INTERACTION, NULL);
+
+  self->static_certificate = cert ? g_object_ref (cert) : NULL;
+  return G_TLS_INTERACTION (self);
+}
+
+GTlsInteraction *
+mock_interaction_new_static_error (GQuark domain,
+                                   gint code,
+                                   const gchar *message)
+{
+  MockInteraction *self;
+
+  self = g_object_new (MOCK_TYPE_INTERACTION, NULL);
+
+  self->static_error = g_error_new (domain, code, "%s", message);
+  return G_TLS_INTERACTION (self);
+}
diff --git a/tls/tests/mock-interaction.h b/tls/tests/mock-interaction.h
index 90668c7..f357d8a 100644
--- a/tls/tests/mock-interaction.h
+++ b/tls/tests/mock-interaction.h
@@ -41,6 +41,8 @@ struct _MockInteraction
 {
   GTlsInteraction parent_instance;
   gchar *static_password;
+  GTlsCertificate *static_certificate;
+  GError *static_error;
 };
 
 struct _MockInteractionClass
@@ -50,7 +52,14 @@ struct _MockInteractionClass
 
 
 GType            mock_interaction_get_type   (void);
-GTlsInteraction *mock_interaction_new_static       (const gchar *password);
+
+GTlsInteraction *mock_interaction_new_static_password       (const gchar *password);
+
+GTlsInteraction *mock_interaction_new_static_certificate    (GTlsCertificate *cert);
+
+GTlsInteraction *mock_interaction_new_static_error          (GQuark domain,
+                                                             gint code,
+                                                             const gchar *message);
 
 G_END_DECLS
 
diff --git a/tls/tests/pkcs11-slot.c b/tls/tests/pkcs11-slot.c
index 0d80044..6165bf3 100644
--- a/tls/tests/pkcs11-slot.c
+++ b/tls/tests/pkcs11-slot.c
@@ -463,7 +463,7 @@ test_enumerate_private (TestSlot     *test,
 
   /* This time we log in, and should have a match */
   results = g_ptr_array_new_with_free_func ((GDestroyNotify)g_pkcs11_array_unref);
-  interaction = mock_interaction_new_static (MOCK_SLOT_ONE_PIN);
+  interaction = mock_interaction_new_static_password (MOCK_SLOT_ONE_PIN);
 
   state = g_pkcs11_slot_enumerate (test->slot, interaction,
                                    match->attrs, match->count, TRUE,


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