diff options
author | Carlos Garcia Campos <cgarcia@igalia.com> | 2021-04-19 15:07:55 +0200 |
---|---|---|
committer | Carlos Garcia Campos <carlosgc@gnome.org> | 2021-06-02 07:22:46 +0000 |
commit | d72d9eabd74880be037818deb4c6ae73fdda697b (patch) | |
tree | 50e800cac7e20f0d0b25b519670d57d7dee9a8ee | |
parent | e13c09112c0a5506c9625f35ca0d3739f92b8516 (diff) | |
download | libsoup-d72d9eabd74880be037818deb4c6ae73fdda697b.tar.gz |
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.
-rw-r--r-- | docs/reference/libsoup-3.0-sections.txt | 1 | ||||
-rw-r--r-- | docs/reference/meson.build | 1 | ||||
-rw-r--r-- | libsoup/auth/soup-tls-interaction.c | 96 | ||||
-rw-r--r-- | libsoup/auth/soup-tls-interaction.h | 19 | ||||
-rw-r--r-- | libsoup/meson.build | 1 | ||||
-rw-r--r-- | libsoup/soup-connection.c | 82 | ||||
-rw-r--r-- | libsoup/soup-connection.h | 8 | ||||
-rw-r--r-- | libsoup/soup-message.c | 115 | ||||
-rw-r--r-- | libsoup/soup-message.h | 4 | ||||
-rw-r--r-- | tests/ssl-test.c | 133 |
10 files changed, 459 insertions, 1 deletions
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); @@ -2101,6 +2173,49 @@ soup_message_get_tls_peer_certificate_errors (SoupMessage *msg) } /** + * 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 * with this priority will be the last ones to be attended. 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, |