[glib-networking] Revert "Revert "Perform certificate verification during, not after, TLS handshake""
- From: Michael Catanzaro <mcatanzaro src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib-networking] Revert "Revert "Perform certificate verification during, not after, TLS handshake""
- Date: Sun, 11 Nov 2018 23:36:23 +0000 (UTC)
commit f72f792be877656b789c86652eb301af2d556a90
Author: Michael Catanzaro <mcatanzaro igalia com>
Date: Sat Nov 10 12:26:12 2018 -0600
Revert "Revert "Perform certificate verification during, not after, TLS handshake""
This reverts commit 22c1879b773e4f9df5a1c3761b0845986b504caf.
meson.build | 2 +-
tls/gnutls/gtlsconnection-gnutls.c | 283 +++++++++++++++++++++++++++----------
2 files changed, 213 insertions(+), 72 deletions(-)
---
diff --git a/meson.build b/meson.build
index 4900d28..70180b3 100644
--- a/meson.build
+++ b/meson.build
@@ -71,7 +71,7 @@ gsettings_desktop_schemas_dep = dependency('gsettings-desktop-schemas', required
backends = []
# *** Checks for GnuTLS ***
-gnutls_dep = dependency('gnutls', version: '>= 3.4.4', required: get_option('gnutls'))
+gnutls_dep = dependency('gnutls', version: '>= 3.4.6', required: get_option('gnutls'))
if gnutls_dep.found()
backends += ['gnutls']
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index 37f40a4..a2fd4f1 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -118,12 +118,14 @@ static P11KitPin* on_pin_prompt_callback (const char *pinfile,
static void g_tls_connection_gnutls_init_priorities (void);
+static int verify_certificate_cb (gnutls_session_t session);
+
static gboolean do_implicit_handshake (GTlsConnectionGnutls *gnutls,
gint64 timeout,
GCancellable *cancellable,
GError **error);
static gboolean finish_handshake (GTlsConnectionGnutls *gnutls,
- GTask *thread_task,
+ GTask *task,
GError **error);
enum
@@ -177,8 +179,11 @@ typedef struct
GTlsCertificate *certificate, *peer_certificate;
GTlsCertificateFlags peer_certificate_errors;
- GTlsCertificate *peer_certificate_tmp;
- GTlsCertificateFlags peer_certificate_errors_tmp;
+
+ GMutex verify_certificate_mutex;
+ GCond verify_certificate_condition;
+ gboolean peer_certificate_accepted;
+ gboolean peer_certificate_examined;
gboolean require_close_notify;
GTlsRehandshakeMode rehandshake_mode;
@@ -209,6 +214,7 @@ typedef struct
*/
gboolean need_handshake, need_finish_handshake;
gboolean started_handshake, handshaking, ever_handshaked;
+ GMainContext *handshake_context;
GTask *implicit_handshake;
GError *handshake_error;
GByteArray *app_data_buf;
@@ -256,6 +262,9 @@ g_tls_connection_gnutls_init (GTlsConnectionGnutls *gnutls)
gnutls_certificate_allocate_credentials (&priv->creds);
+ g_mutex_init (&priv->verify_certificate_mutex);
+ g_cond_init (&priv->verify_certificate_condition);
+
priv->need_handshake = TRUE;
priv->database_is_unset = TRUE;
@@ -390,6 +399,9 @@ g_tls_connection_gnutls_initable_init (GInitable *initable,
gnutls_init (&priv->session, flags);
+ gnutls_session_set_ptr (priv->session, gnutls);
+ gnutls_session_set_verify_function (priv->session, verify_certificate_cb);
+
status = gnutls_credentials_set (priv->session,
GNUTLS_CRD_CERTIFICATE,
priv->creds);
@@ -450,7 +462,9 @@ g_tls_connection_gnutls_finalize (GObject *object)
g_clear_object (&priv->database);
g_clear_object (&priv->certificate);
g_clear_object (&priv->peer_certificate);
- g_clear_object (&priv->peer_certificate_tmp);
+
+ g_mutex_clear (&priv->verify_certificate_mutex);
+ g_cond_clear (&priv->verify_certificate_condition);
g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
@@ -465,6 +479,8 @@ g_tls_connection_gnutls_finalize (GObject *object)
g_clear_error (&priv->read_error);
g_clear_error (&priv->write_error);
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+
/* This must always be NULL here, as it holds a reference to @gnutls as
* its source object. However, we clear it anyway just in case this changes
* in future. */
@@ -752,6 +768,7 @@ claim_op (GTlsConnectionGnutls *gnutls,
g_mutex_unlock (&priv->op_mutex);
success = finish_handshake (gnutls, priv->implicit_handshake, &my_error);
g_clear_object (&priv->implicit_handshake);
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
g_mutex_lock (&priv->op_mutex);
if (op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH &&
@@ -1023,6 +1040,12 @@ end_gnutls_io (GTlsConnectionGnutls *gnutls,
_("TLS connection peer did not send a certificate"));
return status;
}
+ else if (status == GNUTLS_E_CERTIFICATE_ERROR)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Unacceptable TLS certificate"));
+ return status;
+ }
else if (status == GNUTLS_E_FATAL_ALERT_RECEIVED)
{
g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
@@ -1783,6 +1806,102 @@ verify_peer_certificate (GTlsConnectionGnutls *gnutls,
return errors;
}
+static gboolean
+accept_peer_certificate (GTlsConnectionGnutls *gnutls,
+ GTlsCertificate *peer_certificate,
+ GTlsCertificateFlags peer_certificate_errors)
+{
+ gboolean accepted = FALSE;
+
+ if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
+ {
+ GTlsCertificateFlags validation_flags;
+
+ if (!g_tls_connection_gnutls_is_dtls (gnutls))
+ validation_flags =
+ g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (gnutls));
+ else
+ validation_flags =
+ g_dtls_client_connection_get_validation_flags (G_DTLS_CLIENT_CONNECTION (gnutls));
+
+ if ((peer_certificate_errors & validation_flags) == 0)
+ accepted = TRUE;
+ }
+
+ if (!accepted)
+ {
+ accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls),
+ peer_certificate,
+ peer_certificate_errors);
+ }
+
+ return accepted;
+}
+
+static gboolean
+accept_certificate_cb (gpointer user_data)
+{
+ GTlsConnectionGnutls *gnutls = user_data;
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ g_assert (g_main_context_is_owner (priv->handshake_context));
+
+ g_mutex_lock (&priv->verify_certificate_mutex);
+
+ if (gnutls_certificate_type_get (priv->session) == GNUTLS_CRT_X509)
+ {
+ priv->peer_certificate = get_peer_certificate_from_session (gnutls);
+ if (priv->peer_certificate)
+ priv->peer_certificate_errors = verify_peer_certificate (gnutls, priv->peer_certificate);
+ }
+
+ priv->peer_certificate_accepted = accept_peer_certificate (gnutls,
+ priv->peer_certificate,
+ priv->peer_certificate_errors);
+
+ priv->peer_certificate_examined = TRUE;
+
+ g_cond_signal (&priv->verify_certificate_condition);
+ g_mutex_unlock (&priv->verify_certificate_mutex);
+
+ g_object_notify (G_OBJECT (gnutls), "peer-certificate");
+ g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
+
+ return G_SOURCE_REMOVE;
+}
+
+static int
+verify_certificate_cb (gnutls_session_t session)
+{
+ GTlsConnectionGnutls *gnutls = gnutls_session_get_ptr (session);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ gboolean accepted;
+
+ g_mutex_lock (&priv->verify_certificate_mutex);
+ priv->peer_certificate_examined = FALSE;
+ priv->peer_certificate_accepted = FALSE;
+ g_mutex_unlock (&priv->verify_certificate_mutex);
+
+ /* Invoke the callback on the handshake context's thread. This is
+ * necessary because we need to ensure the accept-certificate signal
+ * is emitted on the original thread.
+ */
+ g_assert (priv->handshake_context);
+ g_main_context_invoke (priv->handshake_context, accept_certificate_cb, gnutls);
+
+ /* We'll block the handshake thread until the original thread has
+ * decided whether to accept the certificate.
+ */
+ g_mutex_lock (&priv->verify_certificate_mutex);
+ while (!priv->peer_certificate_examined)
+ g_cond_wait (&priv->verify_certificate_condition, &priv->verify_certificate_mutex);
+ accepted = priv->peer_certificate_accepted;
+ g_mutex_unlock (&priv->verify_certificate_mutex);
+
+ /* Return 0 for the handshake to continue, non-zero to terminate. */
+ return !accepted;
+}
+
static void
handshake_thread (GTask *task,
gpointer object,
@@ -1900,16 +2019,10 @@ handshake_thread (GTask *task,
END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret,
_("Error performing TLS handshake"), &error);
- if (ret == 0 && gnutls_certificate_type_get (priv->session) == GNUTLS_CRT_X509)
+ if (G_IS_TLS_CLIENT_CONNECTION (gnutls) && !priv->peer_certificate && !error)
{
- priv->peer_certificate_tmp = get_peer_certificate_from_session (gnutls);
- if (priv->peer_certificate_tmp)
- priv->peer_certificate_errors_tmp = verify_peer_certificate (gnutls, priv->peer_certificate_tmp);
- else if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
- {
- g_set_error_literal (&error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Server did not return a valid TLS certificate"));
- }
+ g_set_error_literal (&error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Server did not return a valid TLS certificate"));
}
/* This calls the finish_handshake code of GTlsClientConnectionGnutls
@@ -1930,38 +2043,6 @@ handshake_thread (GTask *task,
}
}
-static gboolean
-accept_peer_certificate (GTlsConnectionGnutls *gnutls,
- GTlsCertificate *peer_certificate,
- GTlsCertificateFlags peer_certificate_errors)
-{
- gboolean accepted = FALSE;
-
- if (G_IS_TLS_CLIENT_CONNECTION (gnutls))
- {
- GTlsCertificateFlags validation_flags;
-
- if (!g_tls_connection_gnutls_is_dtls (gnutls))
- validation_flags =
- g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (gnutls));
- else
- validation_flags =
- g_dtls_client_connection_get_validation_flags (G_DTLS_CLIENT_CONNECTION (gnutls));
-
- if ((peer_certificate_errors & validation_flags) == 0)
- accepted = TRUE;
- }
-
- if (!accepted)
- {
- accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls),
- peer_certificate,
- peer_certificate_errors);
- }
-
- return accepted;
-}
-
static void
begin_handshake (GTlsConnectionGnutls *gnutls)
{
@@ -1974,29 +2055,13 @@ finish_handshake (GTlsConnectionGnutls *gnutls,
GError **error)
{
GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsCertificate *peer_certificate;
- GTlsCertificateFlags peer_certificate_errors;
-
g_assert (error != NULL);
- peer_certificate = priv->peer_certificate_tmp;
- priv->peer_certificate_tmp = NULL;
- peer_certificate_errors = priv->peer_certificate_errors_tmp;
- priv->peer_certificate_errors_tmp = 0;
-
- if (g_task_propagate_boolean (task, error) && peer_certificate)
+ if (g_task_propagate_boolean (task, error) &&
+ priv->peer_certificate && !priv->peer_certificate_accepted)
{
- if (!accept_peer_certificate (gnutls, peer_certificate,
- peer_certificate_errors))
- {
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Unacceptable TLS certificate"));
- }
-
- priv->peer_certificate = peer_certificate;
- priv->peer_certificate_errors = peer_certificate_errors;
- g_object_notify (G_OBJECT (gnutls), "peer-certificate");
- g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors");
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Unacceptable TLS certificate"));
}
if (*error && priv->started_handshake)
@@ -2005,26 +2070,77 @@ finish_handshake (GTlsConnectionGnutls *gnutls,
return (*error == NULL);
}
+static void
+sync_handshake_thread_completed (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ g_assert (g_main_context_is_owner (priv->handshake_context));
+
+ g_mutex_lock (&priv->op_mutex);
+ priv->need_finish_handshake = TRUE;
+ g_mutex_unlock (&priv->op_mutex);
+
+ g_main_context_wakeup (priv->handshake_context);
+}
+
+static void
+crank_sync_handshake_context (GTlsConnectionGnutls *gnutls,
+ GCancellable *cancellable)
+{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+
+ /* need_finish_handshake will be set inside sync_handshake_thread_completed(),
+ * which should only ever be invoked while iterating the handshake context
+ * here. So need_finish_handshake should only change on this thread.
+ */
+ g_mutex_lock (&priv->op_mutex);
+ priv->need_finish_handshake = FALSE;
+ while (!priv->need_finish_handshake && !g_cancellable_is_cancelled (cancellable))
+ {
+ g_mutex_unlock (&priv->op_mutex);
+ g_main_context_iteration (priv->handshake_context, TRUE);
+ g_mutex_lock (&priv->op_mutex);
+ }
+ g_mutex_unlock (&priv->op_mutex);
+}
+
static gboolean
g_tls_connection_gnutls_handshake (GTlsConnection *conn,
GCancellable *cancellable,
GError **error)
{
GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn);
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
GTask *task;
gboolean success;
gint64 *timeout = NULL;
GError *my_error = NULL;
- task = g_task_new (conn, cancellable, NULL, NULL);
+ g_assert (priv->handshake_context == NULL);
+ priv->handshake_context = g_main_context_new ();
+
+ g_main_context_push_thread_default (priv->handshake_context);
+
+ begin_handshake (gnutls);
+
+ task = g_task_new (conn, cancellable, sync_handshake_thread_completed, NULL);
g_task_set_source_tag (task, g_tls_connection_gnutls_handshake);
+ g_task_set_return_on_cancel (task, TRUE);
timeout = g_new0 (gint64, 1);
*timeout = -1; /* blocking */
g_task_set_task_data (task, timeout, g_free);
- begin_handshake (gnutls);
- g_task_run_in_thread_sync (task, handshake_thread);
+ g_task_run_in_thread (task, handshake_thread);
+ crank_sync_handshake_context (gnutls, cancellable);
+
+ g_main_context_pop_thread_default (priv->handshake_context);
+
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
success = finish_handshake (gnutls, task, &my_error);
g_object_unref (task);
@@ -2060,6 +2176,8 @@ handshake_thread_completed (GObject *object,
GError *error = NULL;
gboolean need_finish_handshake, success;
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+
g_mutex_lock (&priv->op_mutex);
if (priv->need_finish_handshake)
{
@@ -2116,9 +2234,13 @@ g_tls_connection_gnutls_handshake_async (GTlsConnection *conn,
GAsyncReadyCallback callback,
gpointer user_data)
{
+ GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (G_TLS_CONNECTION_GNUTLS
(conn));
GTask *thread_task, *caller_task;
gint64 *timeout = NULL;
+ g_assert (!priv->handshake_context);
+ priv->handshake_context = g_main_context_ref_thread_default ();
+
caller_task = g_task_new (conn, cancellable, callback, user_data);
g_task_set_source_tag (caller_task, g_tls_connection_gnutls_handshake_async);
g_task_set_priority (caller_task, io_priority);
@@ -2179,8 +2301,21 @@ do_implicit_handshake (GTlsConnectionGnutls *gnutls,
/* We have op_mutex */
+ g_assert (priv->handshake_context == NULL);
+ if (timeout != 0)
+ {
+ priv->handshake_context = g_main_context_new ();
+ g_main_context_push_thread_default (priv->handshake_context);
+ }
+ else
+ {
+ priv->handshake_context = g_main_context_ref_thread_default ();
+ }
+
g_assert (priv->implicit_handshake == NULL);
- priv->implicit_handshake = g_task_new (gnutls, cancellable, NULL, NULL);
+ priv->implicit_handshake = g_task_new (gnutls, cancellable,
+ timeout ? sync_handshake_thread_completed : NULL,
+ NULL);
g_task_set_source_tag (priv->implicit_handshake,
do_implicit_handshake);
@@ -2204,8 +2339,14 @@ do_implicit_handshake (GTlsConnectionGnutls *gnutls,
g_mutex_unlock (&priv->op_mutex);
- g_task_run_in_thread_sync (priv->implicit_handshake,
- handshake_thread);
+ g_task_set_return_on_cancel (priv->implicit_handshake, TRUE);
+ g_task_run_in_thread (priv->implicit_handshake, handshake_thread);
+ crank_sync_handshake_context (gnutls, cancellable);
+
+ g_main_context_pop_thread_default (priv->handshake_context);
+
+ g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+
success = finish_handshake (gnutls,
priv->implicit_handshake,
&my_error);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]