From 65af7c47fff4ab04f26d6c8e33c6a01e19139613 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 28 Nov 2012 22:01:21 +0100 Subject: [PATCH] Add a request_certificate virtual method to GTlsInteraction This allows GTlsConnection implementations to request a certificate from the user. Fix ups by Dan Winship https://bugzilla.gnome.org/show_bug.cgi?id=637257 --- docs/reference/gio/gio-sections.txt | 7 +- gio/gioenums.h | 14 + gio/gtlsinteraction.c | 390 ++++++++++++++++++++++++--- gio/gtlsinteraction.h | 47 +++- gio/tests/Makefile.am | 2 + gio/tests/tls-interaction.c | 508 ++++++++++++++++++++++++++++++++---- 6 files changed, 867 insertions(+), 101 deletions(-) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index d53ca78..ee6c5de 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -3567,10 +3567,15 @@ G_TYPE_TLS_PASSWORD_FLAGS GTlsInteraction GTlsInteraction GTlsInteractionResult +GTlsCertificateRequestFlags +g_tls_interaction_invoke_ask_password +g_tls_interaction_invoke_request_certificate g_tls_interaction_ask_password g_tls_interaction_ask_password_async g_tls_interaction_ask_password_finish -g_tls_interaction_invoke_ask_password +g_tls_interaction_request_certificate +g_tls_interaction_request_certificate_async +g_tls_interaction_request_certificate_finish GTlsInteractionClass G_IS_TLS_INTERACTION diff --git a/gio/gioenums.h b/gio/gioenums.h index 49f2917..dc7341b 100644 --- a/gio/gioenums.h +++ b/gio/gioenums.h @@ -1631,6 +1631,20 @@ typedef enum { } GTlsDatabaseLookupFlags; /** + * GTlsCertificateRequestFlags: + * @G_TLS_CERTIFICATE_REQUEST_NONE: No flags + * + * Flags for g_tls_interaction_request_certificate(), + * g_tls_interaction_request_certificate_async(), and + * g_tls_interaction_invoke_request_certificate(). + * + * Since: 2.40 + */ +typedef enum { + G_TLS_CERTIFICATE_REQUEST_NONE = 0 +} GTlsCertificateRequestFlags; + +/** * GIOModuleScopeFlags: * @G_IO_MODULE_SCOPE_NONE: No module scan flags * @G_IO_MODULE_SCOPE_BLOCK_DUPLICATES: When using this scope to load or diff --git a/gio/gtlsinteraction.c b/gio/gtlsinteraction.c index 0701ddb..8059dc3 100644 --- a/gio/gtlsinteraction.c +++ b/gio/gtlsinteraction.c @@ -24,6 +24,8 @@ #include +#include "gtlscertificate.h" +#include "gtlsconnection.h" #include "gtlsinteraction.h" #include "gtlspassword.h" #include "gasyncresult.h" @@ -182,6 +184,55 @@ invoke_closure_wait_and_free (InvokeClosure *closure, return result; } +static GTlsInteractionResult +invoke_closure_complete_and_free (GTlsInteraction *interaction, + InvokeClosure *closure, + GError **error) +{ + GTlsInteractionResult result; + gboolean complete; + + /* + * Handle the case where we've been called from within the main context + * or in the case where the main context is not running. This approximates + * the behavior of a modal dialog. + */ + if (g_main_context_acquire (interaction->priv->context)) + { + for (;;) + { + g_mutex_lock (&closure->mutex); + complete = closure->complete; + g_mutex_unlock (&closure->mutex); + if (complete) + break; + g_main_context_iteration (interaction->priv->context, TRUE); + } + + g_main_context_release (interaction->priv->context); + + if (closure->error) + { + g_propagate_error (error, closure->error); + closure->error = NULL; + } + + result = closure->result; + invoke_closure_free (closure); + } + + /* + * Handle the case where we're in a different thread than the main + * context and a main loop is running. + */ + else + { + result = invoke_closure_wait_and_free (closure, error); + } + + return result; +} + static void g_tls_interaction_init (GTlsInteraction *interaction) { @@ -231,9 +282,9 @@ on_invoke_ask_password_sync (gpointer user_data) } static void -on_async_as_sync_complete (GObject *source, - GAsyncResult *result, - gpointer user_data) +on_ask_password_complete (GObject *source, + GAsyncResult *result, + gpointer user_data) { InvokeClosure *closure = user_data; GTlsInteractionClass *klass; @@ -266,7 +317,7 @@ on_invoke_ask_password_async_as_sync (gpointer user_data) klass->ask_password_async (closure->interaction, G_TLS_PASSWORD (closure->argument), closure->cancellable, - on_async_as_sync_complete, + on_ask_password_complete, closure); /* Note that we've used these */ @@ -318,7 +369,6 @@ g_tls_interaction_invoke_ask_password (GTlsInteraction *interaction, GTlsInteractionResult result; InvokeClosure *closure; GTlsInteractionClass *klass; - gboolean complete; g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), G_TLS_INTERACTION_UNHANDLED); g_return_val_if_fail (G_IS_TLS_PASSWORD (password), G_TLS_INTERACTION_UNHANDLED); @@ -339,43 +389,7 @@ g_tls_interaction_invoke_ask_password (GTlsInteraction *interaction, g_main_context_invoke (interaction->priv->context, on_invoke_ask_password_async_as_sync, closure); - /* - * Handle the case where we've been called from within the main context - * or in the case where the main context is not running. This approximates - * the behavior of a modal dialog. - */ - if (g_main_context_acquire (interaction->priv->context)) - { - for (;;) - { - g_mutex_lock (&closure->mutex); - complete = closure->complete; - g_mutex_unlock (&closure->mutex); - if (complete) - break; - g_main_context_iteration (interaction->priv->context, TRUE); - } - - g_main_context_release (interaction->priv->context); - - if (closure->error) - { - g_propagate_error (error, closure->error); - closure->error = NULL; - } - - result = closure->result; - invoke_closure_free (closure); - } - - /* - * Handle the case where we're in a different thread than the main - * context and a main loop is running. - */ - else - { - result = invoke_closure_wait_and_free (closure, error); - } + result = invoke_closure_complete_and_free (interaction, closure, error); } else { @@ -386,7 +400,6 @@ g_tls_interaction_invoke_ask_password (GTlsInteraction *interaction, return result; } - /** * g_tls_interaction_ask_password: * @interaction: a #GTlsInteraction object @@ -531,3 +544,294 @@ g_tls_interaction_ask_password_finish (GTlsInteraction *interaction, return g_task_propagate_int (G_TASK (result), error); } } + +static gboolean +on_invoke_request_certificate_sync (gpointer user_data) +{ + InvokeClosure *closure = user_data; + GTlsInteractionClass *klass; + + g_mutex_lock (&closure->mutex); + + klass = G_TLS_INTERACTION_GET_CLASS (closure->interaction); + g_assert (klass->request_certificate != NULL); + + closure->result = klass->request_certificate (closure->interaction, + G_TLS_CONNECTION (closure->argument), + 0, + closure->cancellable, + &closure->error); + + closure->complete = TRUE; + g_cond_signal (&closure->cond); + g_mutex_unlock (&closure->mutex); + + return FALSE; /* don't call again */ +} + +static void +on_request_certificate_complete (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + InvokeClosure *closure = user_data; + GTlsInteractionClass *klass; + + g_mutex_lock (&closure->mutex); + + klass = G_TLS_INTERACTION_GET_CLASS (closure->interaction); + g_assert (klass->request_certificate_finish != NULL); + + closure->result = klass->request_certificate_finish (closure->interaction, + result, &closure->error); + + closure->complete = TRUE; + g_cond_signal (&closure->cond); + g_mutex_unlock (&closure->mutex); +} + +static gboolean +on_invoke_request_certificate_async_as_sync (gpointer user_data) +{ + InvokeClosure *closure = user_data; + GTlsInteractionClass *klass; + + g_mutex_lock (&closure->mutex); + + klass = G_TLS_INTERACTION_GET_CLASS (closure->interaction); + g_assert (klass->request_certificate_async); + + klass->request_certificate_async (closure->interaction, + G_TLS_CONNECTION (closure->argument), 0, + closure->cancellable, + on_request_certificate_complete, + closure); + + /* Note that we've used these */ + closure->callback = NULL; + closure->user_data = NULL; + + g_mutex_unlock (&closure->mutex); + + return FALSE; /* don't call again */ +} + +/** + * g_tls_interaction_invoke_request_certificate: + * @interaction: a #GTlsInteraction object + * @connection: a #GTlsConnection object + * @flags: flags providing more information about the request + * @cancellable: an optional #GCancellable cancellation object + * @error: an optional location to place an error on failure + * + * Invoke the interaction to ask the user to choose a certificate to + * use with the connection. It invokes this interaction in the main + * loop, specifically the #GMainContext returned by + * g_main_context_get_thread_default() when the interaction is + * created. This is called by called by #GTlsConnection when the peer + * requests a certificate during the handshake. + * + * Derived subclasses usually implement a certificate selector, + * although they may also choose to provide a certificate from + * elsewhere. Alternatively the user may abort this certificate + * request, which may or may not abort the TLS connection. + * + * The implementation can either be a synchronous (eg: modal dialog) or an + * asynchronous one (eg: modeless dialog). This function will take care of + * calling which ever one correctly. + * + * If the interaction is cancelled by the cancellation object, or by the + * user then %G_TLS_INTERACTION_FAILED will be returned with an error that + * contains a %G_IO_ERROR_CANCELLED error code. Certain implementations may + * not support immediate cancellation. + * + * Returns: The status of the certificate request interaction. + * + * Since: 2.40 + */ +GTlsInteractionResult +g_tls_interaction_invoke_request_certificate (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GError **error) +{ + GTlsInteractionResult result; + InvokeClosure *closure; + GTlsInteractionClass *klass; + + g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), G_TLS_INTERACTION_UNHANDLED); + g_return_val_if_fail (G_IS_TLS_CONNECTION (connection), G_TLS_INTERACTION_UNHANDLED); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), G_TLS_INTERACTION_UNHANDLED); + + closure = invoke_closure_new (interaction, G_OBJECT (connection), cancellable); + + klass = G_TLS_INTERACTION_GET_CLASS (interaction); + if (klass->request_certificate) + { + g_main_context_invoke (interaction->priv->context, + on_invoke_request_certificate_sync, closure); + result = invoke_closure_wait_and_free (closure, error); + } + else if (klass->request_certificate_async) + { + g_return_val_if_fail (klass->request_certificate_finish, G_TLS_INTERACTION_UNHANDLED); + g_main_context_invoke (interaction->priv->context, + on_invoke_request_certificate_async_as_sync, closure); + + result = invoke_closure_complete_and_free (interaction, closure, error); + } + else + { + result = G_TLS_INTERACTION_UNHANDLED; + invoke_closure_free (closure); + } + + return result; +} + +/** + * g_tls_interaction_request_certificate: + * @interaction: a #GTlsInteraction object + * @connection: a #GTlsConnection object + * @flags: flags providing more information about the request + * @cancellable: an optional #GCancellable cancellation object + * @error: an optional location to place an error on failure + * + * Run synchronous interaction to ask the user to choose a certificate to use + * with the connection. In general, g_tls_interaction_invoke_request_certificate() + * should be used instead of this function. + * + * Derived subclasses usually implement a certificate selector, although they may + * also choose to provide a certificate from elsewhere. Alternatively the user may + * abort this certificate request, which will usually abort the TLS connection. + * + * If %G_TLS_INTERACTION_HANDLED is returned, then the #GTlsConnection + * passed to g_tls_interaction_request_certificate() will have had its + * #GTlsConnection:certificate filled in. + * + * If the interaction is cancelled by the cancellation object, or by the + * user then %G_TLS_INTERACTION_FAILED will be returned with an error that + * contains a %G_IO_ERROR_CANCELLED error code. Certain implementations may + * not support immediate cancellation. + * + * Returns: The status of the request certificate interaction. + * + * Since: 2.40 + */ +GTlsInteractionResult +g_tls_interaction_request_certificate (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GError **error) +{ + GTlsInteractionClass *klass; + + g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), G_TLS_INTERACTION_UNHANDLED); + g_return_val_if_fail (G_IS_TLS_CONNECTION (connection), G_TLS_INTERACTION_UNHANDLED); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), G_TLS_INTERACTION_UNHANDLED); + + klass = G_TLS_INTERACTION_GET_CLASS (interaction); + if (klass->request_certificate) + return (klass->request_certificate) (interaction, connection, flags, cancellable, error); + else + return G_TLS_INTERACTION_UNHANDLED; +} + +/** + * g_tls_interaction_request_certificate_async: + * @interaction: a #GTlsInteraction object + * @connection: a #GTlsConnection object + * @flags: flags providing more information about the request + * @cancellable: an optional #GCancellable cancellation object + * @callback: (allow-none): will be called when the interaction completes + * @user_data: (allow-none): data to pass to the @callback + * + * Run asynchronous interaction to ask the user for a certificate to use with + * the connection. In general, g_tls_interaction_invoke_request_certificate() should + * be used instead of this function. + * + * Derived subclasses usually implement a certificate selector, although they may + * also choose to provide a certificate from elsewhere. @callback will be called + * when the operation completes. Alternatively the user may abort this certificate + * request, which will usually abort the TLS connection. + * + * Since: 2.40 + */ +void +g_tls_interaction_request_certificate_async (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTlsInteractionClass *klass; + GTask *task; + + g_return_if_fail (G_IS_TLS_INTERACTION (interaction)); + g_return_if_fail (G_IS_TLS_CONNECTION (connection)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + klass = G_TLS_INTERACTION_GET_CLASS (interaction); + if (klass->request_certificate_async) + { + g_return_if_fail (klass->request_certificate_finish); + (klass->request_certificate_async) (interaction, connection, flags, + cancellable, callback, user_data); + } + else + { + task = g_task_new (interaction, cancellable, callback, user_data); + g_task_set_source_tag (task, g_tls_interaction_request_certificate_async); + g_task_return_int (task, G_TLS_INTERACTION_UNHANDLED); + g_object_unref (task); + } +} + +/** + * g_tls_interaction_request_certificate_finish: + * @interaction: a #GTlsInteraction object + * @result: the result passed to the callback + * @error: an optional location to place an error on failure + * + * Complete an request certificate user interaction request. This should be once + * the g_tls_interaction_request_certificate_async() completion callback is called. + * + * If %G_TLS_INTERACTION_HANDLED is returned, then the #GTlsConnection + * passed to g_tls_interaction_request_certificate_async() will have had its + * #GTlsConnection:certificate filled in. + * + * If the interaction is cancelled by the cancellation object, or by the + * user then %G_TLS_INTERACTION_FAILED will be returned with an error that + * contains a %G_IO_ERROR_CANCELLED error code. + * + * Returns: The status of the request certificate interaction. + * + * Since: 2.40 + */ +GTlsInteractionResult +g_tls_interaction_request_certificate_finish (GTlsInteraction *interaction, + GAsyncResult *result, + GError **error) +{ + GTlsInteractionClass *klass; + + g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), G_TLS_INTERACTION_UNHANDLED); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), G_TLS_INTERACTION_UNHANDLED); + + klass = G_TLS_INTERACTION_GET_CLASS (interaction); + if (klass->request_certificate_finish) + { + g_return_val_if_fail (klass->request_certificate_async != NULL, G_TLS_INTERACTION_UNHANDLED); + + return (klass->request_certificate_finish) (interaction, result, error); + } + else + { + g_return_val_if_fail (g_async_result_is_tagged (result, g_tls_interaction_request_certificate_async), G_TLS_INTERACTION_UNHANDLED); + + return g_task_propagate_int (G_TASK (result), error); + } +} diff --git a/gio/gtlsinteraction.h b/gio/gtlsinteraction.h index 283464e..bb3df32 100644 --- a/gio/gtlsinteraction.h +++ b/gio/gtlsinteraction.h @@ -69,9 +69,26 @@ struct _GTlsInteractionClass GAsyncResult *result, GError **error); + GTlsInteractionResult (* request_certificate) (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GError **error); + + void (* request_certificate_async) (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + + GTlsInteractionResult (* request_certificate_finish) (GTlsInteraction *interaction, + GAsyncResult *result, + GError **error); + /*< private >*/ /* Padding for future expansion */ - gpointer padding[24]; + gpointer padding[21]; }; GLIB_AVAILABLE_IN_ALL @@ -83,7 +100,6 @@ GTlsInteractionResult g_tls_interaction_invoke_ask_password (GTlsInteraction GCancellable *cancellable, GError **error); - GLIB_AVAILABLE_IN_ALL GTlsInteractionResult g_tls_interaction_ask_password (GTlsInteraction *interaction, GTlsPassword *password, @@ -102,6 +118,33 @@ GTlsInteractionResult g_tls_interaction_ask_password_finish (GTlsInteraction GAsyncResult *result, GError **error); +GLIB_AVAILABLE_IN_2_40 +GTlsInteractionResult g_tls_interaction_invoke_request_certificate (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GError **error); + +GLIB_AVAILABLE_IN_2_40 +GTlsInteractionResult g_tls_interaction_request_certificate (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GError **error); + +GLIB_AVAILABLE_IN_2_40 +void g_tls_interaction_request_certificate_async (GTlsInteraction *interaction, + GTlsConnection *connection, + GTlsCertificateRequestFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GLIB_AVAILABLE_IN_2_40 +GTlsInteractionResult g_tls_interaction_request_certificate_finish (GTlsInteraction *interaction, + GAsyncResult *result, + GError **error); + G_END_DECLS #endif /* __G_TLS_INTERACTION_H__ */ diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index 3d9d475..e1ef22a 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -403,6 +403,8 @@ CLEANFILES += gdbus-test-codegen-generated.[ch] gdbus-test-codegen-generated-doc endif # OS_UNIX endif # HAVE_DBUS_DAEMON +tls_interaction_SOURCES = tls-interaction.c gtesttlsbackend.c gtesttlsbackend.h + # ----------------------------------------------------------------------------- if OS_WIN32 diff --git a/gio/tests/tls-interaction.c b/gio/tests/tls-interaction.c index a34ff29..ce2aa88 100644 --- a/gio/tests/tls-interaction.c +++ b/gio/tests/tls-interaction.c @@ -24,11 +24,18 @@ #include +#include "gtesttlsbackend.h" + +static GPtrArray *fixtures = NULL; + typedef struct { /* Class virtual interaction methods */ gpointer ask_password_func; gpointer ask_password_async_func; gpointer ask_password_finish_func; + gpointer request_certificate_func; + gpointer request_certificate_async_func; + gpointer request_certificate_finish_func; /* Expected results */ GTlsInteractionResult result; @@ -40,6 +47,7 @@ typedef struct { typedef struct { GTlsInteraction *interaction; GTlsPassword *password; + GTlsConnection *connection; GMainLoop *loop; GThread *interaction_thread; GThread *test_thread; @@ -213,6 +221,155 @@ test_interaction_ask_password_sync_failure (GTlsInteraction *interaction, return G_TLS_INTERACTION_FAILED; } +static void +test_interaction_request_certificate_async_success (GTlsInteraction *interaction, + GTlsConnection *connection, + gint unused_flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (G_IS_TLS_CONNECTION (connection)); + g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_assert (unused_flags == 0); + + task = g_task_new (self, cancellable, callback, user_data); + + /* + * IRL would call g_tls_connection_set_certificate(). But here just touch + * the connection in a detectable way. + */ + g_object_set_data (G_OBJECT (connection), "chosen-certificate", "my-certificate"); + g_task_return_int (task, G_TLS_INTERACTION_HANDLED); + g_object_unref (task); +} + +static GTlsInteractionResult +test_interaction_request_certificate_finish_success (GTlsInteraction *interaction, + GAsyncResult *result, + GError **error) +{ + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (g_task_is_valid (result, interaction)); + g_assert (error != NULL); + g_assert (*error == NULL); + + return g_task_propagate_int (G_TASK (result), error); +} + +static void +test_interaction_request_certificate_async_failure (GTlsInteraction *interaction, + GTlsConnection *connection, + gint unused_flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (G_IS_TLS_CONNECTION (connection)); + g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_assert (unused_flags == 0); + + task = g_task_new (self, cancellable, callback, user_data); + + g_task_return_new_error (task, G_FILE_ERROR, G_FILE_ERROR_NOENT, "Another message"); + g_object_unref (task); +} + +static GTlsInteractionResult +test_interaction_request_certificate_finish_failure (GTlsInteraction *interaction, + GAsyncResult *result, + GError **error) +{ + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (g_task_is_valid (result, interaction)); + g_assert (error != NULL); + g_assert (*error == NULL); + + if (g_task_propagate_int (G_TASK (result), error) != -1) + g_assert_not_reached (); + + return G_TLS_INTERACTION_FAILED; +} + +static GTlsInteractionResult +test_interaction_request_certificate_sync_success (GTlsInteraction *interaction, + GTlsConnection *connection, + gint unused_flags, + GCancellable *cancellable, + GError **error) +{ + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (G_IS_TLS_CONNECTION (connection)); + g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_assert (error != NULL); + g_assert (*error == NULL); + + /* + * IRL would call g_tls_connection_set_certificate(). But here just touch + * the connection in a detectable way. + */ + g_object_set_data (G_OBJECT (connection), "chosen-certificate", "my-certificate"); + return G_TLS_INTERACTION_HANDLED; +} + +static GTlsInteractionResult +test_interaction_request_certificate_sync_failure (GTlsInteraction *interaction, + GTlsConnection *connection, + gint unused_flags, + GCancellable *cancellable, + GError **error) +{ + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (G_IS_TLS_CONNECTION (connection)); + g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_assert (unused_flags == 0); + g_assert (error != NULL); + g_assert (*error == NULL); + + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "Another message"); + return G_TLS_INTERACTION_FAILED; +} + /* ---------------------------------------------------------------------------- * ACTUAL TESTS */ @@ -344,6 +501,133 @@ test_ask_password (Test *test, g_main_loop_quit (test->loop); } +static void +on_request_certificate_async_call (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + Test *test = user_data; + GTlsInteractionResult res; + GError *error = NULL; + + g_assert (G_IS_TLS_INTERACTION (source)); + g_assert (G_TLS_INTERACTION (source) == test->interaction); + + /* Check that this callback is being run in the right place */ + g_assert (g_thread_self () == test->interaction_thread); + + res = g_tls_interaction_request_certificate_finish (test->interaction, result, &error); + + /* Check that the results match the fixture */ + g_assert_cmpuint (test->fixture->result, ==, res); + switch (test->fixture->result) + { + case G_TLS_INTERACTION_HANDLED: + g_assert_no_error (error); + g_assert_cmpstr (g_object_get_data (G_OBJECT (test->connection), "chosen-certificate"), ==, "my-certificate"); + break; + case G_TLS_INTERACTION_FAILED: + g_assert_error (error, test->fixture->error_domain, test->fixture->error_code); + g_assert_cmpstr (error->message, ==, test->fixture->error_message); + g_clear_error (&error); + break; + case G_TLS_INTERACTION_UNHANDLED: + g_assert_no_error (error); + break; + default: + g_assert_not_reached (); + } + + /* Signal the end of the test */ + g_main_loop_quit (test->loop); +} + +static void +test_request_certificate_async (Test *test, + gconstpointer unused) +{ + /* This test only works with a main loop */ + g_assert (test->loop); + + g_tls_interaction_request_certificate_async (test->interaction, + test->connection, 0, NULL, + on_request_certificate_async_call, + test); + + /* teardown waits until g_main_loop_quit(). called from callback */ +} + +static void +test_invoke_request_certificate (Test *test, + gconstpointer unused) +{ + GTlsInteractionResult res; + GError *error = NULL; + + res = g_tls_interaction_invoke_request_certificate (test->interaction, + test->connection, + 0, NULL, &error); + + /* Check that the results match the fixture */ + g_assert_cmpuint (test->fixture->result, ==, res); + switch (test->fixture->result) + { + case G_TLS_INTERACTION_HANDLED: + g_assert_no_error (error); + g_assert_cmpstr (g_object_get_data (G_OBJECT (test->connection), "chosen-certificate"), ==, "my-certificate"); + break; + case G_TLS_INTERACTION_FAILED: + g_assert_error (error, test->fixture->error_domain, test->fixture->error_code); + g_assert_cmpstr (error->message, ==, test->fixture->error_message); + g_clear_error (&error); + break; + case G_TLS_INTERACTION_UNHANDLED: + g_assert_no_error (error); + break; + default: + g_assert_not_reached (); + } + + /* This allows teardown to stop if running with loop */ + if (test->loop) + g_main_loop_quit (test->loop); +} + +static void +test_request_certificate (Test *test, + gconstpointer unused) +{ + GTlsInteractionResult res; + GError *error = NULL; + + res = g_tls_interaction_request_certificate (test->interaction, test->connection, + 0, NULL, &error); + + /* Check that the results match the fixture */ + g_assert_cmpuint (test->fixture->result, ==, res); + switch (test->fixture->result) + { + case G_TLS_INTERACTION_HANDLED: + g_assert_no_error (error); + g_assert_cmpstr (g_object_get_data (G_OBJECT (test->connection), "chosen-certificate"), ==, "my-certificate"); + break; + case G_TLS_INTERACTION_FAILED: + g_assert_error (error, test->fixture->error_domain, test->fixture->error_code); + g_assert_cmpstr (error->message, ==, test->fixture->error_message); + g_clear_error (&error); + break; + case G_TLS_INTERACTION_UNHANDLED: + g_assert_no_error (error); + break; + default: + g_assert_not_reached (); + } + + /* This allows teardown to stop if running with loop */ + if (test->loop) + g_main_loop_quit (test->loop); +} + /* ---------------------------------------------------------------------------- * TEST SETUP */ @@ -354,6 +638,9 @@ setup_without_loop (Test *test, { const Fixture *fixture = user_data; GTlsInteractionClass *klass; + GTlsBackend *backend; + GError *error = NULL; + test->fixture = fixture; test->interaction = g_object_new (TEST_TYPE_INTERACTION, NULL); @@ -365,6 +652,14 @@ setup_without_loop (Test *test, klass->ask_password = fixture->ask_password_func; klass->ask_password_async = fixture->ask_password_async_func; klass->ask_password_finish = fixture->ask_password_finish_func; + klass->request_certificate = fixture->request_certificate_func; + klass->request_certificate_async = fixture->request_certificate_async_func; + klass->request_certificate_finish = fixture->request_certificate_finish_func; + + backend = g_object_new (G_TYPE_TEST_TLS_BACKEND, NULL); + test->connection = g_object_new (g_tls_backend_get_server_connection_type (backend), NULL); + g_assert_no_error (error); + g_object_unref (backend); test->password = g_tls_password_new (0, "Description"); test->test_thread = g_thread_self (); @@ -384,6 +679,8 @@ teardown_without_loop (Test *test, g_object_add_weak_pointer (weak_pointer, &weak_pointer); + g_object_unref (test->connection); + g_object_unref (test->password); g_object_unref (test->interaction); @@ -507,11 +804,10 @@ teardown_with_normal_loop (Test *test, typedef void (*TestFunc) (Test *test, gconstpointer data); static void -test_with_async_ask_password_implementations (const gchar *name, - TestFunc setup, - TestFunc func, - TestFunc teardown, - GPtrArray *fixtures) +test_with_async_ask_password (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown) { gchar *test_name; Fixture *fixture; @@ -541,12 +837,12 @@ test_with_async_ask_password_implementations (const gchar *name, g_free (test_name); g_ptr_array_add (fixtures, fixture); } + static void -test_with_unhandled_ask_password_implementations (const gchar *name, - TestFunc setup, - TestFunc func, - TestFunc teardown, - GPtrArray *fixtures) +test_with_unhandled_ask_password (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown) { gchar *test_name; Fixture *fixture; @@ -564,11 +860,10 @@ test_with_unhandled_ask_password_implementations (const gchar *name, } static void -test_with_sync_ask_password_implementations (const gchar *name, +test_with_sync_ask_password (const gchar *name, TestFunc setup, TestFunc func, - TestFunc teardown, - GPtrArray *fixtures) + TestFunc teardown) { gchar *test_name; Fixture *fixture; @@ -599,11 +894,122 @@ test_with_sync_ask_password_implementations (const gchar *name, g_ptr_array_add (fixtures, fixture); } +static void +test_with_all_ask_password (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown) +{ + test_with_unhandled_ask_password (name, setup, func, teardown); + test_with_async_ask_password (name, setup, func, teardown); + test_with_sync_ask_password (name, setup, func, teardown); +} + +static void +test_with_async_request_certificate (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown) +{ + gchar *test_name; + Fixture *fixture; + + /* Async implementation that succeeds */ + fixture = g_new0 (Fixture, 1); + fixture->request_certificate_async_func = test_interaction_request_certificate_async_success; + fixture->request_certificate_finish_func = test_interaction_request_certificate_finish_success; + fixture->request_certificate_func = NULL; + fixture->result = G_TLS_INTERACTION_HANDLED; + test_name = g_strdup_printf ("%s/async-implementation-success", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); + + /* Async implementation that fails */ + fixture = g_new0 (Fixture, 1); + fixture->request_certificate_async_func = test_interaction_request_certificate_async_failure; + fixture->request_certificate_finish_func = test_interaction_request_certificate_finish_failure; + fixture->request_certificate_func = NULL; + fixture->result = G_TLS_INTERACTION_FAILED; + fixture->error_domain = G_FILE_ERROR; + fixture->error_code = G_FILE_ERROR_NOENT; + fixture->error_message = "Another message"; + test_name = g_strdup_printf ("%s/async-implementation-failure", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); +} + +static void +test_with_unhandled_request_certificate (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown) +{ + gchar *test_name; + Fixture *fixture; + + /* Unhandled implementation */ + fixture = g_new0 (Fixture, 1); + fixture->request_certificate_async_func = NULL; + fixture->request_certificate_finish_func = NULL; + fixture->request_certificate_func = NULL; + fixture->result = G_TLS_INTERACTION_UNHANDLED; + test_name = g_strdup_printf ("%s/unhandled-implementation", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); +} + +static void +test_with_sync_request_certificate (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown) +{ + gchar *test_name; + Fixture *fixture; + + /* Sync implementation that succeeds */ + fixture = g_new0 (Fixture, 1); + fixture->request_certificate_async_func = NULL; + fixture->request_certificate_finish_func = NULL; + fixture->request_certificate_func = test_interaction_request_certificate_sync_success; + fixture->result = G_TLS_INTERACTION_HANDLED; + test_name = g_strdup_printf ("%s/sync-implementation-success", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); + + /* Async implementation that fails */ + fixture = g_new0 (Fixture, 1); + fixture->request_certificate_async_func = NULL; + fixture->request_certificate_finish_func = NULL; + fixture->request_certificate_func = test_interaction_request_certificate_sync_failure; + fixture->result = G_TLS_INTERACTION_FAILED; + fixture->error_domain = G_FILE_ERROR; + fixture->error_code = G_FILE_ERROR_NOENT; + fixture->error_message = "Another message"; + test_name = g_strdup_printf ("%s/sync-implementation-failure", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); +} + +static void +test_with_all_request_certificate (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown) +{ + test_with_unhandled_request_certificate (name, setup, func, teardown); + test_with_async_request_certificate (name, setup, func, teardown); + test_with_sync_request_certificate (name, setup, func, teardown); +} int main (int argc, char *argv[]) { - GPtrArray *fixtures; gint ret; g_test_init (&argc, &argv, NULL); @@ -611,52 +1017,44 @@ main (int argc, fixtures = g_ptr_array_new_with_free_func (g_free); /* Tests for g_tls_interaction_invoke_ask_password */ + test_with_all_ask_password ("/tls-interaction/ask-password/invoke-with-loop", + setup_with_thread_loop, test_invoke_ask_password, teardown_with_thread_loop); + test_with_all_ask_password ("/tls-interaction/ask-password/invoke-without-loop", + setup_without_loop, test_invoke_ask_password, teardown_without_loop); + test_with_all_ask_password ("/tls-interaction/ask-password/invoke-in-loop", + setup_with_normal_loop, test_invoke_ask_password, teardown_with_normal_loop); + + /* Tests for g_tls_interaction_ask_password */ + test_with_unhandled_ask_password ("/tls-interaction/ask-password/sync", + setup_without_loop, test_ask_password, teardown_without_loop); + test_with_sync_ask_password ("/tls-interaction/ask-password/sync", + setup_without_loop, test_ask_password, teardown_without_loop); - test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop", - setup_with_thread_loop, test_invoke_ask_password, - teardown_with_thread_loop, fixtures); - test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop", - setup_with_thread_loop, test_invoke_ask_password, - teardown_with_thread_loop, fixtures); - test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop", - setup_with_thread_loop, test_invoke_ask_password, - teardown_with_thread_loop, fixtures); - - test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop", - setup_without_loop, test_invoke_ask_password, - teardown_without_loop, fixtures); - test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop", - setup_without_loop, test_invoke_ask_password, - teardown_without_loop, fixtures); - test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop", - setup_without_loop, test_invoke_ask_password, - teardown_without_loop, fixtures); - - test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop", - setup_with_normal_loop, test_invoke_ask_password, - teardown_with_normal_loop, fixtures); - test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop", - setup_with_normal_loop, test_invoke_ask_password, - teardown_with_normal_loop, fixtures); - test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop", - setup_with_normal_loop, test_invoke_ask_password, - teardown_with_normal_loop, fixtures); + /* Tests for g_tls_interaction_ask_password_async */ + test_with_unhandled_ask_password ("/tls-interaction/ask-password/async", + setup_with_normal_loop, test_ask_password_async, teardown_with_normal_loop); + test_with_async_ask_password ("/tls-interaction/ask-password/async", + setup_with_normal_loop, test_ask_password_async, teardown_with_normal_loop); + + /* Tests for g_tls_interaction_invoke_request_certificate */ + test_with_all_request_certificate ("/tls-interaction/request-certificate/invoke-with-loop", + setup_with_thread_loop, test_invoke_request_certificate, teardown_with_thread_loop); + test_with_all_request_certificate ("/tls-interaction/request-certificate/invoke-without-loop", + setup_without_loop, test_invoke_request_certificate, teardown_without_loop); + test_with_all_request_certificate ("/tls-interaction/request-certificate/invoke-in-loop", + setup_with_normal_loop, test_invoke_request_certificate, teardown_with_normal_loop); /* Tests for g_tls_interaction_ask_password */ - test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/sync", - setup_without_loop, test_ask_password, - teardown_without_loop, fixtures); - test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/sync", - setup_without_loop, test_ask_password, - teardown_without_loop, fixtures); + test_with_unhandled_request_certificate ("/tls-interaction/request-certificate/sync", + setup_without_loop, test_request_certificate, teardown_without_loop); + test_with_sync_request_certificate ("/tls-interaction/request-certificate/sync", + setup_without_loop, test_request_certificate, teardown_without_loop); /* Tests for g_tls_interaction_ask_password_async */ - test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/async", - setup_with_normal_loop, test_ask_password_async, - teardown_with_normal_loop, fixtures); - test_with_async_ask_password_implementations ("/tls-interaction/ask-password/async", - setup_with_normal_loop, test_ask_password_async, - teardown_with_normal_loop, fixtures); + test_with_unhandled_request_certificate ("/tls-interaction/request-certificate/async", + setup_with_normal_loop, test_request_certificate_async, teardown_with_normal_loop); + test_with_async_request_certificate ("/tls-interaction/request-certificate/async", + setup_with_normal_loop, test_request_certificate_async, teardown_with_normal_loop); ret = g_test_run(); g_ptr_array_free (fixtures, TRUE); -- 2.7.4