/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * GIO TLS tests * * Copyright 2011 Collabora, Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see * . * * In addition, when the library is used with OpenSSL, a special * exception applies. Refer to the LICENSE_EXCEPTION file for details. * * Author: Stef Walter */ #include "config.h" #include "mock-interaction.h" #include #include #include #include static const gchar * tls_test_file_path (const char *name) { const gchar *const_path; gchar *path; path = g_test_build_filename (G_TEST_DIST, "files", name, NULL); if (!g_path_is_absolute (path)) { gchar *cwd, *abs; cwd = g_get_current_dir (); abs = g_build_filename (cwd, path, NULL); g_free (cwd); g_free (path); path = abs; } const_path = g_intern_string (path); g_free (path); return const_path; } #define TEST_DATA "You win again, gravity!\n" #define TEST_DATA_LENGTH 24 typedef struct { GMainContext *context; GMainLoop *loop; GSocketService *service; GTlsDatabase *database; GIOStream *server_connection; GIOStream *client_connection; GSocketConnectable *identity; GSocketAddress *address; GTlsAuthenticationMode auth_mode; gboolean rehandshake; GTlsCertificateFlags accept_flags; GError *read_error; gboolean expect_server_error; GError *server_error; gboolean server_should_close; gboolean server_running; GTlsCertificate *server_certificate; char buf[128]; gssize nread, nwrote; } TestConnection; static void setup_connection (TestConnection *test, gconstpointer data) { test->context = g_main_context_default (); test->loop = g_main_loop_new (test->context, FALSE); test->auth_mode = G_TLS_AUTHENTICATION_NONE; } /* Waits about 10 seconds for @var to be NULL/FALSE */ #define WAIT_UNTIL_UNSET(var) \ if (var) \ { \ int i; \ \ for (i = 0; i < 13 && (var); i++) \ { \ g_usleep (1000 * (1 << i)); \ g_main_context_iteration (NULL, FALSE); \ } \ \ g_assert (!(var)); \ } static void teardown_connection (TestConnection *test, gconstpointer data) { if (test->service) { g_socket_service_stop (test->service); /* The outstanding accept_async will hold a ref on test->service, * which we want to wait for it to release if we're valgrinding. */ g_object_add_weak_pointer (G_OBJECT (test->service), (gpointer *)&test->service); g_object_unref (test->service); WAIT_UNTIL_UNSET (test->service); } if (test->server_connection) { WAIT_UNTIL_UNSET (test->server_running); g_object_add_weak_pointer (G_OBJECT (test->server_connection), (gpointer *)&test->server_connection); g_object_unref (test->server_connection); WAIT_UNTIL_UNSET (test->server_connection); } if (test->client_connection) { g_object_add_weak_pointer (G_OBJECT (test->client_connection), (gpointer *)&test->client_connection); g_object_unref (test->client_connection); WAIT_UNTIL_UNSET (test->client_connection); } if (test->database) { g_object_add_weak_pointer (G_OBJECT (test->database), (gpointer *)&test->database); g_object_unref (test->database); WAIT_UNTIL_UNSET (test->database); } g_clear_object (&test->address); g_clear_object (&test->identity); g_clear_object (&test->server_certificate); g_main_loop_unref (test->loop); g_clear_error (&test->read_error); g_clear_error (&test->server_error); } static void start_server (TestConnection *test) { GInetAddress *inet; GSocketAddress *addr; GInetSocketAddress *iaddr; GError *error = NULL; inet = g_inet_address_new_from_string ("127.0.0.1"); addr = g_inet_socket_address_new (inet, 0); g_object_unref (inet); g_socket_listener_add_address (G_SOCKET_LISTENER (test->service), addr, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP, NULL, &test->address, &error); g_assert_no_error (error); g_object_unref (addr); /* The hostname in test->identity matches the server certificate. */ iaddr = G_INET_SOCKET_ADDRESS (test->address); test->identity = g_network_address_new ("server.example.com", g_inet_socket_address_get_port (iaddr)); test->server_running = TRUE; } static gboolean on_accept_certificate (GTlsClientConnection *conn, GTlsCertificate *cert, GTlsCertificateFlags errors, gpointer user_data) { TestConnection *test = user_data; return errors == test->accept_flags; } static void on_output_write_finish (GObject *object, GAsyncResult *res, gpointer user_data); static void on_rehandshake_finish (GObject *object, GAsyncResult *res, gpointer user_data) { TestConnection *test = user_data; GError *error = NULL; GOutputStream *stream; g_tls_connection_handshake_finish (G_TLS_CONNECTION (object), res, &error); g_assert_no_error (error); stream = g_io_stream_get_output_stream (test->server_connection); g_output_stream_write_async (stream, TEST_DATA + TEST_DATA_LENGTH / 2, TEST_DATA_LENGTH / 2, G_PRIORITY_DEFAULT, NULL, on_output_write_finish, test); } static void on_server_close_finish (GObject *object, GAsyncResult *res, gpointer user_data) { TestConnection *test = user_data; GError *error = NULL; g_io_stream_close_finish (G_IO_STREAM (object), res, &error); if (test->expect_server_error) g_assert (error != NULL); else g_assert_no_error (error); test->server_running = FALSE; } static void close_server_connection (TestConnection *test) { g_io_stream_close_async (test->server_connection, G_PRIORITY_DEFAULT, NULL, on_server_close_finish, test); } static void on_output_write_finish (GObject *object, GAsyncResult *res, gpointer user_data) { TestConnection *test = user_data; g_assert (test->server_error == NULL); g_output_stream_write_finish (G_OUTPUT_STREAM (object), res, &test->server_error); if (!test->server_error && test->rehandshake) { test->rehandshake = FALSE; g_tls_connection_handshake_async (G_TLS_CONNECTION (test->server_connection), G_PRIORITY_DEFAULT, NULL, on_rehandshake_finish, test); return; } if (test->server_should_close) close_server_connection (test); } static gboolean on_incoming_connection (GSocketService *service, GSocketConnection *connection, GObject *source_object, gpointer user_data) { TestConnection *test = user_data; GOutputStream *stream; GTlsCertificate *cert; GError *error = NULL; if (test->server_certificate) { cert = g_object_ref (test->server_certificate); } else { cert = g_tls_certificate_new_from_file (tls_test_file_path ("server-and-key.pem"), &error); g_assert_no_error (error); } test->server_connection = g_tls_server_connection_new (G_IO_STREAM (connection), cert, &error); g_assert_no_error (error); g_object_unref (cert); g_object_set (test->server_connection, "authentication-mode", test->auth_mode, NULL); g_signal_connect (test->server_connection, "accept-certificate", G_CALLBACK (on_accept_certificate), test); if (test->database) g_tls_connection_set_database (G_TLS_CONNECTION (test->server_connection), test->database); stream = g_io_stream_get_output_stream (test->server_connection); g_output_stream_write_async (stream, TEST_DATA, test->rehandshake ? TEST_DATA_LENGTH / 2 : TEST_DATA_LENGTH, G_PRIORITY_DEFAULT, NULL, on_output_write_finish, test); return FALSE; } static void start_async_server_service (TestConnection *test, GTlsAuthenticationMode auth_mode, gboolean should_close) { test->service = g_socket_service_new (); start_server (test); test->auth_mode = auth_mode; g_signal_connect (test->service, "incoming", G_CALLBACK (on_incoming_connection), test); test->server_should_close = should_close; } static GIOStream * start_async_server_and_connect_to_it (TestConnection *test, GTlsAuthenticationMode auth_mode, gboolean should_close) { GSocketClient *client; GError *error = NULL; GSocketConnection *connection; start_async_server_service (test, auth_mode, should_close); client = g_socket_client_new (); connection = g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address), NULL, &error); g_assert_no_error (error); g_object_unref (client); return G_IO_STREAM (connection); } static void run_echo_server (GThreadedSocketService *service, GSocketConnection *connection, GObject *source_object, gpointer user_data) { TestConnection *test = user_data; GTlsConnection *tlsconn; GTlsCertificate *cert; GError *error = NULL; GInputStream *istream; GOutputStream *ostream; gssize nread, nwrote, total; gchar buf[128]; if (test->server_certificate) { cert = g_object_ref (test->server_certificate); } else { cert = g_tls_certificate_new_from_file (tls_test_file_path ("server-and-key.pem"), &error); g_assert_no_error (error); } test->server_connection = g_tls_server_connection_new (G_IO_STREAM (connection), cert, &error); g_assert_no_error (error); g_object_unref (cert); tlsconn = G_TLS_CONNECTION (test->server_connection); g_tls_connection_handshake (tlsconn, NULL, &error); g_assert_no_error (error); istream = g_io_stream_get_input_stream (test->server_connection); ostream = g_io_stream_get_output_stream (test->server_connection); while (TRUE) { nread = g_input_stream_read (istream, buf, sizeof (buf), NULL, &error); g_assert_no_error (error); g_assert_cmpint (nread, >=, 0); if (nread == 0) break; for (total = 0; total < nread; total += nwrote) { nwrote = g_output_stream_write (ostream, buf + total, nread - total, NULL, &error); g_assert_no_error (error); } if (test->rehandshake) { test->rehandshake = FALSE; g_tls_connection_handshake (tlsconn, NULL, &error); g_assert_no_error (error); } } g_io_stream_close (test->server_connection, NULL, &error); g_assert_no_error (error); test->server_running = FALSE; } static void start_echo_server_service (TestConnection *test) { test->service = g_threaded_socket_service_new (5); start_server (test); g_signal_connect (test->service, "run", G_CALLBACK (run_echo_server), test); } static GIOStream * start_echo_server_and_connect_to_it (TestConnection *test) { GSocketClient *client; GError *error = NULL; GSocketConnection *connection; start_echo_server_service (test); client = g_socket_client_new (); connection = g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address), NULL, &error); g_assert_no_error (error); g_object_unref (client); return G_IO_STREAM (connection); } static void on_client_connection_close_finish (GObject *object, GAsyncResult *res, gpointer user_data) { TestConnection *test = user_data; GError *error = NULL; g_io_stream_close_finish (G_IO_STREAM (object), res, &error); g_assert_no_error (error); g_main_loop_quit (test->loop); } static void on_input_read_finish (GObject *object, GAsyncResult *res, gpointer user_data) { TestConnection *test = user_data; gchar *line, *check; line = g_data_input_stream_read_line_finish (G_DATA_INPUT_STREAM (object), res, NULL, &test->read_error); if (!test->read_error) { g_assert (line); check = g_strdup (TEST_DATA); g_strstrip (check); g_assert_cmpstr (line, ==, check); g_free (check); g_free (line); } g_io_stream_close_async (test->client_connection, G_PRIORITY_DEFAULT, NULL, on_client_connection_close_finish, test); } static void read_test_data_async (TestConnection *test) { GDataInputStream *stream; stream = g_data_input_stream_new (g_io_stream_get_input_stream (test->client_connection)); g_assert (stream); g_data_input_stream_read_line_async (stream, G_PRIORITY_DEFAULT, NULL, on_input_read_finish, test); g_object_unref (stream); } static void test_basic_connection (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_assert_no_error (error); g_object_unref (connection); /* No validation at all in this test */ g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), 0); 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); } static void test_verified_connection (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; 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, TRUE); 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); /* 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_no_error (test->read_error); g_assert_no_error (test->server_error); } static void test_verified_chain (TestConnection *test, gconstpointer data) { GTlsBackend *backend; GTlsCertificate *server_cert; GTlsCertificate *intermediate_cert; char *cert_data = NULL; char *key_data = NULL; GError *error = NULL; backend = g_tls_backend_get_default (); /* Prepare the intermediate cert. */ intermediate_cert = g_tls_certificate_new_from_file (tls_test_file_path ("intermediate-ca.pem"), &error); g_assert_no_error (error); g_assert (intermediate_cert); /* Prepare the server cert. */ g_clear_pointer (&cert_data, g_free); g_file_get_contents (tls_test_file_path ("server-intermediate.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); g_file_get_contents (tls_test_file_path ("server-intermediate-key.pem"), &key_data, NULL, &error); g_assert_no_error (error); g_assert (key_data); server_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", intermediate_cert, "certificate-pem", cert_data, "private-key-pem", key_data, NULL); g_assert_no_error (error); g_assert (server_cert); g_object_unref (intermediate_cert); g_free (cert_data); g_free (key_data); test->server_certificate = server_cert; test_verified_connection (test, data); } static void test_verified_chain_with_redundant_root_cert (TestConnection *test, gconstpointer data) { GTlsBackend *backend; GTlsCertificate *server_cert; GTlsCertificate *intermediate_cert; GTlsCertificate *root_cert; char *cert_data = NULL; char *key_data = NULL; GError *error = NULL; backend = g_tls_backend_get_default (); /* The root is redundant. It should not hurt anything. */ root_cert = g_tls_certificate_new_from_file (tls_test_file_path ("ca.pem"), &error); g_assert_no_error (error); g_assert (root_cert); /* Prepare the intermediate cert. */ g_file_get_contents (tls_test_file_path ("intermediate-ca.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); intermediate_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", root_cert, "certificate-pem", cert_data, NULL); g_assert_no_error (error); g_assert (intermediate_cert); /* Prepare the server cert. */ g_clear_pointer (&cert_data, g_free); g_file_get_contents (tls_test_file_path ("server-intermediate.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); g_file_get_contents (tls_test_file_path ("server-intermediate-key.pem"), &key_data, NULL, &error); g_assert_no_error (error); g_assert (key_data); server_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", intermediate_cert, "certificate-pem", cert_data, "private-key-pem", key_data, NULL); g_assert_no_error (error); g_assert (server_cert); g_object_unref (intermediate_cert); g_object_unref (root_cert); g_free (cert_data); g_free (key_data); test->server_certificate = server_cert; test_verified_connection (test, data); } static void test_verified_chain_with_duplicate_server_cert (TestConnection *test, gconstpointer data) { /* This is another common server misconfiguration. Apache reads certificates * from two configuration files: one for the server cert, and one for the rest * of the chain. If the server cert is pasted into both files, it will be sent * twice. We should be tolerant of this. */ GTlsBackend *backend; GTlsCertificate *server_cert; GTlsCertificate *extra_server_cert; GTlsCertificate *intermediate_cert; char *cert_data = NULL; char *key_data = NULL; GError *error = NULL; backend = g_tls_backend_get_default (); /* Prepare the intermediate cert. */ intermediate_cert = g_tls_certificate_new_from_file (tls_test_file_path ("intermediate-ca.pem"), &error); g_assert_no_error (error); g_assert (intermediate_cert); /* Prepare the server cert. */ g_clear_pointer (&cert_data, g_free); g_file_get_contents (tls_test_file_path ("server-intermediate.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); g_file_get_contents (tls_test_file_path ("server-intermediate-key.pem"), &key_data, NULL, &error); g_assert_no_error (error); g_assert (key_data); server_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", intermediate_cert, "certificate-pem", cert_data, NULL); g_assert_no_error (error); g_assert (server_cert); /* Prepare the server cert... again. Private key must go on this one. */ extra_server_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", server_cert, "certificate-pem", cert_data, "private-key-pem", key_data, NULL); g_assert_no_error (error); g_assert (extra_server_cert); g_object_unref (intermediate_cert); g_object_unref (server_cert); g_free (cert_data); g_free (key_data); test->server_certificate = extra_server_cert; test_verified_connection (test, data); } static void test_verified_unordered_chain (TestConnection *test, gconstpointer data) { GTlsBackend *backend; GTlsCertificate *server_cert; GTlsCertificate *intermediate_cert; GTlsCertificate *root_cert; char *cert_data = NULL; char *key_data = NULL; GError *error = NULL; backend = g_tls_backend_get_default (); /* Prepare the intermediate cert (to be sent last, out of order)! */ intermediate_cert = g_tls_certificate_new_from_file (tls_test_file_path ("intermediate-ca.pem"), &error); g_assert_no_error (error); g_assert (intermediate_cert); g_file_get_contents (tls_test_file_path ("ca.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); /* Prepare the root cert (to be sent in the middle of the chain). */ root_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", intermediate_cert, "certificate-pem", cert_data, NULL); g_assert_no_error (error); g_assert (root_cert); g_clear_pointer (&cert_data, g_free); g_file_get_contents (tls_test_file_path ("server-intermediate.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); g_file_get_contents (tls_test_file_path ("server-intermediate-key.pem"), &key_data, NULL, &error); g_assert_no_error (error); g_assert (key_data); /* Prepare the server cert. */ server_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", root_cert, "certificate-pem", cert_data, "private-key-pem", key_data, NULL); g_assert_no_error (error); g_assert (server_cert); g_object_unref (intermediate_cert); g_object_unref (root_cert); g_free (cert_data); g_free (key_data); test->server_certificate = server_cert; test_verified_connection (test, data); } static void test_verified_chain_with_alternative_ca_cert (TestConnection *test, gconstpointer data) { GTlsBackend *backend; GTlsCertificate *server_cert; GTlsCertificate *intermediate_cert; GTlsCertificate *root_cert; char *cert_data = NULL; char *key_data = NULL; GError *error = NULL; backend = g_tls_backend_get_default (); /* This "root" cert is issued by a CA that is not in the trust store. So it's * not really a root, but it has the same public key as a cert in the trust * store. If the client insists on a traditional chain of trust, this will * fail, since the issuer is untrusted. */ root_cert = g_tls_certificate_new_from_file (tls_test_file_path ("ca-alternative.pem"), &error); g_assert_no_error (error); g_assert (root_cert); /* Prepare the intermediate cert. Modern TLS libraries are expected to notice * that it is signed by the same public key as a certificate in the root * store, and accept the certificate, ignoring the untrusted "root" sent next * in the chain, which servers send for compatibility with clients that don't * have the new CA cert in the trust store yet. (In this scenario, the old * client still trusts the old CA cert.) */ g_file_get_contents (tls_test_file_path ("intermediate-ca.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); intermediate_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", root_cert, "certificate-pem", cert_data, NULL); g_assert_no_error (error); g_assert (intermediate_cert); /* Prepare the server cert. */ g_clear_pointer (&cert_data, g_free); g_file_get_contents (tls_test_file_path ("server-intermediate.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); g_file_get_contents (tls_test_file_path ("server-intermediate-key.pem"), &key_data, NULL, &error); g_assert_no_error (error); g_assert (key_data); server_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", intermediate_cert, "certificate-pem", cert_data, "private-key-pem", key_data, NULL); g_assert_no_error (error); g_assert (server_cert); g_object_unref (intermediate_cert); g_object_unref (root_cert); g_free (cert_data); g_free (key_data); test->server_certificate = server_cert; test_verified_connection (test, data); } static void test_invalid_chain_with_alternative_ca_cert (TestConnection *test, gconstpointer data) { GTlsBackend *backend; GTlsCertificate *server_cert; GTlsCertificate *root_cert; GIOStream *connection; char *cert_data = NULL; char *key_data = NULL; GError *error = NULL; backend = g_tls_backend_get_default (); /* This certificate has the same public key as a certificate in the root store. */ root_cert = g_tls_certificate_new_from_file (tls_test_file_path ("ca-alternative.pem"), &error); g_assert_no_error (error); g_assert (root_cert); /* The intermediate cert is not sent. The chain should be rejected, since without intermediate.pem * there is no proof that ca-alternative.pem signed server-intermediate.pem. */ g_file_get_contents (tls_test_file_path ("server-intermediate.pem"), &cert_data, NULL, &error); g_assert_no_error (error); g_assert (cert_data); g_file_get_contents (tls_test_file_path ("server-intermediate-key.pem"), &key_data, NULL, &error); g_assert_no_error (error); g_assert (key_data); server_cert = g_initable_new (g_tls_backend_get_certificate_type (backend), NULL, &error, "issuer", root_cert, "certificate-pem", cert_data, "private-key-pem", key_data, NULL); g_assert_no_error (error); g_assert (server_cert); g_object_unref (root_cert); g_free (cert_data); g_free (key_data); test->server_certificate = server_cert; connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE); 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); /* Make sure this test doesn't expire. */ g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), G_TLS_CERTIFICATE_VALIDATE_ALL & ~G_TLS_CERTIFICATE_EXPIRED); read_test_data_async (test); g_main_loop_run (test->loop); g_assert_error (test->read_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE); g_assert_no_error (test->server_error); } static void on_notify_accepted_cas (GObject *obj, GParamSpec *spec, gpointer user_data) { gboolean *changed = user_data; g_assert (*changed == FALSE); *changed = TRUE; } static void test_client_auth_connection (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; GTlsCertificate *cert; GTlsCertificate *peer; gboolean cas_changed; GSocketClient *client; 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, TRUE); 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); cert = g_tls_certificate_new_from_file (tls_test_file_path ("client-and-key.pem"), &error); g_assert_no_error (error); g_tls_connection_set_certificate (G_TLS_CONNECTION (test->client_connection), cert); /* 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); g_object_unref (test->database); g_object_unref (test->client_connection); /* Now start a new connection to the same server with a different client cert */ client = g_socket_client_new (); connection = G_IO_STREAM (g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address), NULL, &error)); g_assert_no_error (error); g_object_unref (client); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_object_unref (connection); g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), 0); cert = g_tls_certificate_new_from_file (tls_test_file_path ("client2-and-key.pem"), &error); g_assert_no_error (error); g_tls_connection_set_certificate (G_TLS_CONNECTION (test->client_connection), cert); g_object_unref (cert); 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); /* peer should see the second client cert */ 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)); } static void test_client_auth_rehandshake (TestConnection *test, gconstpointer data) { test->rehandshake = TRUE; test_client_auth_connection (test, data); } static void test_client_auth_failure (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; gboolean accepted_changed; GSocketClient *client; GTlsCertificate *cert; GTlsCertificate *peer; 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, TRUE); 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); /* No Certificate set */ /* All validation in this test */ g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), G_TLS_CERTIFICATE_VALIDATE_ALL); accepted_changed = FALSE; g_signal_connect (test->client_connection, "notify::accepted-cas", G_CALLBACK (on_notify_accepted_cas), &accepted_changed); read_test_data_async (test); g_main_loop_run (test->loop); g_assert_error (test->read_error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED); g_assert_error (test->server_error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED); g_assert (accepted_changed == TRUE); g_object_unref (test->client_connection); g_object_unref (test->database); g_clear_error (&test->read_error); g_clear_error (&test->server_error); /* Now start a new connection to the same server with a valid client cert; * this should succeed, and not use the cached failed session from above */ client = g_socket_client_new (); connection = G_IO_STREAM (g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address), NULL, &error)); g_assert_no_error (error); g_object_unref (client); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); 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); accepted_changed = FALSE; g_signal_connect (test->client_connection, "notify::accepted-cas", G_CALLBACK (on_notify_accepted_cas), &accepted_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 (accepted_changed == TRUE); g_object_unref (cert); } 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, TRUE); 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, TRUE); 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) { GIOStream *connection; GError *error = NULL; connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE); 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); /* Overrides loading of the default database */ g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), NULL); /* All validation in this test */ g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), G_TLS_CERTIFICATE_VALIDATE_ALL); test->accept_flags = G_TLS_CERTIFICATE_UNKNOWN_CA; g_signal_connect (test->client_connection, "accept-certificate", G_CALLBACK (on_accept_certificate), test); 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); } static void handshake_failed_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TestConnection *test = user_data; GError *error = NULL; g_tls_connection_handshake_finish (G_TLS_CONNECTION (test->client_connection), result, &error); g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE); g_clear_error (&error); g_main_loop_quit (test->loop); } static void test_failed_connection (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; GSocketConnectable *bad_addr; connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE); bad_addr = g_network_address_new ("wrong.example.com", 80); test->client_connection = g_tls_client_connection_new (connection, bad_addr, &error); g_object_unref (bad_addr); g_assert_no_error (error); g_object_unref (connection); g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection), G_PRIORITY_DEFAULT, NULL, handshake_failed_cb, test); g_main_loop_run (test->loop); 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_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE); g_assert_no_error (test->server_error); } static void socket_client_connected (GObject *source, GAsyncResult *result, gpointer user_data) { TestConnection *test = user_data; GSocketConnection *connection; GError *error = NULL; connection = g_socket_client_connect_finish (G_SOCKET_CLIENT (source), result, &error); g_assert_no_error (error); test->client_connection = G_IO_STREAM (connection); g_main_loop_quit (test->loop); } static void test_connection_socket_client (TestConnection *test, gconstpointer data) { GSocketClient *client; GTlsCertificateFlags flags; GSocketConnection *connection; GIOStream *base; GError *error = NULL; start_async_server_service (test, G_TLS_AUTHENTICATION_NONE, TRUE); client = g_socket_client_new (); g_socket_client_set_tls (client, TRUE); flags = G_TLS_CERTIFICATE_VALIDATE_ALL & ~G_TLS_CERTIFICATE_UNKNOWN_CA; /* test->address doesn't match the server's cert */ flags = flags & ~G_TLS_CERTIFICATE_BAD_IDENTITY; g_socket_client_set_tls_validation_flags (client, flags); g_socket_client_connect_async (client, G_SOCKET_CONNECTABLE (test->address), NULL, socket_client_connected, test); g_main_loop_run (test->loop); connection = (GSocketConnection *)test->client_connection; test->client_connection = NULL; g_assert (G_IS_TCP_WRAPPER_CONNECTION (connection)); base = g_tcp_wrapper_connection_get_base_io_stream (G_TCP_WRAPPER_CONNECTION (connection)); g_assert (G_IS_TLS_CONNECTION (base)); g_io_stream_close (G_IO_STREAM (connection), NULL, &error); g_assert_no_error (error); g_object_unref (connection); g_object_unref (client); } static void socket_client_failed (GObject *source, GAsyncResult *result, gpointer user_data) { TestConnection *test = user_data; GError *error = NULL; g_socket_client_connect_finish (G_SOCKET_CLIENT (source), result, &error); g_assert_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE); g_clear_error (&error); g_main_loop_quit (test->loop); } static void test_connection_socket_client_failed (TestConnection *test, gconstpointer data) { GSocketClient *client; start_async_server_service (test, G_TLS_AUTHENTICATION_NONE, TRUE); client = g_socket_client_new (); g_socket_client_set_tls (client, TRUE); /* this time we don't adjust the validation flags */ g_socket_client_connect_async (client, G_SOCKET_CONNECTABLE (test->address), NULL, socket_client_failed, test); g_main_loop_run (test->loop); g_object_unref (client); } static void socket_client_timed_out_write (GObject *source, GAsyncResult *result, gpointer user_data) { TestConnection *test = user_data; GSocketConnection *connection; GInputStream *input_stream; GOutputStream *output_stream; GError *error = NULL; gchar buffer[TEST_DATA_LENGTH]; gssize size; connection = g_socket_client_connect_finish (G_SOCKET_CLIENT (source), result, &error); g_assert_no_error (error); test->client_connection = G_IO_STREAM (connection); input_stream = g_io_stream_get_input_stream (test->client_connection); output_stream = g_io_stream_get_output_stream (test->client_connection); /* read TEST_DATA_LENGTH once */ size = g_input_stream_read (input_stream, &buffer, TEST_DATA_LENGTH, NULL, &error); g_assert_no_error (error); g_assert_cmpint (size, ==, TEST_DATA_LENGTH); /* read TEST_DATA_LENGTH again to cause the time out */ size = g_input_stream_read (input_stream, &buffer, TEST_DATA_LENGTH, NULL, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT); g_assert_cmpint (size, ==, -1); g_clear_error (&error); /* write after a timeout, session should still be valid */ size = g_output_stream_write (output_stream, TEST_DATA, TEST_DATA_LENGTH, NULL, &error); g_assert_no_error (error); g_assert_cmpint (size, ==, TEST_DATA_LENGTH); g_main_loop_quit (test->loop); } static void test_connection_read_time_out_write (TestConnection *test, gconstpointer data) { GSocketClient *client; GTlsCertificateFlags flags; GSocketConnection *connection; GIOStream *base; GError *error = NULL; /* Don't close the server connection after writing TEST_DATA. */ start_async_server_service (test, G_TLS_AUTHENTICATION_NONE, FALSE); client = g_socket_client_new (); /* Set a 1 second time out on the socket */ g_socket_client_set_timeout (client, 1); g_socket_client_set_tls (client, TRUE); flags = G_TLS_CERTIFICATE_VALIDATE_ALL & ~G_TLS_CERTIFICATE_UNKNOWN_CA; /* test->address doesn't match the server's cert */ flags = flags & ~G_TLS_CERTIFICATE_BAD_IDENTITY; g_socket_client_set_tls_validation_flags (client, flags); g_socket_client_connect_async (client, G_SOCKET_CONNECTABLE (test->address), NULL, socket_client_timed_out_write, test); g_main_loop_run (test->loop); /* Close the server now */ close_server_connection (test); connection = (GSocketConnection *)test->client_connection; test->client_connection = NULL; g_assert (G_IS_TCP_WRAPPER_CONNECTION (connection)); base = g_tcp_wrapper_connection_get_base_io_stream (G_TCP_WRAPPER_CONNECTION (connection)); g_assert (G_IS_TLS_CONNECTION (base)); g_io_stream_close (G_IO_STREAM (connection), NULL, &error); g_assert_no_error (error); g_object_unref (connection); g_object_unref (client); } static void simul_async_read_complete (GObject *object, GAsyncResult *result, gpointer user_data) { TestConnection *test = user_data; gssize nread; GError *error = NULL; nread = g_input_stream_read_finish (G_INPUT_STREAM (object), result, &error); g_assert_no_error (error); test->nread += nread; g_assert_cmpint (test->nread, <=, TEST_DATA_LENGTH); if (test->nread == TEST_DATA_LENGTH) { g_io_stream_close (test->client_connection, NULL, &error); g_assert_no_error (error); g_main_loop_quit (test->loop); } else { g_input_stream_read_async (G_INPUT_STREAM (object), test->buf + test->nread, TEST_DATA_LENGTH / 2, G_PRIORITY_DEFAULT, NULL, simul_async_read_complete, test); } } static void simul_async_write_complete (GObject *object, GAsyncResult *result, gpointer user_data) { TestConnection *test = user_data; gssize nwrote; GError *error = NULL; nwrote = g_output_stream_write_finish (G_OUTPUT_STREAM (object), result, &error); g_assert_no_error (error); test->nwrote += nwrote; if (test->nwrote < TEST_DATA_LENGTH) { g_output_stream_write_async (G_OUTPUT_STREAM (object), TEST_DATA + test->nwrote, TEST_DATA_LENGTH - test->nwrote, G_PRIORITY_DEFAULT, NULL, simul_async_write_complete, test); } } static void test_simultaneous_async (TestConnection *test, gconstpointer data) { GIOStream *connection; GTlsCertificateFlags flags; GError *error = NULL; connection = start_echo_server_and_connect_to_it (test); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_assert_no_error (error); g_object_unref (connection); flags = G_TLS_CERTIFICATE_VALIDATE_ALL & ~(G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_BAD_IDENTITY); g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), flags); memset (test->buf, 0, sizeof (test->buf)); test->nread = test->nwrote = 0; g_input_stream_read_async (g_io_stream_get_input_stream (test->client_connection), test->buf, TEST_DATA_LENGTH / 2, G_PRIORITY_DEFAULT, NULL, simul_async_read_complete, test); g_output_stream_write_async (g_io_stream_get_output_stream (test->client_connection), TEST_DATA, TEST_DATA_LENGTH / 2, G_PRIORITY_DEFAULT, NULL, simul_async_write_complete, test); g_main_loop_run (test->loop); g_assert_cmpint (test->nread, ==, TEST_DATA_LENGTH); g_assert_cmpint (test->nwrote, ==, TEST_DATA_LENGTH); g_assert_cmpstr (test->buf, ==, TEST_DATA); } static gboolean check_gnutls_has_rehandshaking_bug (void) { const char *version = gnutls_check_version (NULL); return (!strcmp (version, "3.1.27") || !strcmp (version, "3.1.28") || !strcmp (version, "3.2.19") || !strcmp (version, "3.3.8") || !strcmp (version, "3.3.9") || !strcmp (version, "3.3.10") || !strcmp (version, "3.6.1") || !strcmp (version, "3.6.2")); } static void test_simultaneous_async_rehandshake (TestConnection *test, gconstpointer data) { if (check_gnutls_has_rehandshaking_bug ()) { g_test_skip ("test would fail due to https://bugzilla.gnome.org/show_bug.cgi?id=794286#c13"); return; } test->rehandshake = TRUE; test_simultaneous_async (test, data); } static gpointer simul_read_thread (gpointer user_data) { TestConnection *test = user_data; GInputStream *istream = g_io_stream_get_input_stream (test->client_connection); GError *error = NULL; gssize nread; while (test->nread < TEST_DATA_LENGTH) { nread = g_input_stream_read (istream, test->buf + test->nread, MIN (TEST_DATA_LENGTH / 2, TEST_DATA_LENGTH - test->nread), NULL, &error); g_assert_no_error (error); test->nread += nread; } return NULL; } static gpointer simul_write_thread (gpointer user_data) { TestConnection *test = user_data; GOutputStream *ostream = g_io_stream_get_output_stream (test->client_connection); GError *error = NULL; gssize nwrote; while (test->nwrote < TEST_DATA_LENGTH) { nwrote = g_output_stream_write (ostream, TEST_DATA + test->nwrote, MIN (TEST_DATA_LENGTH / 2, TEST_DATA_LENGTH - test->nwrote), NULL, &error); g_assert_no_error (error); test->nwrote += nwrote; } return NULL; } static void test_simultaneous_sync (TestConnection *test, gconstpointer data) { GIOStream *connection; GTlsCertificateFlags flags; GError *error = NULL; GThread *read_thread, *write_thread; connection = start_echo_server_and_connect_to_it (test); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_assert_no_error (error); g_object_unref (connection); flags = G_TLS_CERTIFICATE_VALIDATE_ALL & ~(G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_BAD_IDENTITY); g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), flags); memset (test->buf, 0, sizeof (test->buf)); test->nread = test->nwrote = 0; read_thread = g_thread_new ("reader", simul_read_thread, test); write_thread = g_thread_new ("writer", simul_write_thread, test); /* We need to run the main loop to get the GThreadedSocketService to * receive the connection and spawn the server thread. */ while (!test->server_connection) g_main_context_iteration (NULL, FALSE); g_thread_join (write_thread); g_thread_join (read_thread); g_assert_cmpint (test->nread, ==, TEST_DATA_LENGTH); g_assert_cmpint (test->nwrote, ==, TEST_DATA_LENGTH); g_assert_cmpstr (test->buf, ==, TEST_DATA); g_io_stream_close (test->client_connection, NULL, &error); g_assert_no_error (error); } static void test_simultaneous_sync_rehandshake (TestConnection *test, gconstpointer data) { if (check_gnutls_has_rehandshaking_bug ()) { g_test_skip ("test would fail due to https://bugzilla.gnome.org/show_bug.cgi?id=794286#c13"); return; } test->rehandshake = TRUE; test_simultaneous_sync (test, data); } static void test_close_immediately (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_assert_no_error (error); g_object_unref (connection); /* * At this point the server won't get a chance to run. But regardless * closing should not wait on the server, trying to handshake or something. */ g_io_stream_close (test->client_connection, NULL, &error); g_assert_no_error (error); } static void quit_loop_on_notify (GObject *obj, GParamSpec *spec, gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); } static void handshake_completed (GObject *object, GAsyncResult *result, gpointer user_data) { gboolean *complete = user_data; *complete = TRUE; return; } static void test_close_during_handshake (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; GMainContext *context; GMainLoop *loop; gboolean handshake_complete = FALSE; g_test_bug ("688751"); connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_REQUESTED, TRUE); test->expect_server_error = TRUE; test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_assert_no_error (error); g_object_unref (connection); loop = g_main_loop_new (NULL, FALSE); g_signal_connect (test->client_connection, "notify::accepted-cas", G_CALLBACK (quit_loop_on_notify), loop); context = g_main_context_new (); g_main_context_push_thread_default (context); g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection), G_PRIORITY_DEFAULT, NULL, handshake_completed, &handshake_complete); g_main_context_pop_thread_default (context); /* Now run the (default GMainContext) loop, which is needed for * the server side of things. The client-side handshake will run in * a thread, but its callback will never be invoked because its * context isn't running. */ g_main_loop_run (loop); g_main_loop_unref (loop); /* At this point handshake_thread() has started (and maybe * finished), but handshake_thread_completed() (and thus * finish_handshake()) has not yet run. Make sure close doesn't * block. */ g_io_stream_close (test->client_connection, NULL, &error); g_assert_no_error (error); /* We have to let the handshake_async() call finish now, or * teardown_connection() will assert. */ while (!handshake_complete) g_main_context_iteration (context, TRUE); g_main_context_unref (context); } static void test_output_stream_close_during_handshake (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; GMainContext *context; GMainLoop *loop; gboolean handshake_complete = FALSE; g_test_bug ("688751"); connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_REQUESTED, TRUE); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_assert_no_error (error); g_object_unref (connection); loop = g_main_loop_new (NULL, FALSE); g_signal_connect (test->client_connection, "notify::accepted-cas", G_CALLBACK (quit_loop_on_notify), loop); context = g_main_context_new (); g_main_context_push_thread_default (context); g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection), G_PRIORITY_DEFAULT, NULL, handshake_completed, &handshake_complete); g_main_context_pop_thread_default (context); /* Now run the (default GMainContext) loop, which is needed for * the server side of things. The client-side handshake will run in * a thread, but its callback will never be invoked because its * context isn't running. */ g_main_loop_run (loop); g_main_loop_unref (loop); /* At this point handshake_thread() has started (and maybe * finished), but handshake_thread_completed() (and thus * finish_handshake()) has not yet run. Make sure close doesn't * block. */ g_output_stream_close (g_io_stream_get_output_stream (test->client_connection), NULL, &error); g_assert_no_error (error); /* We have to let the handshake_async() call finish now, or * teardown_connection() will assert. */ while (!handshake_complete) g_main_context_iteration (context, TRUE); g_main_context_unref (context); } static void test_write_during_handshake (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; GMainContext *context; GMainLoop *loop; GOutputStream *ostream; gboolean handshake_complete = FALSE; g_test_bug ("697754"); connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_REQUESTED, TRUE); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_assert_no_error (error); g_object_unref (connection); loop = g_main_loop_new (NULL, FALSE); g_signal_connect (test->client_connection, "notify::accepted-cas", G_CALLBACK (quit_loop_on_notify), loop); context = g_main_context_new (); g_main_context_push_thread_default (context); g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection), G_PRIORITY_DEFAULT, NULL, handshake_completed, &handshake_complete); g_main_context_pop_thread_default (context); /* Now run the (default GMainContext) loop, which is needed for * the server side of things. The client-side handshake will run in * a thread, but its callback will never be invoked because its * context isn't running. */ g_main_loop_run (loop); g_main_loop_unref (loop); /* At this point handshake_thread() has started (and maybe * finished), but handshake_thread_completed() (and thus * finish_handshake()) has not yet run. Make sure close doesn't * block. */ ostream = g_io_stream_get_output_stream (test->client_connection); g_output_stream_write (ostream, TEST_DATA, TEST_DATA_LENGTH, G_PRIORITY_DEFAULT, &error); g_assert_no_error (error); /* We have to let the handshake_async() call finish now, or * teardown_connection() will assert. */ while (!handshake_complete) g_main_context_iteration (context, TRUE); g_main_context_unref (context); } static gboolean async_implicit_handshake_dispatch (GPollableInputStream *stream, gpointer user_data) { TestConnection *test = user_data; GError *error = NULL; gchar buffer[TEST_DATA_LENGTH]; gssize size; gboolean keep_running; size = g_pollable_input_stream_read_nonblocking (stream, buffer, TEST_DATA_LENGTH, NULL, &error); keep_running = (-1 == size); if (keep_running) { g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK); g_error_free (error); } else { g_assert_no_error (error); g_assert_cmpint (size, ==, TEST_DATA_LENGTH); g_main_loop_quit (test->loop); } return keep_running; } static void test_async_implicit_handshake (TestConnection *test, gconstpointer data) { GTlsCertificateFlags flags; GIOStream *stream; GInputStream *input_stream; GSource *input_source; GError *error = NULL; g_test_bug ("710691"); stream = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE); test->client_connection = g_tls_client_connection_new (stream, test->identity, &error); g_assert_no_error (error); g_object_unref (stream); flags = G_TLS_CERTIFICATE_VALIDATE_ALL & ~(G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_BAD_IDENTITY); g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), flags); /** * Create a source from the client's input stream. The dispatch * callback will be called a first time, which will perform a * non-blocking read triggering the asynchronous implicit * handshaking. */ input_stream = g_io_stream_get_input_stream (test->client_connection); input_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (input_stream), NULL); g_source_set_callback (input_source, (GSourceFunc) async_implicit_handshake_dispatch, test, NULL); g_source_attach (input_source, NULL); g_main_loop_run (test->loop); g_io_stream_close (G_IO_STREAM (test->client_connection), NULL, &error); g_assert_no_error (error); g_object_unref (test->client_connection); test->client_connection = NULL; } static void quit_on_handshake_complete (GObject *object, GAsyncResult *result, gpointer user_data) { TestConnection *test = user_data; GError *error = NULL; g_tls_connection_handshake_finish (G_TLS_CONNECTION (object), result, &error); g_assert_no_error (error); g_main_loop_quit (test->loop); return; } static void test_fallback (TestConnection *test, gconstpointer data) { GIOStream *connection; GTlsConnection *tlsconn; GError *error = NULL; connection = start_echo_server_and_connect_to_it (test); test->client_connection = g_tls_client_connection_new (connection, NULL, &error); g_assert_no_error (error); tlsconn = G_TLS_CONNECTION (test->client_connection); g_object_unref (connection); g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), 0); #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif g_tls_client_connection_set_use_ssl3 (G_TLS_CLIENT_CONNECTION (test->client_connection), TRUE); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif g_tls_connection_handshake_async (tlsconn, G_PRIORITY_DEFAULT, NULL, quit_on_handshake_complete, test); g_main_loop_run (test->loop); /* In 2.42 we don't have the API to test that the correct version was negotiated, * so we merely test that the connection succeeded at all. */ g_io_stream_close (test->client_connection, NULL, &error); g_assert_no_error (error); } static void test_output_stream_close (TestConnection *test, gconstpointer data) { GIOStream *connection; GError *error = NULL; gboolean ret; gboolean handshake_complete = FALSE; gssize size; connection = start_async_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE, TRUE); test->client_connection = g_tls_client_connection_new (connection, test->identity, &error); g_assert_no_error (error); g_object_unref (connection); /* No validation at all in this test */ g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection), 0); g_tls_connection_handshake_async (G_TLS_CONNECTION (test->client_connection), G_PRIORITY_DEFAULT, NULL, handshake_completed, &handshake_complete); while (!handshake_complete) g_main_context_iteration (NULL, TRUE); ret = g_output_stream_close (g_io_stream_get_output_stream (test->client_connection), NULL, &error); g_assert_no_error (error); g_assert (ret); /* Verify that double close returns TRUE */ ret = g_output_stream_close (g_io_stream_get_output_stream (test->client_connection), NULL, &error); g_assert_no_error (error); g_assert (ret); size = g_output_stream_write (g_io_stream_get_output_stream (test->client_connection), "data", 4, NULL, &error); g_assert (size == -1); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED); g_clear_error (&error); /* We closed the output stream, but not the input stream, so receiving * data should still work. */ 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); ret = g_io_stream_close (test->client_connection, NULL, &error); g_assert_no_error (error); g_assert (ret); } int main (int argc, char *argv[]) { int ret; g_test_init (&argc, &argv, NULL); g_test_bug_base ("http://bugzilla.gnome.org/"); g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); g_setenv ("GIO_EXTRA_MODULES", TOP_BUILDDIR "/tls/gnutls/.libs", TRUE); g_setenv ("GIO_USE_TLS", "gnutls", TRUE); g_test_add ("/tls/connection/basic", TestConnection, NULL, setup_connection, test_basic_connection, teardown_connection); g_test_add ("/tls/connection/verified", TestConnection, NULL, setup_connection, test_verified_connection, teardown_connection); g_test_add ("/tls/connection/verified-chain", TestConnection, NULL, setup_connection, test_verified_chain, teardown_connection); g_test_add ("/tls/connection/verified-chain-with-redundant-root-cert", TestConnection, NULL, setup_connection, test_verified_chain_with_redundant_root_cert, teardown_connection); g_test_add ("/tls/connection/verified-chain-with-duplicate-server-cert", TestConnection, NULL, setup_connection, test_verified_chain_with_duplicate_server_cert, teardown_connection); g_test_add ("/tls/connection/verified-unordered-chain", TestConnection, NULL, setup_connection, test_verified_unordered_chain, teardown_connection); g_test_add ("/tls/connection/verified-chain-with-alternative-ca-cert", TestConnection, NULL, setup_connection, test_verified_chain_with_alternative_ca_cert, teardown_connection); g_test_add ("/tls/connection/invalid-chain-with-alternative-ca-cert", TestConnection, NULL, setup_connection, test_invalid_chain_with_alternative_ca_cert, teardown_connection); g_test_add ("/tls/connection/client-auth", TestConnection, NULL, setup_connection, test_client_auth_connection, teardown_connection); g_test_add ("/tls/connection/client-auth-rehandshake", TestConnection, NULL, 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, setup_connection, test_failed_connection, teardown_connection); g_test_add ("/tls/connection/socket-client", TestConnection, NULL, setup_connection, test_connection_socket_client, teardown_connection); g_test_add ("/tls/connection/socket-client-failed", TestConnection, NULL, setup_connection, test_connection_socket_client_failed, teardown_connection); g_test_add ("/tls/connection/read-time-out-then-write", TestConnection, NULL, setup_connection, test_connection_read_time_out_write, teardown_connection); g_test_add ("/tls/connection/simultaneous-async", TestConnection, NULL, setup_connection, test_simultaneous_async, teardown_connection); g_test_add ("/tls/connection/simultaneous-sync", TestConnection, NULL, setup_connection, test_simultaneous_sync, teardown_connection); g_test_add ("/tls/connection/simultaneous-async-rehandshake", TestConnection, NULL, setup_connection, test_simultaneous_async_rehandshake, teardown_connection); g_test_add ("/tls/connection/simultaneous-sync-rehandshake", TestConnection, NULL, setup_connection, test_simultaneous_sync_rehandshake, teardown_connection); g_test_add ("/tls/connection/close-immediately", TestConnection, NULL, setup_connection, test_close_immediately, teardown_connection); g_test_add ("/tls/connection/close-during-handshake", TestConnection, NULL, setup_connection, test_close_during_handshake, teardown_connection); g_test_add ("/tls/connection/close-output-stream-during-handshake", TestConnection, NULL, setup_connection, test_output_stream_close_during_handshake, teardown_connection); g_test_add ("/tls/connection/write-during-handshake", TestConnection, NULL, setup_connection, test_write_during_handshake, teardown_connection); g_test_add ("/tls/connection/async-implicit-handshake", TestConnection, NULL, setup_connection, test_async_implicit_handshake, teardown_connection); g_test_add ("/tls/connection/output-stream-close", TestConnection, NULL, setup_connection, test_output_stream_close, teardown_connection); g_test_add ("/tls/connection/fallback", TestConnection, NULL, setup_connection, test_fallback, teardown_connection); ret = g_test_run(); /* for valgrinding */ g_main_context_unref (g_main_context_default ()); return ret; }