[libsoup/carlosgc/client-side-certs2: 7/17] message: add API to handle client side certificates




commit d72d9eabd74880be037818deb4c6ae73fdda697b
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Mon Apr 19 15:07:55 2021 +0200

    message: add API to handle client side certificates
    
    When SoupSession doesn't have a GTlsInteraction set, SoupMessage can
    handle client side certificates. A new signal request-certificate is
    emitted when the connection requests a certificate and
    soup_message_set_tls_client_certificate() can be called to complete the
    request.

 docs/reference/libsoup-3.0-sections.txt |   1 +
 docs/reference/meson.build              |   1 +
 libsoup/auth/soup-tls-interaction.c     |  96 +++++++++++++++++++++++
 libsoup/auth/soup-tls-interaction.h     |  19 +++++
 libsoup/meson.build                     |   1 +
 libsoup/soup-connection.c               |  82 +++++++++++++++++++-
 libsoup/soup-connection.h               |   8 ++
 libsoup/soup-message.c                  | 115 +++++++++++++++++++++++++++
 libsoup/soup-message.h                  |   4 +
 tests/ssl-test.c                        | 133 ++++++++++++++++++++++++++++++++
 10 files changed, 459 insertions(+), 1 deletion(-)
---
diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt
index b34559f4..19d814a9 100644
--- a/docs/reference/libsoup-3.0-sections.txt
+++ b/docs/reference/libsoup-3.0-sections.txt
@@ -31,6 +31,7 @@ soup_message_get_connection_id
 soup_message_get_remote_address
 soup_message_get_tls_peer_certificate
 soup_message_get_tls_peer_certificate_errors
+soup_message_set_tls_client_certificate
 <SUBSECTION>
 soup_message_set_first_party
 soup_message_get_first_party
diff --git a/docs/reference/meson.build b/docs/reference/meson.build
index c11db34a..19732d5b 100644
--- a/docs/reference/meson.build
+++ b/docs/reference/meson.build
@@ -43,6 +43,7 @@ ignore_headers = [
   'soup-client-message-io-http1.h',
   'soup-client-message-io-http2.h',
   'soup-body-input-stream-http2.h',
+  'soup-tls-interaction.h',
 ]
 
 mkdb_args = [
diff --git a/libsoup/auth/soup-tls-interaction.c b/libsoup/auth/soup-tls-interaction.c
new file mode 100644
index 00000000..0ade927e
--- /dev/null
+++ b/libsoup/auth/soup-tls-interaction.c
@@ -0,0 +1,96 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * soup-tls-interaction.c: TLS interaction implementation
+ *
+ * Copyright (C) 2021 Igalia S.L.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-tls-interaction.h"
+
+struct _SoupTlsInteraction {
+        GTlsInteraction parent;
+};
+
+typedef struct {
+        SoupConnection *conn;
+} SoupTlsInteractionPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupTlsInteraction, soup_tls_interaction, G_TYPE_TLS_INTERACTION)
+
+static void
+soup_tls_interaction_request_certificate_async (GTlsInteraction             *tls_interaction,
+                                                GTlsConnection              *connection,
+                                                GTlsCertificateRequestFlags  flags,
+                                                GCancellable                *cancellable,
+                                                GAsyncReadyCallback          callback,
+                                                gpointer                     user_data)
+{
+        SoupTlsInteractionPrivate *priv = soup_tls_interaction_get_instance_private (SOUP_TLS_INTERACTION 
(tls_interaction));
+        GTask *task;
+
+        task = g_task_new (tls_interaction, cancellable, callback, user_data);
+        if (priv->conn)
+                soup_connection_request_tls_certificate (priv->conn, connection, task);
+        else
+                g_task_return_int (task, G_TLS_INTERACTION_FAILED);
+        g_object_unref (task);
+}
+
+static GTlsInteractionResult
+soup_tls_interaction_request_certificate_finish (GTlsInteraction *tls_interaction,
+                                                 GAsyncResult    *result,
+                                                 GError         **error)
+{
+        int task_result;
+
+        task_result = g_task_propagate_int (G_TASK (result), error);
+        return task_result != -1 ? task_result : G_TLS_INTERACTION_FAILED;
+}
+
+static void
+soup_tls_interaction_finalize (GObject *object)
+{
+        SoupTlsInteractionPrivate *priv = soup_tls_interaction_get_instance_private (SOUP_TLS_INTERACTION 
(object));
+
+        if (priv->conn) {
+                g_object_remove_weak_pointer (G_OBJECT (priv->conn), (gpointer*)&priv->conn);
+                priv->conn = NULL;
+        }
+
+        G_OBJECT_CLASS (soup_tls_interaction_parent_class)->finalize (object);
+}
+
+static void
+soup_tls_interaction_init (SoupTlsInteraction *interaction)
+{
+}
+
+static void
+soup_tls_interaction_class_init (SoupTlsInteractionClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass);
+
+        object_class->finalize = soup_tls_interaction_finalize;
+
+        interaction_class->request_certificate_async = soup_tls_interaction_request_certificate_async;
+        interaction_class->request_certificate_finish = soup_tls_interaction_request_certificate_finish;
+}
+
+GTlsInteraction *
+soup_tls_interaction_new (SoupConnection *conn)
+{
+        GTlsInteraction *interaction;
+        SoupTlsInteractionPrivate *priv;
+
+        interaction = g_object_new (SOUP_TYPE_TLS_INTERACTION, NULL);
+        priv = soup_tls_interaction_get_instance_private (SOUP_TLS_INTERACTION (interaction));
+        priv->conn = conn;
+        g_object_add_weak_pointer (G_OBJECT (priv->conn), (gpointer*)&priv->conn);
+
+        return interaction;
+}
diff --git a/libsoup/auth/soup-tls-interaction.h b/libsoup/auth/soup-tls-interaction.h
new file mode 100644
index 00000000..f6ba25ee
--- /dev/null
+++ b/libsoup/auth/soup-tls-interaction.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2021 Igalia S.L.
+ */
+
+#pragma once
+
+#include "soup-connection.h"
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_TLS_INTERACTION (soup_tls_interaction_get_type ())
+G_DECLARE_FINAL_TYPE (SoupTlsInteraction, soup_tls_interaction, SOUP, TLS_INTERACTION, GTlsInteraction)
+
+GType            soup_tls_interaction_get_type (void);
+GTlsInteraction *soup_tls_interaction_new      (SoupConnection *conn);
+
+G_END_DECLS
diff --git a/libsoup/meson.build b/libsoup/meson.build
index a9faf833..6142ef7b 100644
--- a/libsoup/meson.build
+++ b/libsoup/meson.build
@@ -11,6 +11,7 @@ soup_sources = [
   'auth/soup-auth-negotiate.c',
   'auth/soup-auth-manager.c',
   'auth/soup-connection-auth.c',
+  'auth/soup-tls-interaction.c',
 
   'cache/soup-cache.c',
   'cache/soup-cache-client-input-stream.c',
diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c
index 6ead5709..0da2696f 100644
--- a/libsoup/soup-connection.c
+++ b/libsoup/soup-connection.c
@@ -17,6 +17,7 @@
 #include "soup-client-message-io-http2.h"
 #include "soup-socket-properties.h"
 #include "soup-private-enum-types.h"
+#include "soup-tls-interaction.h"
 #include <gio/gnetworking.h>
 
 struct _SoupConnection {
@@ -43,6 +44,8 @@ typedef struct {
         guint        in_use;
         SoupHTTPVersion http_version;
 
+        GTlsCertificate *tls_client_cert;
+
        GCancellable *cancellable;
 } SoupConnectionPrivate;
 
@@ -51,6 +54,7 @@ G_DEFINE_TYPE_WITH_PRIVATE (SoupConnection, soup_connection, G_TYPE_OBJECT)
 enum {
        EVENT,
        ACCEPT_CERTIFICATE,
+        REQUEST_CERTIFICATE,
        DISCONNECTED,
        LAST_SIGNAL
 };
@@ -114,6 +118,7 @@ soup_connection_finalize (GObject *object)
        }
 
        g_clear_object (&priv->iostream);
+        g_clear_object (&priv->tls_client_cert);
 
        G_OBJECT_CLASS (soup_connection_parent_class)->finalize (object);
 }
@@ -229,6 +234,16 @@ soup_connection_class_init (SoupConnectionClass *connection_class)
                               G_TYPE_BOOLEAN, 2,
                               G_TYPE_TLS_CERTIFICATE,
                               G_TYPE_TLS_CERTIFICATE_FLAGS);
+        signals[REQUEST_CERTIFICATE] =
+                g_signal_new ("request-certificate",
+                              G_OBJECT_CLASS_TYPE (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              0,
+                              g_signal_accumulator_true_handled, NULL,
+                              NULL,
+                              G_TYPE_BOOLEAN, 2,
+                              G_TYPE_TLS_CLIENT_CONNECTION,
+                              G_TYPE_TASK);
        signals[DISCONNECTED] =
                g_signal_new ("disconnected",
                              G_OBJECT_CLASS_TYPE (object_class),
@@ -503,6 +518,7 @@ new_tls_connection (SoupConnection    *conn,
 {
         SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
         GTlsClientConnection *tls_connection;
+        GTlsInteraction *tls_interaction;
         GPtrArray *advertised_protocols = g_ptr_array_sized_new (4);
 
         // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
@@ -513,12 +529,13 @@ new_tls_connection (SoupConnection    *conn,
         g_ptr_array_add (advertised_protocols, "http/1.0");
         g_ptr_array_add (advertised_protocols, NULL);
 
+        tls_interaction = priv->socket_props->tls_interaction ? g_object_ref 
(priv->socket_props->tls_interaction) : soup_tls_interaction_new (conn);
         tls_connection = g_initable_new (g_tls_backend_get_client_connection_type (g_tls_backend_get_default 
()),
                                          priv->cancellable, error,
                                          "base-io-stream", connection,
                                          "server-identity", priv->remote_connectable,
                                          "require-close-notify", FALSE,
-                                         "interaction", priv->socket_props->tls_interaction,
+                                         "interaction", tls_interaction,
                                          "advertised-protocols", advertised_protocols->pdata,
                                          NULL);
 
@@ -1120,6 +1137,69 @@ soup_connection_get_tls_certificate_errors (SoupConnection *conn)
        return g_tls_connection_get_peer_certificate_errors (G_TLS_CONNECTION (priv->connection));
 }
 
+void
+soup_connection_set_tls_client_certificate (SoupConnection  *conn,
+                                            GTlsCertificate *certificate)
+{
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
+
+        if (G_IS_TLS_CONNECTION (priv->connection)) {
+                g_tls_connection_set_certificate (G_TLS_CONNECTION (priv->connection),
+                                                  certificate);
+                g_clear_object (&priv->tls_client_cert);
+                return;
+        }
+
+        if (priv->tls_client_cert == certificate)
+                return;
+
+        g_clear_object (&priv->tls_client_cert);
+        priv->tls_client_cert = g_object_ref (certificate);
+}
+
+void
+soup_connection_request_tls_certificate (SoupConnection *conn,
+                                         GTlsConnection *connection,
+                                         GTask          *task)
+{
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
+        gboolean handled = FALSE;
+
+        if (!G_IS_TLS_CONNECTION (priv->connection) || G_TLS_CONNECTION (priv->connection) != connection) {
+                g_task_return_int (task, G_TLS_INTERACTION_FAILED);
+                return;
+        }
+
+        if (priv->tls_client_cert) {
+                soup_connection_complete_tls_certificate_request (conn,
+                                                                  priv->tls_client_cert,
+                                                                  g_object_ref (task));
+                g_clear_object (&priv->tls_client_cert);
+                return;
+        }
+
+        g_signal_emit (conn, signals[REQUEST_CERTIFICATE], 0, connection, task, &handled);
+        if (!handled)
+                g_task_return_int (task, G_TLS_INTERACTION_FAILED);
+}
+
+void
+soup_connection_complete_tls_certificate_request (SoupConnection  *conn,
+                                                  GTlsCertificate *certificate,
+                                                  GTask           *task)
+{
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
+
+        if (G_IS_TLS_CONNECTION (priv->connection) && certificate) {
+                g_tls_connection_set_certificate (G_TLS_CONNECTION (priv->connection),
+                                                  certificate);
+                g_task_return_int (task, G_TLS_INTERACTION_HANDLED);
+        } else {
+                g_task_return_int (task, G_TLS_INTERACTION_FAILED);
+        }
+        g_object_unref (task);
+}
+
 guint64
 soup_connection_get_id (SoupConnection *conn)
 {
diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h
index 399866a8..701b2d94 100644
--- a/libsoup/soup-connection.h
+++ b/libsoup/soup-connection.h
@@ -65,6 +65,14 @@ SoupClientMessageIO *soup_connection_setup_message_io    (SoupConnection *conn,
 
 GTlsCertificate     *soup_connection_get_tls_certificate        (SoupConnection *conn);
 GTlsCertificateFlags soup_connection_get_tls_certificate_errors (SoupConnection *conn);
+void                 soup_connection_request_tls_certificate    (SoupConnection *conn,
+                                                                 GTlsConnection *connection,
+                                                                 GTask           *task);
+void                 soup_connection_complete_tls_certificate_request (SoupConnection  *conn,
+                                                                       GTlsCertificate *certificate,
+                                                                       GTask           *task);
+void                 soup_connection_set_tls_client_certificate (SoupConnection  *conn,
+                                                                 GTlsCertificate *certificate);
 
 guint64              soup_connection_get_id                     (SoupConnection *conn);
 GSocketAddress      *soup_connection_get_remote_address         (SoupConnection *conn);
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index be6f26b9..1d627cb5 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -89,6 +89,9 @@ typedef struct {
        GTlsCertificate      *tls_peer_certificate;
        GTlsCertificateFlags  tls_peer_certificate_errors;
 
+        GTlsCertificate *tls_client_certificate;
+        GTask *pending_tls_cert_request;
+
        SoupMessagePriority priority;
 
        gboolean is_top_level_navigation;
@@ -119,6 +122,7 @@ enum {
        AUTHENTICATE,
        NETWORK_EVENT,
        ACCEPT_CERTIFICATE,
+        REQUEST_CERTIFICATE,
        HSTS_ENFORCED,
 
        LAST_SIGNAL
@@ -169,6 +173,11 @@ soup_message_finalize (GObject *object)
        SoupMessage *msg = SOUP_MESSAGE (object);
        SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
 
+        if (priv->pending_tls_cert_request) {
+                g_task_return_int (priv->pending_tls_cert_request, G_TLS_INTERACTION_FAILED);
+                g_object_unref (priv->pending_tls_cert_request);
+        }
+
        soup_message_set_connection (msg, NULL);
 
        g_clear_pointer (&priv->uri, g_uri_unref);
@@ -183,6 +192,7 @@ soup_message_finalize (GObject *object)
 
        g_clear_object (&priv->tls_peer_certificate);
         g_clear_object (&priv->remote_address);
+        g_clear_object (&priv->tls_client_certificate);
 
        soup_message_headers_unref (priv->request_headers);
        soup_message_headers_unref (priv->response_headers);
@@ -594,6 +604,35 @@ soup_message_class_init (SoupMessageClass *message_class)
                              G_TYPE_TLS_CERTIFICATE,
                              G_TYPE_TLS_CERTIFICATE_FLAGS);
 
+        /**
+         * SoupMessage::request-certificate:
+         * @msg: the message
+         * @tls_connection: the #GTlsClientConnection
+         *
+         * Emitted during the @msg's connection TLS handshake when
+         * @tls_connection requests a certificate from the client.
+         * You can set the client certificate by calling
+         * soup_message_set_tls_client_certificate() and returning %TRUE.
+         * It's possible to handle the request asynchornously by returning
+         * %TRUE and call soup_message_set_tls_client_certificate() later
+         * once the certificate is available.
+         * Note that this signal is not emitted if #SoupSession::tls-interaction
+         * was set, or if soup_message_set_tls_client_certificate() was called
+         * before the connection TLS handshake started.
+         *
+         * Returns: %TRUE to handle the request, or %FALSE to make the connection
+         *     fail with %G_TLS_ERROR_CERTIFICATE_REQUIRED.
+         */
+        signals[REQUEST_CERTIFICATE] =
+                g_signal_new ("request-certificate",
+                              G_OBJECT_CLASS_TYPE (object_class),
+                              G_SIGNAL_RUN_LAST,
+                              0,
+                              g_signal_accumulator_true_handled, NULL,
+                              NULL,
+                              G_TYPE_BOOLEAN, 1,
+                              G_TYPE_TLS_CLIENT_CONNECTION);
+
        /**
         * SoupMessage::hsts-enforced:
         * @msg: the message
@@ -1391,6 +1430,23 @@ re_emit_accept_certificate (SoupMessage          *msg,
        return accept;
 }
 
+static gboolean
+re_emit_request_certificate (SoupMessage          *msg,
+                             GTlsClientConnection *tls_conn,
+                             GTask                *task)
+{
+        SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
+        gboolean handled = FALSE;
+
+        priv->pending_tls_cert_request = g_object_ref (task);
+
+        g_signal_emit (msg, signals[REQUEST_CERTIFICATE], 0, tls_conn, &handled);
+        if (!handled)
+                g_clear_object (&priv->pending_tls_cert_request);
+
+        return handled;
+}
+
 static void
 re_emit_tls_certificate_changed (SoupMessage    *msg,
                                 GParamSpec     *pspec,
@@ -1421,6 +1477,13 @@ soup_message_set_connection (SoupMessage    *msg,
        if (priv->connection) {
                g_signal_handlers_disconnect_by_data (priv->connection, msg);
                 priv->io_data = NULL;
+
+                if (priv->pending_tls_cert_request) {
+                        soup_connection_complete_tls_certificate_request (priv->connection,
+                                                                          priv->tls_client_certificate,
+                                                                          g_steal_pointer 
(&priv->pending_tls_cert_request));
+                        g_clear_object (&priv->tls_client_certificate);
+                }
                g_object_remove_weak_pointer (G_OBJECT (priv->connection), (gpointer*)&priv->connection);
                 soup_connection_set_in_use (priv->connection, FALSE);
        }
@@ -1438,12 +1501,21 @@ soup_message_set_connection (SoupMessage    *msg,
                                                soup_connection_get_tls_certificate_errors 
(priv->connection));
         soup_message_set_remote_address (msg, soup_connection_get_remote_address (priv->connection));
 
+        if (priv->tls_client_certificate) {
+                soup_connection_set_tls_client_certificate (priv->connection,
+                                                            priv->tls_client_certificate);
+                g_clear_object (&priv->tls_client_certificate);
+        }
+
        g_signal_connect_object (priv->connection, "event",
                                 G_CALLBACK (re_emit_connection_event),
                                 msg, G_CONNECT_SWAPPED);
        g_signal_connect_object (priv->connection, "accept-certificate",
                                 G_CALLBACK (re_emit_accept_certificate),
                                 msg, G_CONNECT_SWAPPED);
+        g_signal_connect_object (priv->connection, "request-certificate",
+                                 G_CALLBACK (re_emit_request_certificate),
+                                 msg, G_CONNECT_SWAPPED);
        g_signal_connect_object (priv->connection, "notify::tls-certificate",
                                 G_CALLBACK (re_emit_tls_certificate_changed),
                                 msg, G_CONNECT_SWAPPED);
@@ -2100,6 +2172,49 @@ soup_message_get_tls_peer_certificate_errors (SoupMessage *msg)
        return priv->tls_peer_certificate_errors;
 }
 
+/**
+ * soup_message_set_tls_client_certificate:
+ * @msg: a #SoupMessage
+ * @certificate: the #GTlsCertificate to set
+ *
+ * Sets the @certificate to be used by @msg's connection when a
+ * client certificate is requested during the TLS handshake.
+ * You can call this as a response to #SoupMessage::request-certificate
+ * signal, or before the connection is started.
+ * Note that the #GTlsCertificate set by this function will be ignored if
+ * #SoupSession::tls-interaction is not %NULL.
+ */
+void
+soup_message_set_tls_client_certificate (SoupMessage     *msg,
+                                         GTlsCertificate *certificate)
+{
+        SoupMessagePrivate *priv;
+
+        g_return_if_fail (SOUP_IS_MESSAGE (msg));
+        g_return_if_fail (G_IS_TLS_CERTIFICATE (certificate));
+
+        priv = soup_message_get_instance_private (msg);
+        if (priv->pending_tls_cert_request) {
+                g_assert (SOUP_IS_CONNECTION (priv->connection));
+                soup_connection_complete_tls_certificate_request (priv->connection,
+                                                                  certificate,
+                                                                  g_steal_pointer 
(&priv->pending_tls_cert_request));
+                return;
+        }
+
+        if (priv->connection) {
+                soup_connection_set_tls_client_certificate (priv->connection,
+                                                            certificate);
+                return;
+        }
+
+        if (priv->tls_client_certificate == certificate)
+                return;
+
+        g_clear_object (&priv->tls_client_certificate);
+        priv->tls_client_certificate = g_object_ref (certificate);
+}
+
 /**
  * SoupMessagePriority:
  * @SOUP_MESSAGE_PRIORITY_VERY_LOW: The lowest priority, the messages
diff --git a/libsoup/soup-message.h b/libsoup/soup-message.h
index 93fd9af0..5cd75551 100644
--- a/libsoup/soup-message.h
+++ b/libsoup/soup-message.h
@@ -106,6 +106,10 @@ GTlsCertificate     *soup_message_get_tls_peer_certificate        (SoupMessage *
 SOUP_AVAILABLE_IN_ALL
 GTlsCertificateFlags soup_message_get_tls_peer_certificate_errors (SoupMessage *msg);
 
+SOUP_AVAILABLE_IN_ALL
+void                 soup_message_set_tls_client_certificate (SoupMessage     *msg,
+                                                              GTlsCertificate *certificate);
+
 
 /* Specialized signal handlers */
 SOUP_AVAILABLE_IN_ALL
diff --git a/tests/ssl-test.c b/tests/ssl-test.c
index 650c7b87..d0c222aa 100644
--- a/tests/ssl-test.c
+++ b/tests/ssl-test.c
@@ -204,6 +204,138 @@ do_tls_interaction_test (gconstpointer data)
        g_object_unref (certificate);
 }
 
+static gboolean
+request_certificate_cb (SoupMessage          *msg,
+                        GTlsClientConnection *conn,
+                        GTlsCertificate      *certificate)
+{
+        soup_message_set_tls_client_certificate (msg, certificate);
+
+        return TRUE;
+}
+
+typedef struct {
+        SoupMessage *msg;
+        GTlsCertificate *certificate;
+} SetCertificateAsyncData;
+
+static gboolean
+set_certificate_idle_cb (SetCertificateAsyncData *data)
+{
+        soup_message_set_tls_client_certificate (data->msg, data->certificate);
+
+        return FALSE;
+}
+
+static gboolean
+request_certificate_async_cb (SoupMessage          *msg,
+                              GTlsClientConnection *conn,
+                              GTlsCertificate      *certificate)
+{
+        SetCertificateAsyncData *data;
+
+        data = g_new (SetCertificateAsyncData, 1);
+        data->msg = msg;
+        data->certificate = certificate;
+        g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+                         (GSourceFunc)set_certificate_idle_cb,
+                         data, g_free);
+
+        return TRUE;
+}
+
+static void
+do_tls_interaction_msg_test (gconstpointer data)
+{
+        SoupServer *server = (SoupServer *)data;
+        SoupSession *session;
+        SoupMessage *msg;
+        GBytes *body;
+        GTlsDatabase *tls_db;
+        GTlsCertificate *certificate;
+        GError *error = NULL;
+
+        SOUP_TEST_SKIP_IF_NO_TLS;
+
+        session = soup_test_session_new (NULL);
+        tls_db = soup_session_get_tls_database (session);
+
+        g_signal_connect (server, "request-started",
+                          G_CALLBACK (server_request_started),
+                          tls_db);
+
+        /* Not handling request-certificate signal */
+        msg = soup_message_new_from_uri ("GET", uri);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED))
+                g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        /* Handling the request-certificate signal synchronously */
+        g_object_get (server, "tls-certificate", &certificate, NULL);
+        g_assert_nonnull (certificate);
+        msg = soup_message_new_from_uri ("GET", uri);
+        g_signal_connect (msg, "request-certificate",
+                          G_CALLBACK (request_certificate_cb),
+                          certificate);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        g_assert_no_error (error);
+        soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        /* Next load doesn't emit request-certificate because the connection is reused */
+        msg = soup_message_new_from_uri ("GET", uri);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        g_assert_no_error (error);
+        soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        /* It fails for a new connection */
+        msg = soup_message_new_from_uri ("GET", uri);
+        soup_message_add_flags (msg, SOUP_MESSAGE_NEW_CONNECTION);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED))
+                g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        /* request-certificate is not emitted if the certificate is set before the load */
+        msg = soup_message_new_from_uri ("GET", uri);
+        soup_message_add_flags (msg, SOUP_MESSAGE_NEW_CONNECTION);
+        soup_message_set_tls_client_certificate (msg, certificate);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        g_assert_no_error (error);
+        soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        /* Handling the request-certificate signal asynchronously */
+        msg = soup_message_new_from_uri ("GET", uri);
+        soup_message_add_flags (msg, SOUP_MESSAGE_NEW_CONNECTION);
+        g_signal_connect (msg, "request-certificate",
+                          G_CALLBACK (request_certificate_async_cb),
+                          certificate);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        g_assert_no_error (error);
+        soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        g_signal_handlers_disconnect_by_data (server, tls_db);
+
+        soup_test_session_abort_unref (session);
+        g_object_unref (certificate);
+}
+
 static void
 server_handler (SoupServer        *server,
                SoupServerMessage *msg,
@@ -233,6 +365,7 @@ main (int argc, char **argv)
                uri = NULL;
 
        g_test_add_data_func ("/ssl/tls-interaction", server, do_tls_interaction_test);
+        g_test_add_data_func ("/ssl/tls-interaction-msg", server, do_tls_interaction_msg_test);
 
        for (i = 0; i < G_N_ELEMENTS (strictness_tests); i++) {
                g_test_add_data_func (strictness_tests[i].name,


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