diff options
| author | Dan Winship <danw@gnome.org> | 2014-05-02 16:01:24 -0400 |
|---|---|---|
| committer | Dan Winship <danw@gnome.org> | 2014-05-03 13:30:52 -0400 |
| commit | 0ee90b166ba4a9ca3c8c66a531d97c661536087d (patch) | |
| tree | 33cfad46af6a6e677bb4ababc3d087483acc4a19 | |
| parent | ba5f2e307b2ee45f4cc166f6b488c7bc8559bfc7 (diff) | |
| download | libsoup-0ee90b166ba4a9ca3c8c66a531d97c661536087d.tar.gz | |
Add tls-interaction property to SoupSession
This can be used by applications to do client-side certificates via
the new g_tls_interaction_request_certificate(). Will be used by
OSTree at least.
Also add a test to tls-test.
Based on a patch from Colin Walters.
https://bugzilla.gnome.org/show_bug.cgi?id=334021
| -rw-r--r-- | libsoup/soup-session.c | 35 | ||||
| -rw-r--r-- | libsoup/soup-session.h | 1 | ||||
| -rw-r--r-- | libsoup/soup-socket-private.h | 18 | ||||
| -rw-r--r-- | libsoup/soup-socket-properties.c | 19 | ||||
| -rw-r--r-- | libsoup/soup-socket.c | 14 | ||||
| -rw-r--r-- | tests/ssl-test.c | 167 |
6 files changed, 238 insertions, 16 deletions
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c index cbefc647..04d311e2 100644 --- a/libsoup/soup-session.c +++ b/libsoup/soup-session.c @@ -87,6 +87,7 @@ typedef struct { gboolean disposed; GTlsDatabase *tlsdb; + GTlsInteraction *tls_interaction; char *ssl_ca_file; gboolean ssl_strict; gboolean tlsdb_use_default; @@ -200,6 +201,7 @@ enum { PROP_HTTP_ALIASES, PROP_HTTPS_ALIASES, PROP_LOCAL_ADDRESS, + PROP_TLS_INTERACTION, LAST_PROP }; @@ -337,6 +339,7 @@ soup_session_finalize (GObject *object) g_free (priv->accept_language); g_clear_object (&priv->tlsdb); + g_clear_object (&priv->tls_interaction); g_free (priv->ssl_ca_file); g_clear_pointer (&priv->async_context, g_main_context_unref); @@ -384,6 +387,7 @@ ensure_socket_props (SoupSession *session) priv->proxy_resolver, priv->local_addr, priv->tlsdb, + priv->tls_interaction, ssl_strict, priv->io_timeout, priv->idle_timeout); @@ -683,6 +687,10 @@ soup_session_set_property (GObject *object, guint prop_id, set_tlsdb (session, g_value_get_object (value)); socket_props_changed = TRUE; break; + case PROP_TLS_INTERACTION: + priv->tls_interaction = g_value_dup_object (value); + socket_props_changed = TRUE; + break; case PROP_SSL_STRICT: priv->ssl_strict = g_value_get_boolean (value); socket_props_changed = TRUE; @@ -821,6 +829,9 @@ soup_session_get_property (GObject *object, guint prop_id, ensure_socket_props (session); g_value_set_object (value, priv->tlsdb); break; + case PROP_TLS_INTERACTION: + g_value_set_object (value, priv->tls_interaction); + break; case PROP_SSL_STRICT: g_value_set_boolean (value, priv->ssl_strict); break; @@ -3748,6 +3759,30 @@ soup_session_class_init (SoupSessionClass *session_class) "Address of local end of socket", SOUP_TYPE_ADDRESS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * SOUP_SESSION_TLS_INTERACTION: + * + * Alias for the #SoupSession:tls-interaction property, qv. + * + * Since: 2.48 + **/ + /** + * SoupSession:tls-interaction: + * + * A #GTlsInteraction object that will be passed on to any + * #GTlsConnections created by the session. (This can be used to + * provide client-side certificates, for example.) + * + * Since: 2.48 + **/ + g_object_class_install_property ( + object_class, PROP_TLS_INTERACTION, + g_param_spec_object (SOUP_SESSION_TLS_INTERACTION, + "TLS Interaction", + "TLS interaction to use", + G_TYPE_TLS_INTERACTION, + G_PARAM_READWRITE)); } diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h index 67a59eaa..eed392df 100644 --- a/libsoup/soup-session.h +++ b/libsoup/soup-session.h @@ -70,6 +70,7 @@ GType soup_session_get_type (void); #define SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE "ssl-use-system-ca-file" #define SOUP_SESSION_TLS_DATABASE "tls-database" #define SOUP_SESSION_SSL_STRICT "ssl-strict" +#define SOUP_SESSION_TLS_INTERACTION "tls-interaction" #define SOUP_SESSION_ASYNC_CONTEXT "async-context" #define SOUP_SESSION_USE_THREAD_CONTEXT "use-thread-context" #define SOUP_SESSION_TIMEOUT "timeout" diff --git a/libsoup/soup-socket-private.h b/libsoup/soup-socket-private.h index 55a37739..68a392fc 100644 --- a/libsoup/soup-socket-private.h +++ b/libsoup/soup-socket-private.h @@ -53,6 +53,7 @@ typedef struct { SoupAddress *local_addr; GTlsDatabase *tlsdb; + GTlsInteraction *tls_interaction; gboolean ssl_strict; guint io_timeout; @@ -65,14 +66,15 @@ typedef struct { GType soup_socket_properties_get_type (void); #define SOUP_TYPE_SOCKET_PROPERTIES (soup_socket_properties_get_type ()) -SoupSocketProperties *soup_socket_properties_new (GMainContext *async_context, - gboolean use_thread_context, - GProxyResolver *proxy_resolver, - SoupAddress *local_addr, - GTlsDatabase *tlsdb, - gboolean ssl_strict, - guint io_timeout, - guint idle_timeout); +SoupSocketProperties *soup_socket_properties_new (GMainContext *async_context, + gboolean use_thread_context, + GProxyResolver *proxy_resolver, + SoupAddress *local_addr, + GTlsDatabase *tlsdb, + GTlsInteraction *tls_interaction, + gboolean ssl_strict, + guint io_timeout, + guint idle_timeout); SoupSocketProperties *soup_socket_properties_ref (SoupSocketProperties *props); void soup_socket_properties_unref (SoupSocketProperties *props); diff --git a/libsoup/soup-socket-properties.c b/libsoup/soup-socket-properties.c index a7ed5128..0482c44f 100644 --- a/libsoup/soup-socket-properties.c +++ b/libsoup/soup-socket-properties.c @@ -7,14 +7,15 @@ #include "soup.h" SoupSocketProperties * -soup_socket_properties_new (GMainContext *async_context, - gboolean use_thread_context, - GProxyResolver *proxy_resolver, - SoupAddress *local_addr, - GTlsDatabase *tlsdb, - gboolean ssl_strict, - guint io_timeout, - guint idle_timeout) +soup_socket_properties_new (GMainContext *async_context, + gboolean use_thread_context, + GProxyResolver *proxy_resolver, + SoupAddress *local_addr, + GTlsDatabase *tlsdb, + GTlsInteraction *tls_interaction, + gboolean ssl_strict, + guint io_timeout, + guint idle_timeout) { SoupSocketProperties *props; @@ -28,6 +29,7 @@ soup_socket_properties_new (GMainContext *async_context, props->local_addr = local_addr ? g_object_ref (local_addr) : NULL; props->tlsdb = tlsdb ? g_object_ref (tlsdb) : NULL; + props->tls_interaction = tls_interaction ? g_object_ref (tls_interaction) : NULL; props->ssl_strict = ssl_strict; props->io_timeout = io_timeout; @@ -53,6 +55,7 @@ soup_socket_properties_unref (SoupSocketProperties *props) g_clear_object (&props->proxy_resolver); g_clear_object (&props->local_addr); g_clear_object (&props->tlsdb); + g_clear_object (&props->tls_interaction); g_slice_free (SoupSocketProperties, props); } diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index 61329a91..157a4cf2 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -79,6 +79,7 @@ typedef struct { GInputStream *istream; GOutputStream *ostream; GTlsCertificateFlags tls_errors; + GTlsInteraction *tls_interaction; GProxyResolver *proxy_resolver; guint non_blocking:1; @@ -216,6 +217,7 @@ soup_socket_finalize (GObject *object) g_clear_object (&priv->local_addr); g_clear_object (&priv->remote_addr); + g_clear_object (&priv->tls_interaction); g_clear_object (&priv->proxy_resolver); g_clear_object (&priv->ssl_creds); @@ -314,6 +316,7 @@ soup_socket_set_property (GObject *object, guint prop_id, g_clear_object (&priv->proxy_resolver); if (props->proxy_resolver) priv->proxy_resolver = g_object_ref (props->proxy_resolver); + g_clear_object (&priv->local_addr); if (props->local_addr) priv->local_addr = g_object_ref (props->local_addr); @@ -321,6 +324,9 @@ soup_socket_set_property (GObject *object, guint prop_id, g_clear_object (&priv->ssl_creds); if (props->tlsdb) priv->ssl_creds = g_object_ref (props->tlsdb); + g_clear_object (&priv->tls_interaction); + if (props->tls_interaction) + priv->tls_interaction = g_object_ref (props->tls_interaction); priv->ssl_strict = props->ssl_strict; priv->timeout = props->io_timeout; @@ -1309,6 +1315,14 @@ soup_socket_setup_ssl (SoupSocket *sock, if (!conn) return FALSE; + /* GLib < 2.41 mistakenly doesn't implement this property in the + * dummy TLS backend, so we don't include it in the g_initable_new() + * call above. + */ + g_object_set (G_OBJECT (conn), + "interaction", priv->tls_interaction, + NULL); + g_object_unref (priv->conn); priv->conn = G_IO_STREAM (conn); diff --git a/tests/ssl-test.c b/tests/ssl-test.c index 989c48d3..8ff50a36 100644 --- a/tests/ssl-test.c +++ b/tests/ssl-test.c @@ -235,6 +235,172 @@ do_session_property_tests (void) soup_test_session_abort_unref (session); } +/* GTlsInteraction subclass for do_interaction_test */ +typedef GTlsInteraction TestTlsInteraction; +typedef GTlsInteractionClass TestTlsInteractionClass; + +GType test_tls_interaction_get_type (void); + +G_DEFINE_TYPE (TestTlsInteraction, test_tls_interaction, G_TYPE_TLS_INTERACTION); + +static void +test_tls_interaction_init (TestTlsInteraction *interaction) +{ + +} + +static GTlsInteractionResult +test_tls_interaction_request_certificate (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GError **error) +{ + GTlsCertificate *cert; + const char *ssl_cert_file, *ssl_key_file; + GError *my_error = NULL; + + /* Yes, we use the same certificate for the client as for the server. Shrug */ + ssl_cert_file = g_test_get_filename (G_TEST_DIST, "test-cert.pem", NULL); + ssl_key_file = g_test_get_filename (G_TEST_DIST, "test-key.pem", NULL); + cert = g_tls_certificate_new_from_files (ssl_cert_file, + ssl_key_file, + &my_error); + g_assert_no_error (my_error); + + g_tls_connection_set_certificate (connection, cert); + g_object_unref (cert); + + return G_TLS_INTERACTION_HANDLED; +} + +static void +test_tls_interaction_class_init (TestTlsInteractionClass *klass) +{ + GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass); + + interaction_class->request_certificate = test_tls_interaction_request_certificate; +} + + +#define INTERACTION_TEST_HTTP_RESPONSE "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nOK\r\n" + +static gboolean +accept_client_certificate (GTlsConnection *server, + GTlsCertificate *client_cert, + GTlsCertificateFlags errors) +{ + return TRUE; +} + +static void +got_connection (GThreadedSocketService *service, + GSocketConnection *connection, + GObject *source_object) +{ + GIOStream *tls; + GTlsCertificate *server_cert; + GError *error = NULL; + const char *ssl_cert_file, *ssl_key_file; + GMainContext *thread_context; + + thread_context = g_main_context_new (); + g_main_context_push_thread_default (thread_context); + + ssl_cert_file = g_test_get_filename (G_TEST_DIST, "test-cert.pem", NULL); + ssl_key_file = g_test_get_filename (G_TEST_DIST, "test-key.pem", NULL); + server_cert = g_tls_certificate_new_from_files (ssl_cert_file, + ssl_key_file, + &error); + g_assert_no_error (error); + + tls = g_tls_server_connection_new (G_IO_STREAM (connection), + server_cert, &error); + g_assert_no_error (error); + g_object_unref (server_cert); + + g_object_set (G_OBJECT (tls), + "authentication-mode", G_TLS_AUTHENTICATION_REQUIRED, + NULL); + g_signal_connect (tls, "accept-certificate", + G_CALLBACK (accept_client_certificate), NULL); + + if (g_tls_connection_handshake (G_TLS_CONNECTION (tls), NULL, &error)) { + g_output_stream_write_all (g_io_stream_get_output_stream (tls), + INTERACTION_TEST_HTTP_RESPONSE, + strlen (INTERACTION_TEST_HTTP_RESPONSE), + NULL, NULL, &error); + g_assert_no_error (error); + } else { + g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED); + g_clear_error (&error); + } + + g_io_stream_close (tls, NULL, &error); + g_assert_no_error (error); + + g_object_unref (tls); + + g_main_context_pop_thread_default (thread_context); + g_main_context_unref (thread_context); +} + +static void +do_tls_interaction_test (void) +{ + GSocketService *service; + GSocketAddress *address, *bound_address; + SoupSession *session; + SoupMessage *msg; + GTlsInteraction *interaction; + SoupURI *test_uri; + GError *error = NULL; + + SOUP_TEST_SKIP_IF_NO_TLS; + + service = g_threaded_socket_service_new (1); + address = g_inet_socket_address_new_from_string ("127.0.0.1", 0); + g_socket_listener_add_address (G_SOCKET_LISTENER (service), address, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, + NULL, &bound_address, &error); + g_assert_no_error (error); + g_object_unref (address); + g_signal_connect (service, "run", G_CALLBACK (got_connection), NULL); + g_socket_service_start (service); + + test_uri = soup_uri_new ("https://127.0.0.1"); + soup_uri_set_port (test_uri, g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (bound_address))); + g_object_unref (bound_address); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + + /* Without a GTlsInteraction */ + msg = soup_message_new_from_uri ("GET", test_uri); + soup_session_send_message (session, msg); + soup_test_assert_message_status (msg, SOUP_STATUS_SSL_FAILED); + g_object_unref (msg); + + interaction = g_object_new (test_tls_interaction_get_type (), NULL); + g_object_set (G_OBJECT (session), + SOUP_SESSION_TLS_INTERACTION, interaction, + NULL); + g_object_unref (interaction); + + /* With a GTlsInteraction */ + msg = soup_message_new_from_uri ("GET", test_uri); + soup_session_send_message (session, msg); + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + g_assert_true (soup_message_get_https_status (msg, NULL, NULL)); + g_object_unref (msg); + + soup_uri_free (test_uri); + soup_test_session_abort_unref (session); + + g_socket_service_stop (service); + g_object_unref (service); +} + static void server_handler (SoupServer *server, SoupMessage *msg, @@ -267,6 +433,7 @@ main (int argc, char **argv) g_test_add_func ("/ssl/session-properties", do_session_property_tests); g_test_add_func ("/ssl/message-properties/async", do_async_properties_tests); g_test_add_func ("/ssl/message-properties/sync", do_sync_properties_tests); + g_test_add_func ("/ssl/tls-interaction", do_tls_interaction_test); for (i = 0; i < G_N_ELEMENTS (strictness_tests); i++) { g_test_add_data_func (strictness_tests[i].name, |
