[glib-networking/mcatanzaro/tls-thread: 1/2] Move TLS operations to separate op thread
- From: Michael Catanzaro <mcatanzaro src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib-networking/mcatanzaro/tls-thread: 1/2] Move TLS operations to separate op thread
- Date: Thu, 9 Jan 2020 01:23:37 +0000 (UTC)
commit 8bf7f9af9da21d1807a7b90f22a809bdc1557a3c
Author: Michael Catanzaro <mcatanzaro gnome org>
Date: Fri Nov 29 18:14:21 2019 -0600
Move TLS operations to separate op thread
The current OpenSSL backend does not maintain the threadsafety
guarantees promised by GIOStream. We need to guarantee that
GTlsConnection methods can be called on two threads simultaneously: a
reader thread and a writer thread. Until 2017, the OpenSSL documentation
incorrectly implied that it was safe to use the SSL object on multiple
threads at the same time, and the previous code assumed this. But it was
never true.
This will also make it dramatically easier to fix four entirely
unrelated issues in the future, as discussed in #89.
To simplify the implementation, this commit removes support for TLS
rehandshakes, which is desirable to do regardless because rehandshaking
is no longer supported in TLS 1.3, and our API documentation already says
the behavior is undefined if TLS 1.3 is in use, "except it is guaranteed
to be reasonable and nondestructive." Doing nothing is best.
Fixes #89
meson.build | 2 +-
tls/base/gtlsconnection-base.c | 1186 ++++++-------------
tls/base/gtlsconnection-base.h | 140 +--
tls/base/gtlsoperationsthread-base.c | 1704 ++++++++++++++++++++++++++++
tls/base/gtlsoperationsthread-base.h | 207 ++++
tls/base/meson.build | 13 +-
tls/gnutls/gtlsbackend-gnutls.c | 2 +
tls/gnutls/gtlscertificate-gnutls.c | 20 +-
tls/gnutls/gtlscertificate-gnutls.h | 6 +-
tls/gnutls/gtlsclientconnection-gnutls.c | 379 +------
tls/gnutls/gtlsconnection-gnutls.c | 1117 +-----------------
tls/gnutls/gtlsconnection-gnutls.h | 9 -
tls/gnutls/gtlsoperationsthread-gnutls.c | 1458 ++++++++++++++++++++++++
tls/gnutls/gtlsoperationsthread-gnutls.h | 44 +
tls/gnutls/gtlsserverconnection-gnutls.c | 96 +-
tls/gnutls/meson.build | 1 +
tls/openssl/gtlsbio.c | 170 +--
tls/openssl/gtlsbio.h | 16 +-
tls/openssl/gtlsclientconnection-openssl.c | 319 ++----
tls/openssl/gtlsconnection-openssl.c | 527 +--------
tls/openssl/gtlsconnection-openssl.h | 6 -
tls/openssl/gtlsoperationsthread-openssl.c | 877 ++++++++++++++
tls/openssl/gtlsoperationsthread-openssl.h | 42 +
tls/openssl/gtlsserverconnection-openssl.c | 363 +-----
tls/openssl/meson.build | 1 +
tls/tests/connection.c | 84 +-
26 files changed, 4907 insertions(+), 3882 deletions(-)
---
diff --git a/meson.build b/meson.build
index 6e3a1ae..44d6c14 100644
--- a/meson.build
+++ b/meson.build
@@ -76,7 +76,7 @@ gsettings_desktop_schemas_dep = dependency('gsettings-desktop-schemas', required
backends = []
# *** Checks for GnuTLS ***
-gnutls_dep = dependency('gnutls', version: '>= 3.6.5', required: get_option('gnutls'))
+gnutls_dep = dependency('gnutls', version: '>= 3.6.7', required: get_option('gnutls'))
if gnutls_dep.found()
backends += ['gnutls']
diff --git a/tls/base/gtlsconnection-base.c b/tls/base/gtlsconnection-base.c
index 10658ad..9d5cc36 100644
--- a/tls/base/gtlsconnection-base.c
+++ b/tls/base/gtlsconnection-base.c
@@ -3,6 +3,8 @@
* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2009-2011 Red Hat, Inc
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -30,6 +32,7 @@
#include "gtlsconnection-base.h"
#include "gtlsinputstream.h"
#include "gtlslog.h"
+#include "gtlsoperationsthread-base.h"
#include "gtlsoutputstream.h"
#include <glib/gi18n-lib.h>
@@ -52,7 +55,7 @@
* communications.
* • Implements GDtlsConnection and GDatagramBased, for DTLS and datagram
* communications.
- * • Implements GInitable for failable initialisation.
+ * • Implements GInitable for failable initialization.
*/
typedef struct
@@ -88,16 +91,9 @@ typedef struct
GTlsInteraction *interaction;
GTlsCertificate *certificate;
- gboolean missing_requested_client_certificate;
- GError *interaction_error;
GTlsCertificate *peer_certificate;
GTlsCertificateFlags peer_certificate_errors;
- GMutex verify_certificate_mutex;
- GCond verify_certificate_condition;
- gboolean peer_certificate_accepted;
- gboolean peer_certificate_examined;
-
gboolean require_close_notify;
GTlsRehandshakeMode rehandshake_mode;
@@ -122,16 +118,15 @@ typedef struct
* future operations). ever_handshaked indicates that TLS has been
* successfully negotiated at some point.
*/
+ /* FIXME: remove a few of these */
gboolean need_handshake;
gboolean need_finish_handshake;
- gboolean sync_handshake_in_progress;
gboolean started_handshake;
gboolean handshaking;
gboolean ever_handshaked;
- GMainContext *handshake_context;
- GTask *implicit_handshake;
+ gboolean peer_certificate_accepted;
+ GTask *async_implicit_handshake;
GError *handshake_error;
- GByteArray *app_data_buf;
/* read_closed means the read direction has closed; write_closed similarly.
* If (and only if) both are set, the entire GTlsConnection is closed. */
@@ -139,16 +134,7 @@ typedef struct
gboolean write_closing, write_closed;
gboolean reading;
- gint64 read_timeout;
- GError *read_error;
- GCancellable *read_cancellable;
-
gboolean writing;
- gint64 write_timeout;
- GError *write_error;
- GCancellable *write_cancellable;
-
- gboolean successful_posthandshake_op;
gboolean is_system_certdb;
gboolean database_is_unset;
@@ -158,19 +144,23 @@ typedef struct
gchar **advertised_protocols;
gchar *negotiated_protocol;
+
+ GTlsOperationsThreadBase *thread;
} GTlsConnectionBasePrivate;
static void g_tls_connection_base_dtls_connection_iface_init (GDtlsConnectionInterface *iface);
static void g_tls_connection_base_datagram_based_iface_init (GDatagramBasedInterface *iface);
+static void g_tls_connection_base_initable_iface_init (GInitableIface *iface);
+
static gboolean do_implicit_handshake (GTlsConnectionBase *tls,
gint64 timeout,
GCancellable *cancellable,
GError **error);
static gboolean finish_handshake (GTlsConnectionBase *tls,
- GTask *task,
+ gboolean success,
GError **error);
static void g_tls_connection_base_handshake_async (GTlsConnection *conn,
@@ -189,6 +179,8 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionBase, g_tls_connection_base, G_T
g_tls_connection_base_datagram_based_iface_init);
G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_CONNECTION,
g_tls_connection_base_dtls_connection_iface_init);
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ g_tls_connection_base_initable_iface_init);
);
@@ -211,14 +203,40 @@ enum
PROP_NEGOTIATED_PROTOCOL,
};
-gboolean
-g_tls_connection_base_is_dtls (GTlsConnectionBase *tls)
+static gboolean
+is_dtls (GTlsConnectionBase *tls)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
return priv->base_socket != NULL;
}
+static GTlsInteractionResult
+operations_thread_request_certificate_cb (GTlsOperationsThreadBase *thread,
+ GTlsInteraction *interaction,
+ GTlsCertificate **own_certificate,
+ GCancellable *cancellable,
+ GError **error,
+ GTlsConnectionBase *tls)
+{
+ GTlsInteractionResult result = G_TLS_INTERACTION_UNHANDLED;
+
+ /* Careful! This is emitted on the op thread. */
+
+ if (interaction)
+ {
+ result = g_tls_interaction_invoke_request_certificate (interaction,
+ G_TLS_CONNECTION (tls),
+ 0,
+ cancellable,
+ error);
+ }
+
+ *own_certificate = g_tls_connection_get_certificate (G_TLS_CONNECTION (tls));
+
+ return result;
+}
+
static void
g_tls_connection_base_init (GTlsConnectionBase *tls)
{
@@ -228,20 +246,44 @@ g_tls_connection_base_init (GTlsConnectionBase *tls)
priv->database_is_unset = TRUE;
priv->is_system_certdb = TRUE;
- g_mutex_init (&priv->verify_certificate_mutex);
- g_cond_init (&priv->verify_certificate_condition);
-
g_mutex_init (&priv->op_mutex);
priv->waiting_for_op = g_cancellable_new ();
}
+static gboolean
+g_tls_connection_base_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (initable);
+ GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+
+ priv->thread = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->create_op_thread (tls);
+ if (!priv->thread)
+ return FALSE;
+
+ if (priv->interaction)
+ g_tls_operations_thread_base_set_interaction (priv->thread, priv->interaction);
+
+ g_tls_operations_thread_base_set_close_notify_required (priv->thread,
+ priv->require_close_notify);
+
+ g_signal_connect_object (priv->thread, "operations-thread-request-certificate",
+ G_CALLBACK (operations_thread_request_certificate_cb),
+ tls, 0);
+
+ return TRUE;
+}
+
static void
g_tls_connection_base_finalize (GObject *object)
{
GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (object);
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+ g_clear_object (&priv->thread);
+
g_clear_object (&priv->base_io_stream);
g_clear_object (&priv->base_socket);
@@ -250,32 +292,20 @@ g_tls_connection_base_finalize (GObject *object)
g_clear_object (&priv->database);
g_clear_object (&priv->certificate);
- g_clear_error (&priv->interaction_error);
g_clear_object (&priv->peer_certificate);
- g_mutex_clear (&priv->verify_certificate_mutex);
- g_cond_clear (&priv->verify_certificate_condition);
-
g_clear_object (&priv->interaction);
- g_clear_pointer (&priv->handshake_context, g_main_context_unref);
-
/* This must always be NULL at this point, as it holds a reference to @tls as
* its source object. However, we clear it anyway just in case this changes
* in future. */
- g_clear_object (&priv->implicit_handshake);
+ g_clear_object (&priv->async_implicit_handshake);
g_clear_error (&priv->handshake_error);
- g_clear_error (&priv->read_error);
- g_clear_error (&priv->write_error);
- g_clear_object (&priv->read_cancellable);
- g_clear_object (&priv->write_cancellable);
g_clear_object (&priv->waiting_for_op);
g_mutex_clear (&priv->op_mutex);
- g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
-
g_clear_pointer (&priv->advertised_protocols, g_strfreev);
g_clear_pointer (&priv->negotiated_protocol, g_free);
@@ -407,6 +437,10 @@ g_tls_connection_base_set_property (GObject *object,
case PROP_REQUIRE_CLOSE_NOTIFY:
priv->require_close_notify = g_value_get_boolean (value);
+
+ if (priv->thread)
+ g_tls_operations_thread_base_set_close_notify_required (priv->thread,
+ priv->require_close_notify);
break;
case PROP_REHANDSHAKE_MODE:
@@ -444,6 +478,10 @@ g_tls_connection_base_set_property (GObject *object,
case PROP_INTERACTION:
g_clear_object (&priv->interaction);
priv->interaction = g_value_dup_object (value);
+
+ if (priv->thread)
+ g_tls_operations_thread_base_set_interaction (priv->thread,
+ priv->interaction);
break;
case PROP_ADVERTISED_PROTOCOLS:
@@ -487,21 +525,19 @@ op_to_string (GTlsConnectionBaseOp op)
}
static const gchar *
-status_to_string (GTlsConnectionBaseStatus st)
+status_to_string (GTlsOperationStatus status)
{
- switch (st)
+ switch (status)
{
- case G_TLS_CONNECTION_BASE_OK:
- return "BASE_OK";
- case G_TLS_CONNECTION_BASE_WOULD_BLOCK:
+ case G_TLS_OPERATION_SUCCESS:
+ return "SUCCESS";
+ case G_TLS_OPERATION_WOULD_BLOCK:
return "WOULD_BLOCK";
- case G_TLS_CONNECTION_BASE_TIMED_OUT:
+ case G_TLS_OPERATION_TIMED_OUT:
return "TIMED_OUT";
- case G_TLS_CONNECTION_BASE_REHANDSHAKE:
- return "REHANDSHAKE";
- case G_TLS_CONNECTION_BASE_TRY_AGAIN:
+ case G_TLS_OPERATION_TRY_AGAIN:
return "TRY_AGAIN";
- case G_TLS_CONNECTION_BASE_ERROR:
+ case G_TLS_OPERATION_ERROR:
return "ERROR";
}
g_assert_not_reached ();
@@ -569,8 +605,9 @@ claim_op (GTlsConnectionBase *tls,
}
}
+ /* Performed async implicit handshake? */
if (priv->need_finish_handshake &&
- priv->implicit_handshake)
+ priv->async_implicit_handshake)
{
GError *my_error = NULL;
gboolean success;
@@ -578,9 +615,13 @@ claim_op (GTlsConnectionBase *tls,
priv->need_finish_handshake = FALSE;
g_mutex_unlock (&priv->op_mutex);
- success = finish_handshake (tls, priv->implicit_handshake, &my_error);
- g_clear_object (&priv->implicit_handshake);
- g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+
+ success = g_task_propagate_boolean (priv->async_implicit_handshake, &my_error);
+ g_clear_object (&priv->async_implicit_handshake);
+
+ /* If we already have an error, ignore further errors. */
+ success = finish_handshake (tls, success, my_error ? NULL : &my_error);
+
g_mutex_lock (&priv->op_mutex);
if (op != G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH &&
@@ -590,7 +631,7 @@ claim_op (GTlsConnectionBase *tls,
{
g_propagate_error (error, my_error);
g_mutex_unlock (&priv->op_mutex);
- g_tls_log_debug (tls, "claim_op failed: finish_handshake failed or operation has been
cancelled");
+ g_tls_log_debug (tls, "claim_op failed: finish_handshake failed");
return FALSE;
}
@@ -598,7 +639,10 @@ claim_op (GTlsConnectionBase *tls,
}
}
+ /* FIXME: store a GThread member to bring back this check */
+#if 0
if (priv->handshaking &&
+ op != G_TLS_CONNECTION_BASE_OP_HANDSHAKE &&
timeout != 0 &&
g_main_context_is_owner (priv->handshake_context))
{
@@ -609,11 +653,12 @@ claim_op (GTlsConnectionBase *tls,
* the handshake (forever, if there's no timeout). Even a close
* op would deadlock here.
*/
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot perform blocking operation during
TLS handshake"));
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, _("Cannot perform blocking operation
during TLS handshake"));
g_mutex_unlock (&priv->op_mutex);
g_tls_log_debug (tls, "claim_op failed: %s", (*error)->message);
return FALSE;
}
+#endif
if ((op != G_TLS_CONNECTION_BASE_OP_WRITE && priv->reading) ||
(op != G_TLS_CONNECTION_BASE_OP_READ && priv->writing) ||
@@ -631,7 +676,8 @@ claim_op (GTlsConnectionBase *tls,
if (timeout == 0)
{
/* Intentionally not translated because this is not a fatal error to be
- * presented to the user, and to avoid this showing up in profiling. */
+ * presented to the user, and to avoid this showing up in profiling.
+ */
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
g_tls_log_debug (tls, "claim_op failed: %s", (*error)->message);
return FALSE;
@@ -702,9 +748,9 @@ claim_op (GTlsConnectionBase *tls,
}
static void
-yield_op (GTlsConnectionBase *tls,
- GTlsConnectionBaseOp op,
- GTlsConnectionBaseStatus status)
+yield_op (GTlsConnectionBase *tls,
+ GTlsConnectionBaseOp op,
+ GTlsOperationStatus status)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
@@ -714,8 +760,6 @@ yield_op (GTlsConnectionBase *tls,
if (op == G_TLS_CONNECTION_BASE_OP_HANDSHAKE)
priv->handshaking = FALSE;
- else if (status == G_TLS_CONNECTION_BASE_REHANDSHAKE && !priv->handshaking)
- priv->need_handshake = TRUE;
if (op == G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH ||
op == G_TLS_CONNECTION_BASE_OP_CLOSE_READ)
@@ -733,150 +777,16 @@ yield_op (GTlsConnectionBase *tls,
g_mutex_unlock (&priv->op_mutex);
}
-static void
-g_tls_connection_base_real_push_io (GTlsConnectionBase *tls,
- GIOCondition direction,
- gint64 timeout,
- GCancellable *cancellable)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- if (direction & G_IO_IN)
- {
- priv->read_timeout = timeout;;
- priv->read_cancellable = cancellable;
- g_clear_error (&priv->read_error);
- }
-
- if (direction & G_IO_OUT)
- {
- priv->write_timeout = timeout;
- priv->write_cancellable = cancellable;
- g_clear_error (&priv->write_error);
- }
-}
-
-void
-g_tls_connection_base_push_io (GTlsConnectionBase *tls,
- GIOCondition direction,
- gint64 timeout,
- GCancellable *cancellable)
-{
- g_assert (direction & (G_IO_IN | G_IO_OUT));
- g_return_if_fail (G_IS_TLS_CONNECTION_BASE (tls));
-
- G_TLS_CONNECTION_BASE_GET_CLASS (tls)->push_io (tls, direction,
- timeout, cancellable);
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_base_real_pop_io (GTlsConnectionBase *tls,
- GIOCondition direction,
- gboolean success,
- GError **error)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GError *my_error = NULL;
-
- /* This function MAY or MAY NOT set error when it fails! */
-
- if (direction & G_IO_IN)
- {
- priv->read_cancellable = NULL;
- if (!success)
- {
- my_error = priv->read_error;
- priv->read_error = NULL;
- }
- else
- g_clear_error (&priv->read_error);
- }
-
- if (direction & G_IO_OUT)
- {
- priv->write_cancellable = NULL;
- if (!success && !my_error)
- {
- my_error = priv->write_error;
- priv->write_error = NULL;
- }
- else
- g_clear_error (&priv->write_error);
- }
-
- if (success)
- return G_TLS_CONNECTION_BASE_OK;
-
- if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
- {
- g_propagate_error (error, my_error);
- return G_TLS_CONNECTION_BASE_WOULD_BLOCK;
- }
-
- if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
- {
- g_propagate_error (error, my_error);
- return G_TLS_CONNECTION_BASE_TIMED_OUT;
- }
-
- if (priv->missing_requested_client_certificate &&
- !priv->successful_posthandshake_op)
- {
- g_assert (G_IS_TLS_CLIENT_CONNECTION (tls));
-
- /* Probably the server requires a client certificate, but we failed to
- * provide one. With TLS 1.3 the server is no longer able to tell us
- * this, so we just have to guess. If there is an error from the TLS
- * interaction (request for user certificate), we provide that. Otherwise,
- * guess that G_TLS_ERROR_CERTIFICATE_REQUIRED is probably appropriate.
- * This could be wrong, but only applies to the small minority of
- * connections where a client cert is requested but not provided, and then
- * then only if the client has never successfully read or written.
- */
- if (priv->interaction_error)
- {
- g_propagate_error (error, priv->interaction_error);
- priv->interaction_error = NULL;
- }
- else
- {
- g_clear_error (error);
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
- _("Server required TLS certificate"));
- }
- g_clear_error (&my_error);
- }
- else if (my_error)
- {
- g_propagate_error (error, my_error);
- }
-
- return G_TLS_CONNECTION_BASE_ERROR;
-}
-
-GTlsConnectionBaseStatus
-g_tls_connection_base_pop_io (GTlsConnectionBase *tls,
- GIOCondition direction,
- gboolean success,
- GError **error)
-{
- g_assert (direction & (G_IO_IN | G_IO_OUT));
- g_assert (!error || !*error);
- g_return_val_if_fail (G_IS_TLS_CONNECTION_BASE (tls), G_TLS_CONNECTION_BASE_ERROR);
-
- return G_TLS_CONNECTION_BASE_GET_CLASS (tls)->pop_io (tls, direction,
- success, error);
-}
-
/* Checks whether the underlying base stream or GDatagramBased meets
- * @condition. */
-gboolean
-g_tls_connection_base_base_check (GTlsConnectionBase *tls,
- GIOCondition condition)
+ * @condition.
+ */
+static gboolean
+base_check (GTlsConnectionBase *tls,
+ GIOCondition condition)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- if (g_tls_connection_base_is_dtls (tls))
+ if (is_dtls (tls))
return g_datagram_based_condition_check (priv->base_socket, condition);
if (condition & G_IO_IN)
@@ -890,7 +800,8 @@ g_tls_connection_base_base_check (GTlsConnectionBase *tls,
}
/* Checks whether the (D)TLS stream meets @condition; not the underlying base
- * stream or GDatagramBased. */
+ * stream or GDatagramBased.
+ */
gboolean
g_tls_connection_base_check (GTlsConnectionBase *tls,
GIOCondition condition)
@@ -912,7 +823,7 @@ g_tls_connection_base_check (GTlsConnectionBase *tls,
return FALSE;
/* Defer to the base stream or GDatagramBased. */
- return g_tls_connection_base_base_check (tls, condition);
+ return base_check (tls, condition);
}
typedef struct {
@@ -934,7 +845,8 @@ typedef struct {
/* Use a custom dummy callback instead of g_source_set_dummy_callback(), as that
* uses a GClosure and is slow. (The GClosure is necessary to deal with any
- * function prototype.) */
+ * function prototype.)
+ */
static gboolean
dummy_callback (gpointer data)
{
@@ -1076,7 +988,6 @@ g_tls_connection_tls_source_dtls_closure_callback (GDatagramBased *datagram_base
g_value_unset (¶m[1]);
return result;
-
}
static GSourceFuncs tls_source_funcs =
@@ -1108,7 +1019,7 @@ g_tls_connection_base_create_source (GTlsConnectionBase *tls,
GSource *source, *cancellable_source;
GTlsConnectionBaseSource *tls_source;
- if (g_tls_connection_base_is_dtls (tls))
+ if (is_dtls (tls))
{
source = g_source_new (&dtls_source_funcs,
sizeof (GTlsConnectionBaseSource));
@@ -1122,7 +1033,7 @@ g_tls_connection_base_create_source (GTlsConnectionBase *tls,
tls_source = (GTlsConnectionBaseSource *)source;
tls_source->tls = g_object_ref (tls);
tls_source->condition = condition;
- if (g_tls_connection_base_is_dtls (tls))
+ if (is_dtls (tls))
tls_source->base = G_OBJECT (tls);
else if (priv->tls_istream && condition & G_IO_IN)
tls_source->base = G_OBJECT (priv->tls_istream);
@@ -1158,7 +1069,7 @@ g_tls_connection_base_dtls_create_source (GDatagramBased *datagram_based,
static GIOCondition
g_tls_connection_base_condition_check (GDatagramBased *datagram_based,
- GIOCondition condition)
+ GIOCondition condition)
{
GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (datagram_based);
@@ -1184,7 +1095,7 @@ g_tls_connection_base_condition_wait (GDatagramBased *datagram_based,
/* Convert from microseconds to milliseconds. */
if (timeout != -1)
- timeout = timeout / 1000;
+ timeout /= 1000;
start_time = g_get_monotonic_time ();
@@ -1237,7 +1148,7 @@ verify_peer_certificate (GTlsConnectionBase *tls,
if (!is_client)
peer_identity = NULL;
- else if (!g_tls_connection_base_is_dtls (tls))
+ else if (!is_dtls (tls))
peer_identity = g_tls_client_connection_get_server_identity (G_TLS_CLIENT_CONNECTION (tls));
else
peer_identity = g_dtls_client_connection_get_server_identity (G_DTLS_CLIENT_CONNECTION (tls));
@@ -1273,337 +1184,198 @@ verify_peer_certificate (GTlsConnectionBase *tls,
return errors;
}
-static void
-update_peer_certificate_and_compute_errors (GTlsConnectionBase *tls)
+static gboolean
+verify_certificate_cb (GTlsOperationsThreadBase *thread,
+ GTlsCertificate *peer_certificate,
+ GTlsConnectionBase *tls)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsCertificate *peer_certificate = NULL;
- GTlsCertificateFlags peer_certificate_errors = 0;
+ gboolean accepted = FALSE;
- /* This function must be called from the handshake context thread
- * (probably the main thread, NOT the handshake thread) because
- * it emits notifies that are application-visible.
- *
- * verify_certificate_mutex should be locked.
+ /* FIXME: when doing async handshake as sync-on-a-thread, this function will
+ * be called from the handshake thread, which is unsafe. The code assumes
+ * it is used from the main thread.
+ * FIXME: eliminate handshake context.
*/
- g_assert (priv->handshake_context);
- g_assert (g_main_context_is_owner (priv->handshake_context));
- peer_certificate = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->retrieve_peer_certificate (tls);
- if (peer_certificate)
- peer_certificate_errors = verify_peer_certificate (tls, peer_certificate);
+ g_assert (G_IS_TLS_CERTIFICATE (peer_certificate));
g_set_object (&priv->peer_certificate, peer_certificate);
- g_clear_object (&peer_certificate);
-
- priv->peer_certificate_errors = peer_certificate_errors;
+ priv->peer_certificate_errors = verify_peer_certificate (tls, peer_certificate);
g_object_notify (G_OBJECT (tls), "peer-certificate");
g_object_notify (G_OBJECT (tls), "peer-certificate-errors");
-}
-static gboolean
-accept_or_reject_peer_certificate (gpointer user_data)
-{
- GTlsConnectionBase *tls = user_data;
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- gboolean accepted = FALSE;
-
- g_assert (g_main_context_is_owner (priv->handshake_context));
-
- g_mutex_lock (&priv->verify_certificate_mutex);
-
- update_peer_certificate_and_compute_errors (tls);
-
- if (priv->peer_certificate)
+ if (G_IS_TLS_CLIENT_CONNECTION (tls))
{
if (G_IS_TLS_CLIENT_CONNECTION (tls))
{
GTlsCertificateFlags validation_flags;
- if (!g_tls_connection_base_is_dtls (tls))
- validation_flags =
- g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (tls));
- else
- validation_flags =
- g_dtls_client_connection_get_validation_flags (G_DTLS_CLIENT_CONNECTION (tls));
+ if (!is_dtls (tls))
+ validation_flags =
+ g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (tls));
+ else
+ validation_flags =
+ g_dtls_client_connection_get_validation_flags (G_DTLS_CLIENT_CONNECTION (tls));
if ((priv->peer_certificate_errors & validation_flags) == 0)
accepted = TRUE;
}
-
- if (!accepted)
- {
- gboolean sync_handshake_in_progress;
-
- g_mutex_lock (&priv->op_mutex);
- sync_handshake_in_progress = priv->sync_handshake_in_progress;
- g_mutex_unlock (&priv->op_mutex);
-
- if (sync_handshake_in_progress)
- g_main_context_pop_thread_default (priv->handshake_context);
-
- accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (tls),
- priv->peer_certificate,
- priv->peer_certificate_errors);
-
- if (sync_handshake_in_progress)
- g_main_context_push_thread_default (priv->handshake_context);
- }
}
- else if (G_IS_TLS_SERVER_CONNECTION (tls))
- {
- GTlsAuthenticationMode mode = 0;
-
- g_object_get (tls,
- "authentication-mode", &mode,
- NULL);
- if (mode != G_TLS_AUTHENTICATION_REQUIRED)
- accepted = TRUE;
+ if (!accepted)
+ {
+ accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (tls),
+ priv->peer_certificate,
+ priv->peer_certificate_errors);
}
priv->peer_certificate_accepted = accepted;
- /* This has to be the very last statement before signaling the
- * condition variable because otherwise the code could spuriously
- * wakeup and continue before we are done here.
- */
- priv->peer_certificate_examined = TRUE;
-
- g_cond_signal (&priv->verify_certificate_condition);
- g_mutex_unlock (&priv->verify_certificate_mutex);
-
- return G_SOURCE_REMOVE;
+ return accepted;
}
-gboolean
-g_tls_connection_base_handshake_thread_verify_certificate (GTlsConnectionBase *tls)
+static void
+session_resumed_cb (GTlsOperationsThreadBase *thread,
+ GTlsCertificate *peer_certificate,
+ GTlsConnectionBase *tls)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- gboolean accepted;
- g_tls_log_debug (tls, "verifying peer certificate");
-
- 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.
+ /* FIXME: when doing async handshake as sync-on-a-thread, this function will
+ * be called from the handshake thread, which is unsafe. The code assumes
+ * it is used from the main thread.
+ * FIXME: eliminate handshake context.
*/
- g_assert (priv->handshake_context);
- g_main_context_invoke (priv->handshake_context, accept_or_reject_peer_certificate, tls);
- /* 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);
+ g_set_object (&priv->peer_certificate, peer_certificate);
+ g_clear_object (&peer_certificate);
- return accepted;
+ priv->peer_certificate_errors = 0;
+
+ g_object_notify (G_OBJECT (tls), "peer-certificate");
+ g_object_notify (G_OBJECT (tls), "peer-certificate-errors");
}
-static void
-handshake_thread (GTask *task,
- gpointer object,
- gpointer task_data,
- GCancellable *cancellable)
+static gboolean
+handshake (GTlsConnectionBase *tls,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
{
- GTlsConnectionBase *tls = object;
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
- GError *error = NULL;
- gint64 start_time;
- gint64 timeout;
+ GTlsAuthenticationMode auth_mode = G_TLS_AUTHENTICATION_NONE;
+ gchar *original_negotiated_protocol;
+ GList *accepted_cas;
- g_tls_log_debug (tls, "TLS handshake thread starts");
+ /* FIXME: in async codepaths, this function is still called on a secondary
+ * handshake thread. That means use of priv members here needs to be guarded
+ * by a mutex. Additionally, it is SUPER UNSAFE to notify negotiated-protocol
+ * on this thread. We need to either eliminate this extra thread, or tighten
+ * up the threadsafety of this function.
+ */
- /* A timeout, in microseconds, must be provided as a gint64* task_data. */
- g_assert (task_data);
- start_time = g_get_monotonic_time ();
- timeout = *((gint64 *)task_data);
+ g_tls_log_debug (tls, "TLS handshake starts");
priv->started_handshake = FALSE;
- priv->missing_requested_client_certificate = FALSE;
+ /* FIXME: I don't like this mismatch. If we claim here, let's yield at the
+ * bottom. Otherwise, move claim to the caller.
+ */
if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
- timeout, cancellable, &error))
+ timeout, cancellable, error))
{
- g_task_return_error (task, error);
- g_tls_log_debug (tls, "TLS handshake thread failed: claiming op failed");
- return;
+ g_tls_log_debug (tls, "TLS handshake failed: claiming op failed");
+ return FALSE;
}
g_clear_error (&priv->handshake_error);
if (priv->ever_handshaked && !priv->need_handshake)
{
- GTlsConnectionBaseStatus status;
-
- if (tls_class->handshake_thread_safe_renegotiation_status (tls) !=
G_TLS_SAFE_RENEGOTIATION_SUPPORTED_BY_PEER)
- {
- g_task_return_new_error (task, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Peer does not support safe renegotiation"));
- g_tls_log_debug (tls, "TLS handshake thread failed: peer does not support safe renegotiation");
- return;
- }
-
- /* Adjust the timeout for the next operation in the sequence. */
- if (timeout > 0)
- {
- timeout -= (g_get_monotonic_time () - start_time);
- if (timeout <= 0)
- timeout = 1;
- }
-
- status = tls_class->handshake_thread_request_rehandshake (tls, timeout, cancellable, &error);
- if (status != G_TLS_CONNECTION_BASE_OK)
- {
- g_task_return_error (task, error);
- g_tls_log_debug (tls, "TLS handshake thread failed: %s", error ? error->message : "no error");
- return;
- }
- }
-
- /* Adjust the timeout for the next operation in the sequence. */
- if (timeout > 0)
- {
- timeout -= (g_get_monotonic_time () - start_time);
- if (timeout <= 0)
- timeout = 1;
+ /* Once upon a time, we allowed calling g_tls_connection_handshake()
+ * twice in order to request a rehandshake. Now that rehandshaking has
+ * been removed from TLS 1.3, we'll instead just ignore the request. We
+ * can't throw an error here because this used to be allowed.
+ */
+ g_tls_log_debug (tls, "Ignoring duplicate TLS handshake request");
+ return TRUE;
}
- priv->started_handshake = TRUE;
- tls_class->handshake_thread_handshake (tls, timeout, cancellable, &error);
- priv->need_handshake = FALSE;
-
- if (error)
- {
- g_task_return_error (task, error);
- g_tls_log_debug (tls, "TLS handshake thread failed: %s", error->message);
- }
- else
+ if (G_IS_TLS_SERVER_CONNECTION (tls))
{
- priv->ever_handshaked = TRUE;
- g_task_return_boolean (task, TRUE);
- g_tls_log_debug (tls, "TLS handshake thread succeeded");
+ g_object_get (tls,
+ "authentication-mode", &auth_mode,
+ NULL);
}
-}
-
-static void
-sync_handshake_thread_completed (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
-{
- GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (object);
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- gpointer source_tag;
- g_tls_log_debug (tls, "synchronous TLS handshake thread completed");
+ original_negotiated_protocol = g_steal_pointer (&priv->negotiated_protocol);
- source_tag = g_task_get_source_tag (G_TASK (result));
- g_assert (source_tag == do_implicit_handshake || source_tag == g_tls_connection_base_handshake);
- g_assert (g_task_is_valid (result, object));
+ priv->started_handshake = TRUE;
- g_assert (g_main_context_is_owner (priv->handshake_context));
+ g_tls_operations_thread_base_handshake (priv->thread,
+ priv->certificate,
+ (const gchar **)priv->advertised_protocols,
+ auth_mode,
+ timeout,
+ (GTlsVerifyCertificateFunc)verify_certificate_cb,
+ (GTlsSessionResumedFunc)session_resumed_cb,
+ &priv->negotiated_protocol,
+ &accepted_cas,
+ cancellable,
+ tls,
+ error);
- g_mutex_lock (&priv->op_mutex);
- priv->sync_handshake_in_progress = FALSE;
- g_mutex_unlock (&priv->op_mutex);
+ priv->need_handshake = FALSE;
- g_main_context_wakeup (priv->handshake_context);
-}
+ if (g_strcmp0 (original_negotiated_protocol, priv->negotiated_protocol) != 0)
+ g_object_notify (G_OBJECT (tls), "negotiated-protocol");
+ g_free (original_negotiated_protocol);
-static void
-crank_sync_handshake_context (GTlsConnectionBase *tls,
- GCancellable *cancellable)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+ if (G_IS_TLS_CLIENT_CONNECTION (tls))
+ G_TLS_CONNECTION_BASE_GET_CLASS (tls)->set_accepted_cas (tls, accepted_cas);
+ else
+ g_assert (!accepted_cas);
- /* 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.
- *
- * FIXME: This function is not cancellable. We should figure out how to
- * support cancellation. We must not return from this function before it is
- * safe to destroy handshake_context, but it's not safe to destroy
- * handshake_context until after the handshake has completed. And the
- * handshake operation is not cancellable, so we have a problem.
- */
- g_mutex_lock (&priv->op_mutex);
- priv->sync_handshake_in_progress = TRUE;
- while (priv->sync_handshake_in_progress)
+ if (error && *error)
{
- g_mutex_unlock (&priv->op_mutex);
- g_main_context_iteration (priv->handshake_context, TRUE);
- g_mutex_lock (&priv->op_mutex);
+ g_tls_log_debug (tls, "TLS handshake failed: %s", (*error)->message);
+ return FALSE;
}
- g_mutex_unlock (&priv->op_mutex);
+
+ priv->ever_handshaked = TRUE;
+ g_tls_log_debug (tls, "TLS handshake succeeded");
+ return TRUE;
}
static gboolean
finish_handshake (GTlsConnectionBase *tls,
- GTask *task,
+ gboolean success,
GError **error)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
- gchar *original_negotiated_protocol;
GError *my_error = NULL;
g_tls_log_debug (tls, "finishing TLS handshake");
- original_negotiated_protocol = g_steal_pointer (&priv->negotiated_protocol);
-
- if (g_task_propagate_boolean (task, &my_error))
+ /* FIXME: Return an error from the handshake instead? */
+ if (success && priv->peer_certificate && !priv->peer_certificate_accepted)
{
- if (tls_class->is_session_resumed && tls_class->is_session_resumed (tls))
- {
- /* Because this session was resumed, we skipped certificate
- * verification on this handshake, so we missed our earlier
- * chance to set peer_certificate and peer_certificate_errors.
- * Do so here instead.
- *
- * The certificate has already been accepted, so we don't do
- * anything with the result here.
- */
- g_mutex_lock (&priv->verify_certificate_mutex);
- update_peer_certificate_and_compute_errors (tls);
- priv->peer_certificate_examined = TRUE;
- priv->peer_certificate_accepted = TRUE;
- g_mutex_unlock (&priv->verify_certificate_mutex);
- }
-
- /* FIXME: Return an error from the handshake thread instead. */
- if (priv->peer_certificate && !priv->peer_certificate_accepted)
- {
- g_set_error_literal (&my_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Unacceptable TLS certificate"));
- }
+ g_set_error_literal (&my_error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Unacceptable TLS certificate"));
}
- if (tls_class->complete_handshake)
- {
- /* If we already have an error, ignore further errors. */
- tls_class->complete_handshake (tls, &priv->negotiated_protocol, my_error ? NULL : &my_error);
-
- if (g_strcmp0 (original_negotiated_protocol, priv->negotiated_protocol) != 0)
- g_object_notify (G_OBJECT (tls), "negotiated-protocol");
- }
- g_free (original_negotiated_protocol);
+ /* FIXME: notify only when accepted-cas has actually changed. */
+ if (G_IS_TLS_CLIENT_CONNECTION (tls))
+ g_object_notify (G_OBJECT (tls), "accepted-cas");
if (my_error && priv->started_handshake)
priv->handshake_error = g_error_copy (my_error);
if (!my_error) {
g_tls_log_debug (tls, "TLS handshake has finished successfully");
- return TRUE;
+ return success;
}
g_tls_log_debug (tls, "TLS handshake has finished with error: %s", my_error->message);
@@ -1617,43 +1389,17 @@ g_tls_connection_base_handshake (GTlsConnection *conn,
GError **error)
{
GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (conn);
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
- GTask *task;
gboolean success;
- gint64 *timeout = NULL;
GError *my_error = NULL;
g_tls_log_debug (tls, "Starting synchronous TLS handshake");
- g_assert (!priv->handshake_context);
- priv->handshake_context = g_main_context_new ();
-
- g_main_context_push_thread_default (priv->handshake_context);
-
- if (tls_class->prepare_handshake)
- tls_class->prepare_handshake (tls, priv->advertised_protocols);
-
- task = g_task_new (conn, cancellable, sync_handshake_thread_completed, NULL);
- g_task_set_source_tag (task, g_tls_connection_base_handshake);
- g_task_set_name (task, "[glib-networking] g_tls_connection_base_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);
-
- g_task_run_in_thread (task, handshake_thread);
- crank_sync_handshake_context (tls, cancellable);
+ success = handshake (tls, -1 /* blocking */, cancellable, error);
- success = finish_handshake (tls, task, &my_error);
- g_object_unref (task);
-
- g_main_context_pop_thread_default (priv->handshake_context);
- g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+ /* If we already have an error, ignore further errors. */
+ success = finish_handshake (tls, success, my_error ? NULL : &my_error);
- yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
- G_TLS_CONNECTION_BASE_OK);
+ yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE, G_TLS_OPERATION_SUCCESS);
if (my_error)
g_propagate_error (error, my_error);
@@ -1673,6 +1419,9 @@ g_tls_connection_base_dtls_handshake (GDtlsConnection *conn,
* handshake_thread() and then call async_handshake_thread_completed(),
* and a second to call the caller's original callback after we call
* finish_handshake().
+ *
+ * Note: async_handshake_thread_completed() is called only for explicit
+ * async handshakes, not for implicit async handshakes.
*/
static void
@@ -1701,17 +1450,12 @@ async_handshake_thread_completed (GObject *object,
need_finish_handshake = FALSE;
g_mutex_unlock (&priv->op_mutex);
- /* We have to clear handshake_context before g_task_return_* because it can
- * return immediately to application code inside g_task_return_*,
- * and the application code could then start a new TLS operation.
- *
- * But we can't clear until after finish_handshake().
- */
if (need_finish_handshake)
{
- success = finish_handshake (tls, G_TASK (result), &error);
+ success = g_task_propagate_boolean (G_TASK (result), &error);
- g_clear_pointer (&priv->handshake_context, g_main_context_unref);
+ /* If we already have an error, ignore further errors. */
+ success = finish_handshake (tls, success, error ? NULL : &error);
if (success)
g_task_return_boolean (caller_task, TRUE);
@@ -1720,8 +1464,6 @@ async_handshake_thread_completed (GObject *object,
}
else
{
- g_clear_pointer (&priv->handshake_context, g_main_context_unref);
-
if (priv->handshake_error)
g_task_return_error (caller_task, g_error_copy (priv->handshake_error));
else
@@ -1737,12 +1479,13 @@ async_handshake_thread (GTask *task,
gpointer task_data,
GCancellable *cancellable)
{
- GTlsConnectionBase *tls = object;
+ GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (object);
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+ GError *error = NULL;
g_tls_log_debug (tls, "Asynchronous TLS handshake thread starts");
- handshake_thread (task, object, task_data, cancellable);
+ handshake (tls, -1 /* blocking */, cancellable, &error);
g_mutex_lock (&priv->op_mutex);
priv->need_finish_handshake = TRUE;
@@ -1753,8 +1496,12 @@ async_handshake_thread (GTask *task,
priv->handshaking = FALSE;
g_mutex_unlock (&priv->op_mutex);
- yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
- G_TLS_CONNECTION_BASE_OK);
+ yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE, G_TLS_OPERATION_SUCCESS);
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
}
static void
@@ -1765,19 +1512,10 @@ g_tls_connection_base_handshake_async (GTlsConnection *conn,
gpointer user_data)
{
GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (conn);
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
GTask *thread_task, *caller_task;
- gint64 *timeout = NULL;
g_tls_log_debug (tls, "Starting asynchronous TLS handshake");
- g_assert (!priv->handshake_context);
- priv->handshake_context = g_main_context_ref_thread_default ();
-
- if (tls_class->prepare_handshake)
- tls_class->prepare_handshake (tls, priv->advertised_protocols);
-
caller_task = g_task_new (conn, cancellable, callback, user_data);
g_task_set_source_tag (caller_task, g_tls_connection_base_handshake_async);
g_task_set_name (caller_task, "[glib-networking] g_tls_connection_base_handshake_async (caller task)");
@@ -1788,10 +1526,6 @@ g_tls_connection_base_handshake_async (GTlsConnection *conn,
g_task_set_name (caller_task, "[glib-networking] g_tls_connection_base_handshake_async (thread task)");
g_task_set_priority (thread_task, io_priority);
- timeout = g_new0 (gint64, 1);
- *timeout = -1; /* blocking */
- g_task_set_task_data (thread_task, timeout, g_free);
-
g_task_run_in_thread (thread_task, async_handshake_thread);
g_object_unref (thread_task);
}
@@ -1828,141 +1562,104 @@ g_tls_connection_base_dtls_handshake_finish (GDtlsConnection *conn,
}
static gboolean
-do_implicit_handshake (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error)
+start_async_implicit_handshake (GTlsConnectionBase *tls,
+ GCancellable *cancellable,
+ GError **error)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
- gint64 *thread_timeout = NULL;
- g_tls_log_debug (tls, "Implcit TLS handshaking starts");
+ g_tls_log_debug (tls, "Starting async implicit handshake");
/* We have op_mutex */
- g_assert (!priv->handshake_context);
- 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->async_implicit_handshake);
+ priv->async_implicit_handshake = g_task_new (tls, cancellable,
+ NULL, NULL);
+ g_task_set_source_tag (priv->async_implicit_handshake, do_implicit_handshake);
+ g_task_set_name (priv->async_implicit_handshake, "[glib-networking] do_implicit_handshake");
- g_assert (!priv->implicit_handshake);
- priv->implicit_handshake = g_task_new (tls, cancellable,
- timeout ? sync_handshake_thread_completed : NULL,
- NULL);
- g_task_set_source_tag (priv->implicit_handshake, do_implicit_handshake);
- g_task_set_name (priv->implicit_handshake, "[glib-networking] do_implicit_handshake");
+ /* In the non-blocking case, start the asynchronous handshake operation
+ * and return EWOULDBLOCK to the caller, who will handle polling for
+ * completion of the handshake and whatever operation they actually cared
+ * about. Run the actual operation as blocking in its thread.
+ */
- thread_timeout = g_new0 (gint64, 1);
- g_task_set_task_data (priv->implicit_handshake,
- thread_timeout, g_free);
+ g_task_run_in_thread (priv->async_implicit_handshake, async_handshake_thread);
- if (tls_class->prepare_handshake)
- tls_class->prepare_handshake (tls, priv->advertised_protocols);
+ /* Intentionally not translated because this is not a fatal error to be
+ * presented to the user, and to avoid this showing up in profiling.
+ */
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
+ return FALSE;
+}
- if (timeout != 0)
- {
- GError *my_error = NULL;
- gboolean success;
+static gboolean
+do_sync_implicit_handshake (GTlsConnectionBase *tls,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
+ GError *my_error = NULL;
+ gboolean success;
- /* In the blocking case, run the handshake operation synchronously in
- * another thread, and delegate handling the timeout to that thread; it
- * should return G_IO_ERROR_TIMED_OUT iff (timeout > 0) and the operation
- * times out. If (timeout < 0) it should block indefinitely until the
- * operation is complete or errors. */
- *thread_timeout = timeout;
+ g_tls_log_debug (tls, "Starting sync implicit handshake");
- g_mutex_unlock (&priv->op_mutex);
+ /* We have op_mutex */
- g_task_set_return_on_cancel (priv->implicit_handshake, TRUE);
- g_task_run_in_thread (priv->implicit_handshake, handshake_thread);
+ g_mutex_unlock (&priv->op_mutex);
- crank_sync_handshake_context (tls, cancellable);
+ success = handshake (tls, timeout, cancellable, &my_error);
- success = finish_handshake (tls,
- priv->implicit_handshake,
- &my_error);
+ /* If we already have an error, ignore further errors. */
+ success = finish_handshake (tls, success,
+ my_error ? NULL : &my_error);
- g_main_context_pop_thread_default (priv->handshake_context);
- g_clear_pointer (&priv->handshake_context, g_main_context_unref);
- g_clear_object (&priv->implicit_handshake);
+ yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE, G_TLS_OPERATION_SUCCESS);
- yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
- G_TLS_CONNECTION_BASE_OK);
+ g_mutex_lock (&priv->op_mutex);
- g_mutex_lock (&priv->op_mutex);
+ if (my_error)
+ g_propagate_error (error, my_error);
+ return success;
+}
- if (my_error)
- g_propagate_error (error, my_error);
- return success;
- }
- else
- {
- /* In the non-blocking case, start the asynchronous handshake operation
- * and return EWOULDBLOCK to the caller, who will handle polling for
- * completion of the handshake and whatever operation they actually cared
- * about. Run the actual operation as blocking in its thread. */
- *thread_timeout = -1; /* blocking */
-
- g_task_run_in_thread (priv->implicit_handshake,
- async_handshake_thread);
-
- /* Intentionally not translated because this is not a fatal error to be
- * presented to the user, and to avoid this showing up in profiling. */
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
- return FALSE;
- }
+static gboolean
+do_implicit_handshake (GTlsConnectionBase *tls,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (timeout == 0)
+ return start_async_implicit_handshake (tls, cancellable, error);
+
+ return do_sync_implicit_handshake (tls, timeout, cancellable, error);
}
gssize
g_tls_connection_base_read (GTlsConnectionBase *tls,
void *buffer,
- gsize count,
+ gsize size,
gint64 timeout,
GCancellable *cancellable,
GError **error)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseStatus status;
+ GTlsOperationStatus status;
gssize nread;
g_tls_log_debug (tls, "starting to read data from TLS connection");
- do
- {
- if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_READ,
- timeout, cancellable, error))
- return -1;
+ if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_READ,
+ timeout, cancellable, error))
+ return -1;
- if (priv->app_data_buf && !priv->handshaking)
- {
- nread = MIN (count, priv->app_data_buf->len);
- memcpy (buffer, priv->app_data_buf->data, nread);
- if (nread == priv->app_data_buf->len)
- g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
- else
- g_byte_array_remove_range (priv->app_data_buf, 0, nread);
- status = G_TLS_CONNECTION_BASE_OK;
- }
- else
- {
- status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
- read_fn (tls, buffer, count, timeout, &nread, cancellable, error);
- }
+ status = g_tls_operations_thread_base_read (priv->thread, buffer, size, timeout, &nread, cancellable,
error);
- yield_op (tls, G_TLS_CONNECTION_BASE_OP_READ, status);
- }
- while (status == G_TLS_CONNECTION_BASE_REHANDSHAKE);
+ yield_op (tls, G_TLS_CONNECTION_BASE_OP_READ, status);
- if (status == G_TLS_CONNECTION_BASE_OK)
+ if (status == G_TLS_OPERATION_SUCCESS)
{
- priv->successful_posthandshake_op = TRUE;
g_tls_log_debug (tls, "successfully read %" G_GSSIZE_FORMAT " bytes from TLS connection", nread);
return nread;
}
@@ -1980,50 +1677,20 @@ g_tls_connection_base_read_message (GTlsConnectionBase *tls,
GError **error)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseStatus status;
+ GTlsOperationStatus status;
gssize nread;
g_tls_log_debug (tls, "starting to read messages from TLS connection");
- do {
- if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_READ,
- timeout, cancellable, error))
- return -1;
+ if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_READ,
+ timeout, cancellable, error))
+ return -1;
- /* Copy data out of the app data buffer first. */
- if (priv->app_data_buf && !priv->handshaking)
- {
- nread = 0;
-
- for (guint i = 0; i < num_vectors; i++)
- {
- gsize count;
- GInputVector *vec = &vectors[i];
-
- count = MIN (vec->size, priv->app_data_buf->len);
- nread += count;
-
- memcpy (vec->buffer, priv->app_data_buf->data, count);
- if (count == priv->app_data_buf->len)
- g_clear_pointer (&priv->app_data_buf, g_byte_array_unref);
- else
- g_byte_array_remove_range (priv->app_data_buf, 0, count);
- status = G_TLS_CONNECTION_BASE_OK;
- }
- }
- else
- {
- g_assert (G_TLS_CONNECTION_BASE_GET_CLASS (tls)->read_message_fn);
- status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
- read_message_fn (tls, vectors, num_vectors, timeout, &nread, cancellable, error);
- }
-
- yield_op (tls, G_TLS_CONNECTION_BASE_OP_READ, status);
- } while (status == G_TLS_CONNECTION_BASE_REHANDSHAKE);
-
- if (status == G_TLS_CONNECTION_BASE_OK)
+ status = g_tls_operations_thread_base_read_message (priv->thread, vectors, num_vectors, timeout, &nread,
cancellable, error);
+ yield_op (tls, G_TLS_CONNECTION_BASE_OP_READ, status);
+
+ if (status == G_TLS_OPERATION_SUCCESS)
{
- priv->successful_posthandshake_op = TRUE;
g_tls_log_debug (tls, "successfully read %" G_GSSIZE_FORMAT " bytes from TLS connection", nread);
return nread;
}
@@ -2042,7 +1709,6 @@ g_tls_connection_base_receive_messages (GDatagramBased *datagram_based,
GError **error)
{
GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (datagram_based);
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
guint i;
GError *child_error = NULL;
@@ -2105,40 +1771,32 @@ g_tls_connection_base_receive_messages (GDatagramBased *datagram_based,
return -1;
}
- priv->successful_posthandshake_op = TRUE;
return i;
}
gssize
g_tls_connection_base_write (GTlsConnectionBase *tls,
const void *buffer,
- gsize count,
+ gsize size,
gint64 timeout,
GCancellable *cancellable,
GError **error)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseStatus status;
+ GTlsOperationStatus status;
gssize nwrote;
- g_tls_log_debug (tls, "starting to write %" G_GSIZE_FORMAT " bytes to TLS connection", count);
+ g_tls_log_debug (tls, "starting to write %" G_GSIZE_FORMAT " bytes to TLS connection", size);
- do
- {
- if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE,
- timeout, cancellable, error))
- return -1;
-
- status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
- write_fn (tls, buffer, count, timeout, &nwrote, cancellable, error);
+ if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE,
+ timeout, cancellable, error))
+ return -1;
- yield_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE, status);
- }
- while (status == G_TLS_CONNECTION_BASE_REHANDSHAKE);
+ status = g_tls_operations_thread_base_write (priv->thread, buffer, size, timeout, &nwrote, cancellable,
error);
+ yield_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE, status);
- if (status == G_TLS_CONNECTION_BASE_OK)
+ if (status == G_TLS_OPERATION_SUCCESS)
{
- priv->successful_posthandshake_op = TRUE;
g_tls_log_debug (tls, "successfully write %" G_GSSIZE_FORMAT " bytes to TLS connection", nwrote);
return nwrote;
}
@@ -2156,26 +1814,21 @@ g_tls_connection_base_write_message (GTlsConnectionBase *tls,
GError **error)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsConnectionBaseStatus status;
+ GTlsOperationStatus status;
gssize nwrote;
g_tls_log_debug (tls, "starting to write messages to TLS connection");
- do {
- if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE,
- timeout, cancellable, error))
- return -1;
+ if (!claim_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE,
+ timeout, cancellable, error))
+ return -1;
- g_assert (G_TLS_CONNECTION_BASE_GET_CLASS (tls)->read_message_fn);
- status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
- write_message_fn (tls, vectors, num_vectors, timeout, &nwrote, cancellable, error);
+ status = g_tls_operations_thread_base_write_message (priv->thread, vectors, num_vectors, timeout, &nwrote,
cancellable, error);
- yield_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE, status);
- } while (status == G_TLS_CONNECTION_BASE_REHANDSHAKE);
+ yield_op (tls, G_TLS_CONNECTION_BASE_OP_WRITE, status);
- if (status == G_TLS_CONNECTION_BASE_OK)
+ if (status == G_TLS_OPERATION_SUCCESS)
{
- priv->successful_posthandshake_op = TRUE;
g_tls_log_debug (tls, "successfully write %" G_GSSIZE_FORMAT " bytes to TLS connection", nwrote);
return nwrote;
}
@@ -2194,7 +1847,6 @@ g_tls_connection_base_send_messages (GDatagramBased *datagram_based,
GError **error)
{
GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (datagram_based);
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
guint i;
GError *child_error = NULL;
@@ -2245,7 +1897,6 @@ g_tls_connection_base_send_messages (GDatagramBased *datagram_based,
return -1;
}
- priv->successful_posthandshake_op = TRUE;
return i;
}
@@ -2277,7 +1928,7 @@ g_tls_connection_base_close_internal (GIOStream *stream,
GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (stream);
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
GTlsConnectionBaseOp op;
- GTlsConnectionBaseStatus status;
+ GTlsOperationStatus status;
gboolean success = TRUE;
GError *close_error = NULL, *stream_error = NULL;
@@ -2304,13 +1955,12 @@ g_tls_connection_base_close_internal (GIOStream *stream,
if (priv->ever_handshaked && !priv->write_closed &&
direction & G_TLS_DIRECTION_WRITE)
{
- status = G_TLS_CONNECTION_BASE_GET_CLASS (tls)->
- close_fn (tls, timeout, cancellable, &close_error);
+ status = g_tls_operations_thread_base_close (priv->thread, cancellable, &close_error);
priv->write_closed = TRUE;
}
else
- status = G_TLS_CONNECTION_BASE_OK;
+ status = G_TLS_OPERATION_SUCCESS;
if (!priv->read_closed && direction & G_TLS_DIRECTION_READ)
priv->read_closed = TRUE;
@@ -2330,7 +1980,7 @@ g_tls_connection_base_close_internal (GIOStream *stream,
success = g_output_stream_close (g_io_stream_get_output_stream (priv->base_io_stream),
cancellable, &stream_error);
}
- else if (g_tls_connection_base_is_dtls (tls))
+ else if (is_dtls (tls))
{
/* We do not close underlying #GDatagramBaseds. There is no
* g_datagram_based_close() method since different datagram-based
@@ -2345,7 +1995,7 @@ g_tls_connection_base_close_internal (GIOStream *stream,
yield_op (tls, op, status);
/* Propagate errors. */
- if (status != G_TLS_CONNECTION_BASE_OK)
+ if (status != G_TLS_OPERATION_SUCCESS)
{
g_tls_log_debug (tls, "error closing TLS connection: %s", close_error->message);
g_propagate_error (error, close_error);
@@ -2362,7 +2012,7 @@ g_tls_connection_base_close_internal (GIOStream *stream,
g_tls_log_debug (tls, "the TLS connection has been closed successfully");
}
- return success && status == G_TLS_CONNECTION_BASE_OK;
+ return success && status == G_TLS_OPERATION_SUCCESS;
}
static gboolean
@@ -2510,153 +2160,12 @@ g_tls_connection_base_dtls_get_negotiated_protocol (GDtlsConnection *conn)
return priv->negotiated_protocol;
}
-GDatagramBased *
-g_tls_connection_base_get_base_socket (GTlsConnectionBase *tls)
+GTlsOperationsThreadBase *
+g_tls_connection_base_get_op_thread (GTlsConnectionBase *tls)
{
GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- g_assert (g_tls_connection_base_is_dtls (tls));
-
- return priv->base_socket;
-}
-
-GIOStream *
-g_tls_connection_base_get_base_iostream (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- g_assert (!g_tls_connection_base_is_dtls (tls));
-
- return priv->base_io_stream;
-}
-
-GPollableInputStream *
-g_tls_connection_base_get_base_istream (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- g_assert (!g_tls_connection_base_is_dtls (tls));
-
- return priv->base_istream;
-}
-
-GPollableOutputStream *
-g_tls_connection_base_get_base_ostream (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- g_assert (!g_tls_connection_base_is_dtls (tls));
-
- return priv->base_ostream;
-}
-
-void
-g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- priv->missing_requested_client_certificate = TRUE;
-}
-
-GError **
-g_tls_connection_base_get_read_error (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- return &priv->read_error;
-}
-
-GError **
-g_tls_connection_base_get_write_error (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- return &priv->write_error;
-}
-
-gint64
-g_tls_connection_base_get_read_timeout (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- return priv->read_timeout;
-}
-
-gint64
-g_tls_connection_base_get_write_timeout (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- return priv->write_timeout;
-}
-
-GCancellable *
-g_tls_connection_base_get_read_cancellable (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- return priv->read_cancellable;
-}
-
-GCancellable *
-g_tls_connection_base_get_write_cancellable (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- return priv->write_cancellable;
-}
-
-gboolean
-g_tls_connection_base_is_handshaking (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- return priv->handshaking;
-}
-
-gboolean
-g_tls_connection_base_ever_handshaked (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- return priv->ever_handshaked;
-}
-
-gboolean
-g_tls_connection_base_handshake_thread_request_certificate (GTlsConnectionBase *tls)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
- GTlsInteractionResult res = G_TLS_INTERACTION_UNHANDLED;
- GTlsInteraction *interaction;
- GTlsConnection *conn;
-
- g_return_val_if_fail (G_IS_TLS_CONNECTION_BASE (tls), FALSE);
-
- conn = G_TLS_CONNECTION (tls);
-
- g_clear_error (&priv->interaction_error);
-
- interaction = g_tls_connection_get_interaction (conn);
- if (!interaction)
- return FALSE;
-
- res = g_tls_interaction_invoke_request_certificate (interaction, conn, 0,
- priv->read_cancellable,
- &priv->interaction_error);
- return res != G_TLS_INTERACTION_FAILED;
-}
-
-void
-g_tls_connection_base_handshake_thread_buffer_application_data (GTlsConnectionBase *tls,
- guint8 *data,
- gsize length)
-{
- GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
-
- if (!priv->app_data_buf)
- priv->app_data_buf = g_byte_array_new ();
-
- g_byte_array_append (priv->app_data_buf, data, length);
+ return priv->thread;
}
static void
@@ -2680,9 +2189,6 @@ g_tls_connection_base_class_init (GTlsConnectionBaseClass *klass)
iostream_class->close_async = g_tls_connection_base_close_async;
iostream_class->close_finish = g_tls_connection_base_close_finish;
- klass->push_io = g_tls_connection_base_real_push_io;
- klass->pop_io = g_tls_connection_base_real_pop_io;
-
/* For GTlsConnection and GDtlsConnection: */
g_object_class_override_property (gobject_class, PROP_BASE_IO_STREAM, "base-io-stream");
g_object_class_override_property (gobject_class, PROP_BASE_SOCKET, "base-socket");
@@ -2720,3 +2226,9 @@ g_tls_connection_base_datagram_based_iface_init (GDatagramBasedInterface *iface)
iface->condition_check = g_tls_connection_base_condition_check;
iface->condition_wait = g_tls_connection_base_condition_wait;
}
+
+static void
+g_tls_connection_base_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = g_tls_connection_base_initable_init;
+}
diff --git a/tls/base/gtlsconnection-base.h b/tls/base/gtlsconnection-base.h
index 221bbe6..6c8a006 100644
--- a/tls/base/gtlsconnection-base.h
+++ b/tls/base/gtlsconnection-base.h
@@ -3,6 +3,8 @@
* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2009-2011 Red Hat, Inc
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -32,109 +34,26 @@ G_BEGIN_DECLS
G_DECLARE_DERIVABLE_TYPE (GTlsConnectionBase, g_tls_connection_base, G, TLS_CONNECTION_BASE, GTlsConnection)
-typedef enum {
- G_TLS_CONNECTION_BASE_OK,
- G_TLS_CONNECTION_BASE_WOULD_BLOCK,
- G_TLS_CONNECTION_BASE_TIMED_OUT,
- G_TLS_CONNECTION_BASE_REHANDSHAKE,
- G_TLS_CONNECTION_BASE_TRY_AGAIN,
- G_TLS_CONNECTION_BASE_ERROR,
-} GTlsConnectionBaseStatus;
-
typedef enum {
G_TLS_DIRECTION_NONE = 0,
G_TLS_DIRECTION_READ = 1 << 0,
G_TLS_DIRECTION_WRITE = 1 << 1,
} GTlsDirection;
-typedef enum {
- G_TLS_SAFE_RENEGOTIATION_SUPPORTED_BY_PEER,
- G_TLS_SAFE_RENEGOTIATION_UNSUPPORTED
-} GTlsSafeRenegotiationStatus;
-
#define G_TLS_DIRECTION_BOTH (G_TLS_DIRECTION_READ | G_TLS_DIRECTION_WRITE)
+typedef struct _GTlsOperationsThreadBase GTlsOperationsThreadBase;
+
struct _GTlsConnectionBaseClass
{
GTlsConnectionClass parent_class;
- void (*prepare_handshake) (GTlsConnectionBase *tls,
- gchar **advertised_protocols);
- GTlsSafeRenegotiationStatus (*handshake_thread_safe_renegotiation_status)
- (GTlsConnectionBase *tls);
- GTlsConnectionBaseStatus (*handshake_thread_request_rehandshake)
- (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error);
- GTlsConnectionBaseStatus (*handshake_thread_handshake) (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error);
- GTlsCertificate *(*retrieve_peer_certificate) (GTlsConnectionBase *tls);
- void (*complete_handshake) (GTlsConnectionBase *tls,
- gchar **negotiated_protocol,
- GError **error);
-
- gboolean (*is_session_resumed) (GTlsConnectionBase *tls);
-
- void (*push_io) (GTlsConnectionBase *tls,
- GIOCondition direction,
- gint64 timeout,
- GCancellable *cancellable);
- GTlsConnectionBaseStatus (*pop_io) (GTlsConnectionBase *tls,
- GIOCondition direction,
- gboolean success,
- GError **error);
+ GTlsOperationsThreadBase *(*create_op_thread) (GTlsConnectionBase *tls);
- GTlsConnectionBaseStatus (*read_fn) (GTlsConnectionBase *tls,
- void *buffer,
- gsize count,
- gint64 timeout,
- gssize *nread,
- GCancellable *cancellable,
- GError **error);
- GTlsConnectionBaseStatus (*read_message_fn) (GTlsConnectionBase *tls,
- GInputVector *vectors,
- guint num_vectors,
- gint64 timeout,
- gssize *nread,
- GCancellable *cancellable,
- GError **error);
-
- GTlsConnectionBaseStatus (*write_fn) (GTlsConnectionBase *tls,
- const void *buffer,
- gsize count,
- gint64 timeout,
- gssize *nwrote,
- GCancellable *cancellable,
- GError **error);
- GTlsConnectionBaseStatus (*write_message_fn) (GTlsConnectionBase *tls,
- GOutputVector *vectors,
- guint num_vectors,
- gint64 timeout,
- gssize *nwrote,
- GCancellable *cancellable,
- GError **error);
-
- GTlsConnectionBaseStatus (*close_fn) (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error);
+ void (*set_accepted_cas) (GTlsConnectionBase *tls,
+ GList *accepted_cas);
};
-gboolean g_tls_connection_base_handshake_thread_verify_certificate
- (GTlsConnectionBase *tls);
-
-void g_tls_connection_base_push_io (GTlsConnectionBase *tls,
- GIOCondition direction,
- gint64 timeout,
- GCancellable *cancellable);
-GTlsConnectionBaseStatus g_tls_connection_base_pop_io (GTlsConnectionBase *tls,
- GIOCondition direction,
- gboolean success,
- GError **error);
-
gssize g_tls_connection_base_read (GTlsConnectionBase *tls,
void *buffer,
gsize size,
@@ -150,48 +69,15 @@ gssize g_tls_connection_base_write (GTlsCon
gboolean g_tls_connection_base_check (GTlsConnectionBase *tls,
GIOCondition condition);
-gboolean g_tls_connection_base_base_check (GTlsConnectionBase *tls,
- GIOCondition condition);
GSource *g_tls_connection_base_create_source (GTlsConnectionBase *tls,
GIOCondition condition,
GCancellable *cancellable);
+gboolean g_tls_connection_base_close_internal (GIOStream *stream,
+ GTlsDirection direction,
+ gint64 timeout,
+ GCancellable *cancellable,
+ GError **error);
-gboolean g_tls_connection_base_close_internal (GIOStream *stream,
- GTlsDirection direction,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error);
-
-gboolean g_tls_connection_base_is_dtls (GTlsConnectionBase *tls);
-
-GDatagramBased *g_tls_connection_base_get_base_socket (GTlsConnectionBase *tls);
-
-GIOStream *g_tls_connection_base_get_base_iostream (GTlsConnectionBase *tls);
-GPollableInputStream *g_tls_connection_base_get_base_istream (GTlsConnectionBase *tls);
-GPollableOutputStream *g_tls_connection_base_get_base_ostream (GTlsConnectionBase *tls);
-
-void g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate
- (GTlsConnectionBase *tls);
-
-GError **g_tls_connection_base_get_read_error (GTlsConnectionBase *tls);
-GError **g_tls_connection_base_get_write_error (GTlsConnectionBase *tls);
-
-gint64 g_tls_connection_base_get_read_timeout (GTlsConnectionBase *tls);
-gint64 g_tls_connection_base_get_write_timeout (GTlsConnectionBase *tls);
-
-GCancellable *g_tls_connection_base_get_read_cancellable (GTlsConnectionBase *tls);
-GCancellable *g_tls_connection_base_get_write_cancellable (GTlsConnectionBase *tls);
-
-gboolean g_tls_connection_base_is_handshaking (GTlsConnectionBase *tls);
-
-gboolean g_tls_connection_base_ever_handshaked (GTlsConnectionBase *tls);
-
-gboolean g_tls_connection_base_handshake_thread_request_certificate
- (GTlsConnectionBase *tls);
-
-void g_tls_connection_base_handshake_thread_buffer_application_data
- (GTlsConnectionBase *tls,
- guint8 *data,
- gsize length);
+GTlsOperationsThreadBase *g_tls_connection_base_get_op_thread (GTlsConnectionBase *tls);
G_END_DECLS
diff --git a/tls/base/gtlsoperationsthread-base.c b/tls/base/gtlsoperationsthread-base.c
new file mode 100644
index 0000000..8f89cd6
--- /dev/null
+++ b/tls/base/gtlsoperationsthread-base.c
@@ -0,0 +1,1704 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#include "config.h"
+#include "gtlsoperationsthread-base.h"
+
+#include "tls-base-builtins.h"
+
+#include <glib/gi18n-lib.h>
+
+/* The purpose of this class is to ensure the underlying TLS library is only
+ * ever used on a single thread. There are multiple benefits of this:
+ *
+ * - OpenSSL objects like the SSL* are not threadsafe and must only be accessed
+ * from a single thread.
+ *
+ * - With GnuTLS, this dramatically simplifies implementation of post-handshake
+ * authentication and alerts, which are hard to handle when the
+ * gnutls_session_t may be used on multiple threads at once. Moving
+ * gnutls_session_t use to a single thread should also make it easier to
+ * implement support for downloading missing certificates using the
+ * Authority Information Access extension.
+ *
+ * - GTlsConnectionBase and its subclasses are very complicated, and it has
+ * become difficult to ensure the correctness of the code considering that the
+ * threadsafety semantics of its parent class, GIOStream, allow it to be used
+ * from separate reader and writer threads simultaneously.
+ *
+ * While the TLS thread class is intended to simplify our code, it has one major
+ * disadvantage: the TLS thread *must never block* during read or write
+ * operations, because GIOStream users are allowed to do a sync read and a sync
+ * write simultaneously in separate threads. Consider a hypothetical scenario:
+ *
+ * (1) Application starts a read on thread A
+ * (2) Application starts a write on thread B
+ * (3) Application's peer waits for the write to complete before sending data.
+ *
+ * In this scenario, the read on thread A is stalled until the write on thread B
+ * is completed. The application is allowed to do this and expect it to work,
+ * because GIOStream says it will work. If our TLS thread were to block on the
+ * read, then the write would never start, and the read could never complete.
+ *
+ * This means that underlying TLS operations must use entirely nonblocking I/O.
+ * We specify a timeout of 0 for every operation to ensure it returns
+ * immediately with an error if I/O cannot be performed immediately. If so, we
+ * create a GSource that will trigger later on, when possibly ready to perform
+ * I/O. In this way, we can simultaneously handle separate synchronous read and
+ * write operations on one thread without either one blocking the other.
+ */
+typedef struct {
+ /* Objects explicitly designed for unlocked multithreaded use. */
+ GThread *op_thread;
+ GMainContext *op_thread_context;
+ GAsyncQueue *queue;
+
+ /* Never mutated after construction. */
+ GTlsOperationsThreadType thread_type;
+
+ /* GIOStream is only partially threadsafe, and GDatagramBased is not
+ * threadsafe at all. Although they are shared across threads, we try to
+ * ensure that we only use them on one thread at any given time.
+ */
+ GIOStream *base_iostream;
+ GDatagramBased *base_socket;
+
+ /* This mutex guards everything below. It's a bit of a failure of design.
+ * Ideally we wouldn't need to share this data between threads and would
+ * instead pass data to the op thread and return data from the op thread
+ * using the op struct. But this is not always easy.
+ *
+ * FIXME: what of this can move into the op struct? The booleans are needed
+ * for more than handshakes, so they'd need to be part of every op.
+ */
+ GMutex mutex;
+
+ GTlsInteraction *interaction;
+ GError *interaction_error;
+ gboolean missing_requested_client_certificate;
+ gboolean performed_successful_posthandshake_op;
+ gboolean require_close_notify;
+} GTlsOperationsThreadBasePrivate;
+
+typedef enum {
+ G_TLS_THREAD_OP_COPY_CLIENT_SESSION_STATE,
+ G_TLS_THREAD_OP_SET_SERVER_IDENTITY,
+ G_TLS_THREAD_OP_HANDSHAKE,
+ G_TLS_THREAD_OP_READ,
+ G_TLS_THREAD_OP_READ_MESSAGE,
+ G_TLS_THREAD_OP_WRITE,
+ G_TLS_THREAD_OP_WRITE_MESSAGE,
+ G_TLS_THREAD_OP_CLOSE,
+ G_TLS_THREAD_OP_SHUTDOWN_THREAD
+} GTlsThreadOperationType;
+
+struct _HandshakeContext
+{
+ GMainContext *caller_context;
+ GTlsVerifyCertificateFunc verify_callback;
+ gpointer user_data;
+};
+
+typedef struct {
+ /* Input */
+ HandshakeContext *context;
+ GTlsCertificate *own_certificate;
+ gchar **advertised_protocols;
+ GTlsAuthenticationMode auth_mode;
+ gboolean require_close_notify;
+
+ /* Output */
+ gchar *negotiated_protocol;
+ GList *accepted_cas;
+ GTlsCertificate *peer_certificate;
+ gboolean session_resumed;
+} HandshakeData;
+
+typedef struct {
+ GTlsThreadOperationType type;
+ GIOCondition io_condition;
+
+ GTlsOperationsThreadBase *thread;
+
+ /* Op input */
+ union {
+ GTlsOperationsThreadBase *source; /* for copy client session state */
+ gchar *server_identity; /* for set server identity */
+ HandshakeData *handshake_data; /* for handshake */
+ void *data; /* for read/write */
+ GInputVector *input_vectors; /* for read message */
+ GOutputVector *output_vectors; /* for write message */
+ };
+ union {
+ gsize size; /* for read/write */
+ guint num_vectors; /* for read/write message */
+ };
+ gint64 timeout;
+ gint64 start_time;
+
+ GCancellable *cancellable;
+
+ /* Op output */
+ GTlsOperationStatus result;
+ gssize count; /* Bytes read or written */
+ GError *error;
+
+ GMutex finished_mutex;
+ GCond finished_condition;
+ gboolean finished;
+} GTlsThreadOperation;
+
+static gboolean process_op (GAsyncQueue *queue,
+ GTlsThreadOperation *delayed_op,
+ GMainLoop *main_loop);
+
+enum
+{
+ REQUEST_CERTIFICATE,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum
+{
+ PROP_0,
+ PROP_BASE_IO_STREAM,
+ PROP_BASE_SOCKET,
+ PROP_THREAD_TYPE,
+ LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+static void g_tls_operations_thread_base_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsOperationsThreadBase, g_tls_operations_thread_base, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (GTlsOperationsThreadBase);
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ g_tls_operations_thread_base_initable_iface_init);)
+
+static inline gboolean
+is_dtls (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ return !!priv->base_socket;
+}
+
+static inline gboolean
+is_client (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ return priv->thread_type == G_TLS_OPERATIONS_THREAD_CLIENT;
+}
+
+static inline gboolean
+is_server (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ return priv->thread_type == G_TLS_OPERATIONS_THREAD_SERVER;
+}
+
+void
+g_tls_operations_thread_base_set_interaction (GTlsOperationsThreadBase *self,
+ GTlsInteraction *interaction)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ g_mutex_lock (&priv->mutex);
+ g_clear_object (&priv->interaction);
+ priv->interaction = interaction? g_object_ref (interaction) : NULL;
+ g_mutex_unlock (&priv->mutex);
+}
+
+GTlsInteraction *
+g_tls_operations_thread_base_ref_interaction (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsInteraction *ref = NULL;
+
+ g_mutex_lock (&priv->mutex);
+ if (priv->interaction)
+ ref = g_object_ref (priv->interaction);
+ g_mutex_unlock (&priv->mutex);
+
+ return ref;
+}
+
+static GError *
+take_interaction_error (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GError *error;
+
+ g_mutex_lock (&priv->mutex);
+ error = g_steal_pointer (&priv->interaction_error);
+ g_mutex_unlock (&priv->mutex);
+
+ return error;
+}
+
+gboolean
+g_tls_operations_thread_base_request_certificate (GTlsOperationsThreadBase *self,
+ GCancellable *cancellable,
+ GTlsCertificate **own_certificate)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsInteractionResult result = G_TLS_INTERACTION_UNHANDLED;
+ GTlsCertificate *cert = NULL;
+
+ g_mutex_lock (&priv->mutex);
+
+ g_clear_error (&priv->interaction_error);
+ g_signal_emit (self, signals[REQUEST_CERTIFICATE], 0,
+ priv->interaction,
+ &cert,
+ cancellable,
+ &priv->interaction_error,
+ &result);
+
+ if (cert)
+ *own_certificate = G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (self)->copy_certificate (self, cert);
+ else
+ *own_certificate = NULL;
+
+ g_mutex_unlock (&priv->mutex);
+
+ return result != G_TLS_INTERACTION_FAILED;
+}
+
+void
+g_tls_operations_thread_base_set_missing_requested_client_certificate (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ /* For client connections only */
+
+ g_mutex_lock (&priv->mutex);
+ priv->missing_requested_client_certificate = TRUE;
+ g_mutex_unlock (&priv->mutex);
+}
+
+static gboolean
+get_is_missing_requested_client_certificate (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ gboolean ret;
+
+ g_mutex_lock (&priv->mutex);
+ ret = priv->missing_requested_client_certificate;
+ g_mutex_unlock (&priv->mutex);
+
+ return ret;
+}
+
+void
+g_tls_operations_thread_base_set_close_notify_required (GTlsOperationsThreadBase *self,
+ gboolean required)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ g_mutex_lock (&priv->mutex);
+ priv->require_close_notify = required;
+ g_mutex_unlock (&priv->mutex);
+}
+
+gboolean
+g_tls_operations_thread_base_get_close_notify_required (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ gboolean ret;
+
+ g_mutex_lock (&priv->mutex);
+ ret = priv->require_close_notify;
+ g_mutex_unlock (&priv->mutex);
+
+ return ret;
+}
+
+static void
+set_performed_successful_posthandshake_op (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ g_mutex_lock (&priv->mutex);
+ priv->performed_successful_posthandshake_op = TRUE;
+ g_mutex_unlock (&priv->mutex);
+}
+
+static gboolean
+has_performed_successful_posthandshake_op (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ gboolean ret;
+
+ g_mutex_lock (&priv->mutex);
+ ret = priv->performed_successful_posthandshake_op;
+ g_mutex_unlock (&priv->mutex);
+
+ return ret;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_pop_io (GTlsOperationsThreadBase *self,
+ GIOCondition direction,
+ gboolean success,
+ GError *op_error /* owned */,
+ GError **error)
+{
+ /* This function MAY or MAY NOT set error when it fails!
+ * FIXME: that is confusing.
+ */
+
+ if (success)
+ {
+ g_assert (!op_error);
+ return G_TLS_OPERATION_SUCCESS;
+ }
+
+ if (g_error_matches (op_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ {
+ g_propagate_error (error, op_error);
+ return G_TLS_OPERATION_WOULD_BLOCK;
+ }
+
+ if (g_error_matches (op_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+ {
+ g_propagate_error (error, op_error);
+ return G_TLS_OPERATION_TIMED_OUT;
+ }
+
+ if (get_is_missing_requested_client_certificate (self) &&
+ !has_performed_successful_posthandshake_op (self))
+ {
+ GError *interaction_error;
+
+ interaction_error = take_interaction_error (self);
+
+ /* We are a client connection.
+ *
+ * Probably the server requires a client certificate, but we failed to
+ * provide one. With TLS 1.3 the server is no longer able to tell us
+ * this, so we just have to guess. If there is an error from the TLS
+ * interaction (request for user certificate), we provide that. Otherwise,
+ * guess that G_TLS_ERROR_CERTIFICATE_REQUIRED is probably appropriate.
+ * This could be wrong, but only applies to the small minority of
+ * connections where a client cert is requested but not provided, and then
+ * then only if the client has never successfully read or written.
+ */
+ if (interaction_error)
+ {
+ g_propagate_error (error, interaction_error);
+ }
+ else
+ {
+ g_clear_error (error);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
+ _("Server required TLS certificate"));
+ }
+
+ if (op_error)
+ g_error_free (op_error);
+ }
+ else if (op_error)
+ {
+ g_propagate_error (error, op_error);
+ }
+
+ return G_TLS_OPERATION_ERROR;
+}
+
+static HandshakeContext *
+handshake_context_new (GTlsVerifyCertificateFunc verify_callback,
+ gpointer user_data)
+{
+ HandshakeContext *context;
+
+ context = g_new0 (HandshakeContext, 1);
+ context->caller_context = g_main_context_ref_thread_default ();
+ context->verify_callback = verify_callback;
+ context->user_data = user_data;
+
+ return context;
+}
+
+static void
+handshake_context_free (HandshakeContext *context)
+{
+ g_main_context_unref (context->caller_context);
+
+ g_free (context);
+}
+
+static HandshakeData *
+handshake_data_new (HandshakeContext *context,
+ GTlsCertificate *own_certificate,
+ const gchar **advertised_protocols,
+ GTlsAuthenticationMode mode)
+{
+ HandshakeData *data;
+
+ data = g_new0 (HandshakeData, 1);
+ data->context = context;
+ data->own_certificate = own_certificate ? g_object_ref (own_certificate) : NULL;
+ data->advertised_protocols = g_strdupv ((gchar **)advertised_protocols);
+ data->auth_mode = mode;
+
+ return data;
+}
+
+static void
+handshake_data_free (HandshakeData *data)
+{
+ g_strfreev (data->advertised_protocols);
+
+ g_clear_object (&data->own_certificate);
+ g_clear_object (&data->peer_certificate);
+
+ g_assert (!data->accepted_cas);
+ g_assert (!data->negotiated_protocol);
+
+ g_free (data);
+}
+
+static GTlsThreadOperation *
+g_tls_thread_copy_client_session_state_operation_new (GTlsOperationsThreadBase *thread,
+ GTlsOperationsThreadBase *source)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_COPY_CLIENT_SESSION_STATE;
+ op->thread = thread;
+ op->source = source;
+
+ g_mutex_init (&op->finished_mutex);
+ g_cond_init (&op->finished_condition);
+
+ return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_set_server_identity_operation_new (GTlsOperationsThreadBase *thread,
+ const gchar *server_identity)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_SET_SERVER_IDENTITY;
+ op->thread = thread;
+ op->server_identity = g_strdup (server_identity);
+
+ g_mutex_init (&op->finished_mutex);
+ g_cond_init (&op->finished_condition);
+
+ return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_handshake_operation_new (GTlsOperationsThreadBase *thread,
+ HandshakeContext *context,
+ GTlsCertificate *own_certificate,
+ const gchar **advertised_protocols,
+ GTlsAuthenticationMode auth_mode,
+ gint64 timeout,
+ GCancellable *cancellable)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_HANDSHAKE;
+ op->io_condition = G_IO_IN | G_IO_OUT;
+ op->thread = thread;
+ op->timeout = timeout;
+ op->cancellable = cancellable;
+
+ op->handshake_data = handshake_data_new (context,
+ own_certificate,
+ advertised_protocols,
+ auth_mode);
+
+ g_mutex_init (&op->finished_mutex);
+ g_cond_init (&op->finished_condition);
+
+ return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_read_operation_new (GTlsOperationsThreadBase *thread,
+ void *data,
+ gsize size,
+ gint64 timeout,
+ GCancellable *cancellable)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_READ;
+ op->io_condition = G_IO_IN;
+ op->thread = thread;
+ op->data = data;
+ op->size = size;
+ op->timeout = timeout;
+ op->cancellable = cancellable;
+
+ g_mutex_init (&op->finished_mutex);
+ g_cond_init (&op->finished_condition);
+
+ return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_read_message_operation_new (GTlsOperationsThreadBase *thread,
+ GInputVector *vectors,
+ guint num_vectors,
+ gint64 timeout,
+ GCancellable *cancellable)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_READ_MESSAGE;
+ op->io_condition = G_IO_IN;
+ op->thread = thread;
+ op->input_vectors = vectors;
+ op->num_vectors = num_vectors;
+ op->timeout = timeout;
+ op->cancellable = cancellable;
+
+ g_mutex_init (&op->finished_mutex);
+ g_cond_init (&op->finished_condition);
+
+ return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_write_operation_new (GTlsOperationsThreadBase *thread,
+ const void *data,
+ gsize size,
+ gint64 timeout,
+ GCancellable *cancellable)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_WRITE;
+ op->io_condition = G_IO_OUT;
+ op->thread = thread;
+ op->data = (void *)data;
+ op->size = size;
+ op->timeout = timeout;
+ op->cancellable = cancellable;
+
+ g_mutex_init (&op->finished_mutex);
+ g_cond_init (&op->finished_condition);
+
+ return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_write_message_operation_new (GTlsOperationsThreadBase *thread,
+ GOutputVector *vectors,
+ guint num_vectors,
+ gint64 timeout,
+ GCancellable *cancellable)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_WRITE_MESSAGE;
+ op->io_condition = G_IO_OUT;
+ op->thread = thread;
+ op->output_vectors = vectors;
+ op->num_vectors = num_vectors;
+ op->timeout = timeout;
+ op->cancellable = cancellable;
+
+ g_mutex_init (&op->finished_mutex);
+ g_cond_init (&op->finished_condition);
+
+ return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_close_operation_new (GTlsOperationsThreadBase *thread,
+ GCancellable *cancellable)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_CLOSE;
+ op->io_condition = G_IO_IN | G_IO_OUT;
+ op->thread = thread;
+ op->timeout = -1;
+ op->cancellable = cancellable;
+
+ g_mutex_init (&op->finished_mutex);
+ g_cond_init (&op->finished_condition);
+
+ return op;
+}
+
+static GTlsThreadOperation *
+g_tls_thread_shutdown_operation_new (void)
+{
+ GTlsThreadOperation *op;
+
+ op = g_new0 (GTlsThreadOperation, 1);
+ op->type = G_TLS_THREAD_OP_SHUTDOWN_THREAD;
+
+ return op;
+}
+
+static void
+g_tls_thread_operation_free (GTlsThreadOperation *op)
+{
+ if (op->type == G_TLS_THREAD_OP_SET_SERVER_IDENTITY)
+ g_free (op->server_identity);
+
+ if (op->type == G_TLS_THREAD_OP_HANDSHAKE)
+ handshake_data_free (op->handshake_data);
+
+ if (op->type != G_TLS_THREAD_OP_SHUTDOWN_THREAD)
+ {
+ g_mutex_clear (&op->finished_mutex);
+ g_cond_clear (&op->finished_condition);
+ }
+
+ g_free (op);
+}
+
+static void
+wait_for_op_completion (GTlsThreadOperation *op)
+{
+ g_mutex_lock (&op->finished_mutex);
+ while (!op->finished)
+ g_cond_wait (&op->finished_condition, &op->finished_mutex);
+ g_mutex_unlock (&op->finished_mutex);
+}
+
+static GTlsOperationStatus
+execute_op (GTlsOperationsThreadBase *self,
+ GTlsThreadOperation *op,
+ gssize *count,
+ GError **error)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsOperationStatus result;
+
+ g_async_queue_push (priv->queue, op);
+ g_main_context_wakeup (priv->op_thread_context);
+
+ wait_for_op_completion (op);
+
+ if (count)
+ *count = op->count;
+
+ result = op->result;
+
+ if (op->error)
+ {
+ g_propagate_error (error, op->error);
+ op->error = NULL;
+ }
+
+ return result;
+}
+
+void
+g_tls_operations_thread_base_copy_client_session_state (GTlsOperationsThreadBase *self,
+ GTlsOperationsThreadBase *source)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsThreadOperation *op;
+
+ g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+ op = g_tls_thread_copy_client_session_state_operation_new (self, source);
+ execute_op (self, op, NULL, NULL);
+ g_tls_thread_operation_free (op);
+}
+
+void
+g_tls_operations_thread_base_set_server_identity (GTlsOperationsThreadBase *self,
+ const gchar *server_identity)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsThreadOperation *op;
+
+ g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+ op = g_tls_thread_set_server_identity_operation_new (self, server_identity);
+ execute_op (self, op, NULL, NULL);
+ g_tls_thread_operation_free (op);
+}
+
+typedef struct {
+ GTlsOperationsThreadBase *thread;
+ GTlsCertificate *peer_certificate;
+ HandshakeContext *context;
+
+ gboolean result;
+ gboolean complete;
+ GMutex mutex;
+ GCond condition;
+} VerifyCertificateData;
+
+static VerifyCertificateData *
+verify_certificate_data_new (GTlsOperationsThreadBase *thread,
+ GTlsCertificate *peer_certificate,
+ HandshakeContext *context)
+{
+ VerifyCertificateData *data;
+
+ data = g_new0 (VerifyCertificateData, 1);
+ data->thread = g_object_ref (thread);
+ data->peer_certificate = g_object_ref (peer_certificate);
+ data->context = context;
+
+ g_mutex_init (&data->mutex);
+ g_cond_init (&data->condition);
+
+ return data;
+}
+
+static void
+verify_certificate_data_free (VerifyCertificateData *data)
+{
+ g_object_unref (data->thread);
+ g_object_unref (data->peer_certificate);
+
+ g_mutex_clear (&data->mutex);
+ g_cond_clear (&data->condition);
+
+ g_free (data);
+}
+
+static gboolean
+execute_verify_certificate_callback_cb (VerifyCertificateData *data)
+{
+ data->result = data->context->verify_callback (data->thread,
+ data->peer_certificate,
+ data->context->user_data);
+
+ g_mutex_lock (&data->mutex);
+ data->complete = TRUE;
+ g_cond_signal (&data->condition);
+ g_mutex_unlock (&data->mutex);
+
+ return G_SOURCE_REMOVE;
+}
+
+gboolean
+g_tls_operations_thread_base_verify_certificate (GTlsOperationsThreadBase *self,
+ GTlsCertificate *peer_certificate,
+ HandshakeContext *context)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ VerifyCertificateData *data;
+ gboolean accepted;
+
+ g_assert (g_main_context_is_owner (priv->op_thread_context));
+ g_assert (G_IS_TLS_CERTIFICATE (peer_certificate));
+ g_assert (context);
+
+ data = verify_certificate_data_new (self, peer_certificate, context);
+
+ /* Invoke the caller's callback on the calling thread, not the op thread. */
+ g_main_context_invoke (context->caller_context,
+ (GSourceFunc)execute_verify_certificate_callback_cb,
+ data);
+
+ /* Block the op thread until the calling thread's callback finishes. */
+ g_mutex_lock (&data->mutex);
+ while (!data->complete)
+ g_cond_wait (&data->condition, &data->mutex);
+ g_mutex_unlock (&data->mutex);
+
+ accepted = data->result;
+
+ verify_certificate_data_free (data);
+
+ return accepted;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_handshake (GTlsOperationsThreadBase *self,
+ GTlsCertificate *own_certificate,
+ const gchar **advertised_protocols,
+ GTlsAuthenticationMode auth_mode,
+ gint64 timeout,
+ GTlsVerifyCertificateFunc verify_callback,
+ GTlsSessionResumedFunc resumed_callback,
+ gchar **negotiated_protocol,
+ GList **accepted_cas,
+ GCancellable *cancellable,
+ gpointer user_data,
+ GError **error)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsOperationStatus status;
+ GTlsThreadOperation *op;
+ GTlsCertificate *copied_cert;
+ HandshakeContext *context;
+
+ g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+ g_mutex_lock (&priv->mutex);
+ priv->missing_requested_client_certificate = FALSE;
+ g_mutex_unlock (&priv->mutex);
+
+ context = handshake_context_new (verify_callback,
+ user_data);
+
+ copied_cert = G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (self)->copy_certificate (self,
+ own_certificate);
+
+ op = g_tls_thread_handshake_operation_new (self,
+ context,
+ copied_cert,
+ advertised_protocols,
+ auth_mode,
+ timeout,
+ cancellable);
+ status = execute_op (self, op, NULL, error);
+
+ if (op->handshake_data->session_resumed)
+ resumed_callback (self, op->handshake_data->peer_certificate, user_data);
+
+ *negotiated_protocol = g_steal_pointer (&op->handshake_data->negotiated_protocol);
+ *accepted_cas = g_steal_pointer (&op->handshake_data->accepted_cas);
+
+ handshake_context_free (context);
+ g_tls_thread_operation_free (op);
+ g_clear_object (&copied_cert);
+
+ return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_read (GTlsOperationsThreadBase *self,
+ void *buffer,
+ gsize size,
+ gint64 timeout,
+ gssize *nread,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsOperationStatus status;
+ GTlsThreadOperation *op;
+
+ g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+ op = g_tls_thread_read_operation_new (self,
+ buffer, size,
+ timeout,
+ cancellable);
+ status = execute_op (self, op, nread, error);
+ g_tls_thread_operation_free (op);
+
+ return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_read_message (GTlsOperationsThreadBase *self,
+ GInputVector *vectors,
+ guint num_vectors,
+ gint64 timeout,
+ gssize *nread,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsOperationStatus status;
+ GTlsThreadOperation *op;
+
+ g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+ op = g_tls_thread_read_message_operation_new (self,
+ vectors, num_vectors,
+ timeout,
+ cancellable);
+ status = execute_op (self, op, nread, error);
+ g_tls_thread_operation_free (op);
+
+ return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_write (GTlsOperationsThreadBase *self,
+ const void *buffer,
+ gsize size,
+ gint64 timeout,
+ gssize *nwrote,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsOperationStatus status;
+ GTlsThreadOperation *op;
+
+ g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+ op = g_tls_thread_write_operation_new (self,
+ buffer, size,
+ timeout,
+ cancellable);
+ status = execute_op (self, op, nwrote, error);
+ g_tls_thread_operation_free (op);
+
+ return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_write_message (GTlsOperationsThreadBase *self,
+ GOutputVector *vectors,
+ guint num_vectors,
+ gint64 timeout,
+ gssize *nwrote,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsOperationStatus status;
+ GTlsThreadOperation *op;
+
+ g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+ op = g_tls_thread_write_message_operation_new (self,
+ vectors, num_vectors,
+ timeout,
+ cancellable);
+ status = execute_op (self, op, nwrote, error);
+ g_tls_thread_operation_free (op);
+
+ return status;
+}
+
+GTlsOperationStatus
+g_tls_operations_thread_base_close (GTlsOperationsThreadBase *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsOperationStatus status;
+ GTlsThreadOperation *op;
+
+ g_assert (!g_main_context_is_owner (priv->op_thread_context));
+
+ op = g_tls_thread_close_operation_new (self, cancellable);
+ status = execute_op (self, op, NULL, error);
+ g_tls_thread_operation_free (op);
+
+ return status;
+}
+
+GIOStream *
+g_tls_operations_thread_base_get_base_iostream (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ return priv->base_iostream;
+}
+
+GDatagramBased *
+g_tls_operations_thread_base_get_base_socket (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ return priv->base_socket;
+}
+
+gboolean
+g_tls_operations_thread_base_check (GTlsOperationsThreadBase *self,
+ GIOCondition condition)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ if (is_dtls (self))
+ return g_datagram_based_condition_check (priv->base_socket, condition);
+
+ if (condition & G_IO_IN)
+ {
+ GInputStream *istream = g_io_stream_get_input_stream (priv->base_iostream);
+ return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (istream));
+ }
+
+ if (condition & G_IO_OUT)
+ {
+ GOutputStream *ostream = g_io_stream_get_output_stream (priv->base_iostream);
+ return g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (ostream));
+ }
+
+ g_assert_not_reached ();
+ return FALSE;
+}
+
+static GSource *
+create_base_source (GTlsOperationsThreadBase *self,
+ GIOCondition condition,
+ GCancellable *cancellable)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ if (is_dtls (self))
+ return g_datagram_based_create_source (priv->base_socket, condition, cancellable);
+
+ if (condition & G_IO_IN)
+ {
+ GInputStream *istream = g_io_stream_get_input_stream (priv->base_iostream);
+ return g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (istream), cancellable);
+ }
+
+ if (condition & G_IO_OUT)
+ {
+ GOutputStream *ostream = g_io_stream_get_output_stream (priv->base_iostream);
+ return g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (ostream), cancellable);
+ }
+
+ g_assert_not_reached ();
+}
+
+typedef struct {
+ GSource source;
+
+ GAsyncQueue *queue;
+} GTlsOpQueueSource;
+
+typedef gboolean (*GTlsOpQueueSourceFunc) (GAsyncQueue *queue,
+ GTlsThreadOperation *op,
+ GMainLoop *main_loop);
+
+static gboolean
+queue_has_pending_op (GAsyncQueue *queue)
+{
+ GTlsThreadOperation *op;
+ gboolean ready = FALSE;
+
+ g_async_queue_lock (queue);
+
+ op = g_async_queue_try_pop_unlocked (queue);
+ if (op)
+ {
+ g_async_queue_push_front_unlocked (queue, op);
+ ready = TRUE;
+ }
+
+ g_async_queue_unlock (queue);
+
+ return ready;
+}
+
+static gboolean
+tls_op_queue_source_prepare (GSource *source,
+ gint *timeout)
+{
+ GTlsOpQueueSource *op_source = (GTlsOpQueueSource *)source;
+ gboolean ready;
+
+ ready = queue_has_pending_op (op_source->queue);
+
+ /* If we are ready to dispatch, timeout should be 0 to ensure poll() returns
+ * immediately. Otherwise, we are in no hurry and can wait "forever." If
+ * a new op is pushed onto the queue, the code performing the push is
+ * responsible for calling g_main_context_wakeup() to end the wait.
+ */
+ *timeout = ready ? 0 : -1;
+
+ return ready;
+}
+
+static gboolean
+tls_op_queue_source_check (GSource *source)
+{
+ GTlsOpQueueSource *op_source = (GTlsOpQueueSource *)source;
+
+ return queue_has_pending_op (op_source->queue);
+}
+
+static gboolean
+tls_op_queue_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ GTlsOpQueueSource *op_source = (GTlsOpQueueSource *)source;
+
+ return ((GTlsOpQueueSourceFunc)callback) (op_source->queue,
+ NULL, /* no delayed source */
+ user_data);
+}
+
+static void
+tls_op_queue_source_finalize (GSource *source)
+{
+ GTlsOpQueueSource *op_source = (GTlsOpQueueSource *)source;
+
+ g_async_queue_unref (op_source->queue);
+}
+
+static gboolean
+tls_op_queue_source_closure_callback (GAsyncQueue *queue,
+ GMainLoop *main_loop,
+ gpointer data)
+{
+ GClosure *closure = data;
+
+ GValue param[3] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT };
+ GValue result_value = G_VALUE_INIT;
+ gboolean result;
+
+ g_value_init (&result_value, G_TYPE_BOOLEAN);
+
+ g_value_init (¶m[0], G_TYPE_POINTER);
+ g_value_set_pointer (¶m[0], queue);
+ g_value_init (¶m[1], G_TYPE_POINTER);
+ g_value_set_pointer (¶m[1], NULL);
+ g_value_init (¶m[2], G_TYPE_MAIN_LOOP);
+ g_value_set_pointer (¶m[2], main_loop);
+
+ g_closure_invoke (closure, &result_value, 3, param, NULL);
+
+ result = g_value_get_boolean (&result_value);
+ g_value_unset (&result_value);
+ g_value_unset (¶m[0]);
+ g_value_unset (¶m[1]);
+ g_value_unset (¶m[2]);
+
+ return result;
+}
+
+static GSourceFuncs tls_op_queue_source_funcs =
+{
+ tls_op_queue_source_prepare,
+ tls_op_queue_source_check,
+ tls_op_queue_source_dispatch,
+ tls_op_queue_source_finalize,
+ (GSourceFunc)tls_op_queue_source_closure_callback,
+ (GSourceDummyMarshal)g_cclosure_marshal_generic
+};
+
+/* TODO: Move this into GLib so we don't need a custom source. glib#94 */
+static GSource *
+tls_op_queue_source_new (GAsyncQueue *queue)
+{
+ GTlsOpQueueSource *source;
+
+ source = (GTlsOpQueueSource *)g_source_new (&tls_op_queue_source_funcs, sizeof (GTlsOpQueueSource));
+ source->queue = g_async_queue_ref (queue);
+
+ return (GSource *)source;
+}
+
+typedef struct
+{
+ GAsyncQueue *queue;
+ GTlsThreadOperation *op;
+ GMainLoop *main_loop;
+} DelayedOpAsyncData;
+
+static DelayedOpAsyncData *
+delayed_op_async_data_new (GAsyncQueue *queue,
+ GTlsThreadOperation *op,
+ GMainLoop *main_loop)
+{
+ DelayedOpAsyncData *data;
+
+ data = g_new (DelayedOpAsyncData, 1);
+
+ /* No refs because these are guaranteed to outlive data. */
+ data->queue = queue;
+ data->op = op;
+ data->main_loop = main_loop;
+
+ return data;
+}
+
+static void
+delayed_op_async_data_free (DelayedOpAsyncData *data)
+{
+ g_free (data);
+}
+
+static gboolean
+resume_tls_op (GObject *pollable_stream,
+ gpointer user_data)
+{
+ DelayedOpAsyncData *data = (DelayedOpAsyncData *)user_data;
+ gboolean ret;
+
+ ret = process_op (data->queue, data->op, data->main_loop);
+ g_assert (ret == G_SOURCE_CONTINUE);
+
+ delayed_op_async_data_free (data);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+resume_dtls_op (GDatagramBased *datagram_based,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ DelayedOpAsyncData *data = (DelayedOpAsyncData *)user_data;
+ gboolean ret;
+
+ ret = process_op (data->queue, data->op, data->main_loop);
+ g_assert (ret == G_SOURCE_CONTINUE);
+
+ delayed_op_async_data_free (data);
+
+ return G_SOURCE_REMOVE;
+}
+
+/* Use a custom dummy callback instead of g_source_set_dummy_callback(), as that
+ * uses a GClosure and is slow. (The GClosure is necessary to deal with any
+ * function prototype.)
+ */
+static gboolean
+dummy_callback (gpointer data)
+{
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+adjust_op_timeout (GTlsThreadOperation *op)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (op->thread);
+ GSocket *socket = NULL;
+
+ /* Nonblocking? */
+ if (op->timeout == 0)
+ return;
+
+ if (is_dtls (op->thread))
+ {
+ if (G_IS_SOCKET (priv->base_socket))
+ socket = (GSocket *)priv->base_socket;
+ }
+ else
+ {
+ if (G_IS_SOCKET_CONNECTION (priv->base_iostream))
+ socket = g_socket_connection_get_socket ((GSocketConnection *)priv->base_iostream);
+ }
+
+ /* We have to "massage" the timeout here because we are using only nonblocking
+ * I/O, so the underlying socket will never time out even if a timeout has
+ * been set. But if we are emulating a blocking operation, we need to make
+ * sure we don't block for longer than the underyling timeout.
+ */
+ if (socket)
+ {
+ gint64 socket_timeout = g_socket_get_timeout (socket);
+
+ if (socket_timeout > 0)
+ {
+ if (op->timeout == -1)
+ op->timeout = socket_timeout;
+
+ g_assert (op->timeout > 0);
+ op->timeout = MIN (op->timeout, socket_timeout);
+ }
+ }
+}
+
+static gboolean
+process_op (GAsyncQueue *queue,
+ GTlsThreadOperation *delayed_op,
+ GMainLoop *main_loop)
+{
+ GTlsThreadOperation *op;
+ GTlsOperationsThreadBaseClass *base_class;
+ gboolean performed_posthandshake_op = FALSE;
+
+ if (delayed_op)
+ {
+ op = delayed_op;
+ g_clear_error (&op->error);
+
+ if (op->timeout != -1)
+ {
+ op->timeout -= g_get_monotonic_time () - op->start_time;
+ op->timeout = MAX (op->timeout, 0);
+ }
+
+ g_assert (op->io_condition != 0);
+ if (!g_tls_operations_thread_base_check (op->thread, op->io_condition))
+ {
+ /* Not ready for I/O. Either we timed out, or were cancelled, or we
+ * could have a spurious wakeup caused by GTlsConnectionBase yield_op.
+ */
+ if (g_cancellable_is_cancelled (op->cancellable))
+ {
+ op->count = 0;
+ g_set_error (&op->error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ _("Operation cancelled"));
+ goto finished;
+ }
+
+ if (op->timeout == 0)
+ {
+ op->count = 0;
+ g_set_error (&op->error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+ _("Socket I/O timed out"));
+ goto finished;
+ }
+
+ /* Spurious wakeup. Try again later. */
+ op->result = G_TLS_OPERATION_WOULD_BLOCK;
+ goto wait;
+ }
+ }
+ else
+ {
+ op = g_async_queue_try_pop (queue);
+ g_assert (op);
+
+ if (op->type == G_TLS_THREAD_OP_SHUTDOWN_THREAD)
+ {
+ g_main_loop_quit (main_loop);
+ return G_SOURCE_REMOVE;
+ }
+
+ adjust_op_timeout (op);
+ }
+
+ if (op->type != G_TLS_THREAD_OP_SHUTDOWN_THREAD)
+ {
+ g_assert (op->thread);
+ base_class = G_TLS_OPERATIONS_THREAD_BASE_GET_CLASS (op->thread);
+ }
+
+ switch (op->type)
+ {
+ case G_TLS_THREAD_OP_COPY_CLIENT_SESSION_STATE:
+ if (base_class->copy_client_session_state)
+ base_class->copy_client_session_state (op->thread, op->source);
+ break;
+ case G_TLS_THREAD_OP_SET_SERVER_IDENTITY:
+ g_assert (base_class->set_server_identity);
+ base_class->set_server_identity (op->thread,
+ op->server_identity);
+ break;
+ case G_TLS_THREAD_OP_HANDSHAKE:
+ op->result = base_class->handshake_fn (op->thread,
+ op->handshake_data->context,
+ op->handshake_data->own_certificate,
+ (const gchar **)op->handshake_data->advertised_protocols,
+ op->handshake_data->auth_mode,
+ op->timeout,
+ &op->handshake_data->negotiated_protocol,
+ &op->handshake_data->accepted_cas,
+ &op->handshake_data->peer_certificate,
+ &op->handshake_data->session_resumed,
+ op->cancellable,
+ &op->error);
+ break;
+ case G_TLS_THREAD_OP_READ:
+ op->result = base_class->read_fn (op->thread,
+ op->data, op->size,
+ &op->count,
+ op->cancellable,
+ &op->error);
+ performed_posthandshake_op = TRUE;
+ break;
+ case G_TLS_THREAD_OP_READ_MESSAGE:
+ g_assert (base_class->read_message_fn);
+ op->result = base_class->read_message_fn (op->thread,
+ op->input_vectors, op->num_vectors,
+ &op->count,
+ op->cancellable,
+ &op->error);
+ performed_posthandshake_op = TRUE;
+ break;
+ case G_TLS_THREAD_OP_WRITE:
+ op->result = base_class->write_fn (op->thread,
+ op->data, op->size,
+ &op->count,
+ op->cancellable,
+ &op->error);
+ performed_posthandshake_op = TRUE;
+ break;
+ case G_TLS_THREAD_OP_WRITE_MESSAGE:
+ g_assert (base_class->write_message_fn);
+ op->result = base_class->write_message_fn (op->thread,
+ op->output_vectors, op->num_vectors,
+ &op->count,
+ op->cancellable,
+ &op->error);
+ performed_posthandshake_op = TRUE;
+ break;
+ case G_TLS_THREAD_OP_CLOSE:
+ op->result = base_class->close_fn (op->thread,
+ op->cancellable,
+ &op->error);
+ performed_posthandshake_op = TRUE;
+ break;
+ case G_TLS_THREAD_OP_SHUTDOWN_THREAD:
+ g_assert_not_reached ();
+ }
+
+ if (op->result == G_TLS_OPERATION_SUCCESS && performed_posthandshake_op)
+ set_performed_successful_posthandshake_op (op->thread);
+
+wait:
+ if (op->result == G_TLS_OPERATION_WOULD_BLOCK &&
+ op->timeout != 0)
+ {
+ GSource *tls_source;
+ GSource *timeout_source;
+ GMainContext *main_context;
+ DelayedOpAsyncData *data;
+
+ tls_source = create_base_source (op->thread,
+ op->io_condition,
+ op->cancellable);
+ if (op->timeout > 0)
+ {
+ op->start_time = g_get_monotonic_time ();
+
+ /* tls_source should fire if (a) we're ready to ready/write without
+ * blocking, or (b) the timeout has elasped.
+ */
+ timeout_source = g_timeout_source_new (op->timeout);
+ g_source_set_callback (timeout_source, dummy_callback, NULL, NULL);
+ g_source_add_child_source (tls_source, timeout_source);
+ g_source_unref (timeout_source);
+ }
+
+ data = delayed_op_async_data_new (queue, op, main_loop);
+ if (is_dtls (op->thread))
+ g_source_set_callback (tls_source, G_SOURCE_FUNC (resume_dtls_op), data, NULL);
+ else
+ g_source_set_callback (tls_source, G_SOURCE_FUNC (resume_tls_op), data, NULL);
+
+ main_context = g_main_loop_get_context (main_loop);
+ g_source_attach (tls_source, main_context);
+ g_source_unref (tls_source);
+
+ return G_SOURCE_CONTINUE;
+ }
+
+finished:
+ g_mutex_lock (&op->finished_mutex);
+ op->finished = TRUE;
+ g_cond_signal (&op->finished_condition);
+ g_mutex_unlock (&op->finished_mutex);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gpointer
+tls_op_thread (gpointer data)
+{
+ GTlsOperationsThreadBase *self = G_TLS_OPERATIONS_THREAD_BASE (data);
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GMainLoop *main_loop;
+ GSource *source;
+
+ main_loop = g_main_loop_new (priv->op_thread_context, FALSE);
+
+ g_main_context_push_thread_default (priv->op_thread_context);
+
+ source = tls_op_queue_source_new (priv->queue);
+ g_source_set_callback (source, G_SOURCE_FUNC (process_op), main_loop, NULL);
+ g_source_attach (source, priv->op_thread_context);
+ g_source_unref (source);
+
+ g_main_loop_run (main_loop);
+
+ g_assert (!queue_has_pending_op (priv->queue));
+
+ g_main_context_pop_thread_default (priv->op_thread_context);
+
+ g_main_loop_unref (main_loop);
+
+ return NULL;
+}
+
+static void
+g_tls_operations_thread_base_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GTlsOperationsThreadBase *self = G_TLS_OPERATIONS_THREAD_BASE (object);
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_BASE_IO_STREAM:
+ g_value_set_object (value, priv->base_iostream);
+ break;
+
+ case PROP_BASE_SOCKET:
+ g_value_set_object (value, priv->base_socket);
+ break;
+
+ case PROP_THREAD_TYPE:
+ g_value_set_enum (value, priv->thread_type);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+g_tls_operations_thread_base_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GTlsOperationsThreadBase *self = G_TLS_OPERATIONS_THREAD_BASE (object);
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_BASE_IO_STREAM:
+ priv->base_iostream = g_value_dup_object (value);
+ if (priv->base_iostream)
+ g_assert (!priv->base_socket);
+ break;
+
+ case PROP_BASE_SOCKET:
+ priv->base_socket = g_value_dup_object (value);
+ if (priv->base_socket)
+ g_assert (!priv->base_iostream);
+ break;
+
+ case PROP_THREAD_TYPE:
+ priv->thread_type = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static gboolean
+g_tls_operations_thread_base_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return TRUE;
+}
+
+static void
+g_tls_operations_thread_base_init (GTlsOperationsThreadBase *self)
+{
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+
+ priv->queue = g_async_queue_new ();
+ priv->op_thread_context = g_main_context_new ();
+ priv->op_thread = g_thread_new ("[glib-networking] GTlsOperationsThreadBase TLS operations thread",
+ tls_op_thread,
+ self);
+
+ g_mutex_init (&priv->mutex);
+}
+
+static void
+g_tls_operations_thread_base_finalize (GObject *object)
+{
+ GTlsOperationsThreadBase *self = G_TLS_OPERATIONS_THREAD_BASE (object);
+ GTlsOperationsThreadBasePrivate *priv = g_tls_operations_thread_base_get_instance_private (self);
+ GTlsThreadOperation *op;
+
+ op = g_tls_thread_shutdown_operation_new ();
+ g_async_queue_push (priv->queue, op);
+ g_main_context_wakeup (priv->op_thread_context);
+
+ g_clear_pointer (&priv->op_thread, g_thread_join);
+ g_clear_pointer (&priv->op_thread_context, g_main_context_unref);
+ g_clear_pointer (&priv->queue, g_async_queue_unref);
+ g_tls_thread_operation_free (op);
+
+ g_clear_object (&priv->base_iostream);
+ g_clear_object (&priv->base_socket);
+
+ g_mutex_clear (&priv->mutex);
+
+ g_clear_object (&priv->interaction);
+ g_clear_error (&priv->interaction_error);
+
+ G_OBJECT_CLASS (g_tls_operations_thread_base_parent_class)->finalize (object);
+}
+
+static void
+g_tls_operations_thread_base_class_init (GTlsOperationsThreadBaseClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = g_tls_operations_thread_base_finalize;
+ gobject_class->get_property = g_tls_operations_thread_base_get_property;
+ gobject_class->set_property = g_tls_operations_thread_base_set_property;
+
+ signals[REQUEST_CERTIFICATE] =
+ g_signal_new ("operations-thread-request-certificate",
+ G_TYPE_TLS_OPERATIONS_THREAD_BASE,
+ G_SIGNAL_RUN_LAST, 0,
+ g_signal_accumulator_first_wins,
+ NULL, NULL,
+ G_TYPE_TLS_INTERACTION_RESULT, 4,
+ G_TYPE_TLS_INTERACTION,
+ G_TYPE_POINTER,
+ G_TYPE_CANCELLABLE,
+ G_TYPE_POINTER);
+
+ obj_properties[PROP_BASE_IO_STREAM] =
+ g_param_spec_object ("base-io-stream",
+ "Base IOStream",
+ "The underlying GIOStream, for TLS connections",
+ G_TYPE_IO_STREAM,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_BASE_SOCKET] =
+ g_param_spec_object ("base-socket",
+ "Base socket",
+ "The underlying GDatagramBased, for DTLS connections",
+ G_TYPE_DATAGRAM_BASED,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_THREAD_TYPE] =
+ g_param_spec_enum ("thread-type",
+ "Thread type",
+ "Whether this thread runs a TLS client or server",
+ G_TYPE_TLS_OPERATIONS_THREAD_TYPE,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
+}
+
+static void
+g_tls_operations_thread_base_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = g_tls_operations_thread_base_initable_init;
+}
diff --git a/tls/base/gtlsoperationsthread-base.h b/tls/base/gtlsoperationsthread-base.h
new file mode 100644
index 0000000..3f4fdca
--- /dev/null
+++ b/tls/base/gtlsoperationsthread-base.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#pragma once
+
+#include "gtlsconnection-base.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TLS_OPERATIONS_THREAD_BASE (g_tls_operations_thread_base_get_type ())
+
+G_DECLARE_DERIVABLE_TYPE (GTlsOperationsThreadBase, g_tls_operations_thread_base, G,
TLS_OPERATIONS_THREAD_BASE, GObject)
+
+typedef enum {
+ G_TLS_OPERATION_SUCCESS,
+ G_TLS_OPERATION_WOULD_BLOCK,
+ G_TLS_OPERATION_TIMED_OUT,
+ G_TLS_OPERATION_TRY_AGAIN,
+ G_TLS_OPERATION_ERROR,
+} GTlsOperationStatus;
+
+typedef enum {
+ G_TLS_OPERATIONS_THREAD_CLIENT,
+ G_TLS_OPERATIONS_THREAD_SERVER
+} GTlsOperationsThreadType;
+
+typedef struct _HandshakeContext HandshakeContext;
+
+struct _GTlsOperationsThreadBaseClass
+{
+ GObjectClass parent_class;
+
+ GTlsCertificate *(*copy_certificate) (GTlsOperationsThreadBase *self,
+ GTlsCertificate *cert);
+
+ void (*copy_client_session_state) (GTlsOperationsThreadBase *self,
+ GTlsOperationsThreadBase *source);
+
+ void (*set_server_identity) (GTlsOperationsThreadBase *self,
+ const gchar *server_identity);
+
+ GTlsOperationStatus (*handshake_fn) (GTlsOperationsThreadBase *self,
+ HandshakeContext *context,
+ GTlsCertificate *own_certificate,
+ const gchar **advertised_protocols,
+ GTlsAuthenticationMode auth_mode,
+ gint64 timeout,
+ gchar **negotiated_protocol,
+ GList **accepted_cas,
+ GTlsCertificate **peer_certificate,
+ gboolean *session_resumed,
+ GCancellable *cancellable,
+ GError **error);
+
+ GTlsOperationStatus (*read_fn) (GTlsOperationsThreadBase *self,
+ void *buffer,
+ gsize size,
+ gssize *nread,
+ GCancellable *cancellable,
+ GError **error);
+ GTlsOperationStatus (*read_message_fn) (GTlsOperationsThreadBase *self,
+ GInputVector *vectors,
+ guint num_vectors,
+ gssize *nread,
+ GCancellable *cancellable,
+ GError **error);
+
+ GTlsOperationStatus (*write_fn) (GTlsOperationsThreadBase *self,
+ const void *buffer,
+ gsize size,
+ gssize *nwrote,
+ GCancellable *cancellable,
+ GError **error);
+ GTlsOperationStatus (*write_message_fn) (GTlsOperationsThreadBase *self,
+ GOutputVector *vectors,
+ guint num_vectors,
+ gssize *nwrote,
+ GCancellable *cancellable,
+ GError **error);
+
+ GTlsOperationStatus (*close_fn) (GTlsOperationsThreadBase *self,
+ GCancellable *cancellable,
+ GError **error);
+};
+
+typedef gboolean (*GTlsVerifyCertificateFunc) (GTlsOperationsThreadBase *thread,
+ GTlsCertificate *peer_certificate,
+ gpointer user_data);
+typedef void (*GTlsSessionResumedFunc) (GTlsOperationsThreadBase *thread,
+ GTlsCertificate *peer_certificate,
+ gpointer user_data);
+
+void g_tls_operations_thread_base_set_interaction (GTlsOperationsThreadBase
*self,
+ GTlsInteraction
*interaction);
+GTlsInteraction *g_tls_operations_thread_base_ref_interaction (GTlsOperationsThreadBase
*self);
+GError *g_tls_operations_thread_base_take_interaction_error (GTlsOperationsThreadBase
*self);
+
+gboolean g_tls_operations_thread_base_request_certificate (GTlsOperationsThreadBase
*self,
+ GCancellable
*cancellable,
+ GTlsCertificate
**own_certificate);
+
+void g_tls_operations_thread_base_set_missing_requested_client_certificate
+ (GTlsOperationsThreadBase
*self);
+
+void g_tls_operations_thread_base_set_close_notify_required (GTlsOperationsThreadBase *self,
+ gboolean
required);
+gboolean g_tls_operations_thread_base_get_close_notify_required (GTlsOperationsThreadBase
*self);
+
+gboolean g_tls_operations_thread_base_verify_certificate (GTlsOperationsThreadBase *self,
+ GTlsCertificate
*peer_certificate,
+ HandshakeContext
*context);
+
+void g_tls_operations_thread_base_copy_client_session_state (GTlsOperationsThreadBase
*self,
+ GTlsOperationsThreadBase
*source);
+
+void g_tls_operations_thread_base_set_server_identity (GTlsOperationsThreadBase
*self,
+ const gchar
*server_identity);
+
+void g_tls_operations_thread_base_push_io (GTlsOperationsThreadBase
*self,
+ GIOCondition
direction,
+ GCancellable
*cancellable);
+GTlsOperationStatus g_tls_operations_thread_base_pop_io (GTlsOperationsThreadBase
*self,
+ GIOCondition
direction,
+ gboolean
success,
+ GError
*op_error,
+ GError
**error);
+
+GTlsOperationStatus g_tls_operations_thread_base_handshake (GTlsOperationsThreadBase
*self,
+ GTlsCertificate
*own_certificate,
+ const gchar
**advertised_protocols,
+ GTlsAuthenticationMode
auth_mode,
+ gint64
timeout,
+ GTlsVerifyCertificateFunc
verify_callback,
+ GTlsSessionResumedFunc
resumed_callback,
+ gchar
**negotiated_protocol,
+ GList
**accepted_cas,
+ GCancellable
*cancellable,
+ gpointer
user_data,
+ GError
**error);
+
+GTlsOperationStatus g_tls_operations_thread_base_read (GTlsOperationsThreadBase
*self,
+ void
*buffer,
+ gsize
size,
+ gint64
timeout,
+ gssize
*nread,
+ GCancellable
*cancellable,
+ GError
**error);
+
+GTlsOperationStatus g_tls_operations_thread_base_read_message (GTlsOperationsThreadBase
*self,
+ GInputVector
*vectors,
+ guint
num_vectors,
+ gint64
timeout,
+ gssize
*nread,
+ GCancellable
*cancellable,
+ GError
**error);
+
+GTlsOperationStatus g_tls_operations_thread_base_write (GTlsOperationsThreadBase
*self,
+ const void
*buffer,
+ gsize
size,
+ gint64
timeout,
+ gssize
*nwrote,
+ GCancellable
*cancellable,
+ GError
**error);
+
+GTlsOperationStatus g_tls_operations_thread_base_write_message (GTlsOperationsThreadBase
*self,
+ GOutputVector
*vectors,
+ guint
num_vectors,
+ gint64
timeout,
+ gssize
*nwrote,
+ GCancellable
*cancellable,
+ GError
**error);
+
+GTlsOperationStatus g_tls_operations_thread_base_close (GTlsOperationsThreadBase
*self,
+ GCancellable
*cancellable,
+ GError
**error);
+
+GIOStream *g_tls_operations_thread_base_get_base_iostream (GTlsOperationsThreadBase
*self);
+GDatagramBased *g_tls_operations_thread_base_get_base_socket (GTlsOperationsThreadBase
*self);
+
+gboolean g_tls_operations_thread_base_check (GTlsOperationsThreadBase
*self,
+ GIOCondition
condition);
+
+G_END_DECLS
diff --git a/tls/base/meson.build b/tls/base/meson.build
index ca7d5a3..70a897a 100644
--- a/tls/base/meson.build
+++ b/tls/base/meson.build
@@ -1,9 +1,18 @@
-tlsbase_sources = files(
+enums_headers = [
+ 'gtlsoperationsthread-base.h'
+]
+
+enums = gnome.mkenums_simple('tls-base-builtins',
+ sources: enums_headers)
+
+tlsbase_sources = [
'gtlsconnection-base.c',
'gtlsinputstream.c',
'gtlslog.c',
+ 'gtlsoperationsthread-base.c',
'gtlsoutputstream.c',
-)
+ enums
+]
tlsbase = static_library('tlsbase',
tlsbase_sources,
diff --git a/tls/gnutls/gtlsbackend-gnutls.c b/tls/gnutls/gtlsbackend-gnutls.c
index bcc7f92..4949e0c 100644
--- a/tls/gnutls/gtlsbackend-gnutls.c
+++ b/tls/gnutls/gtlsbackend-gnutls.c
@@ -3,6 +3,8 @@
* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2010 Red Hat, Inc
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
diff --git a/tls/gnutls/gtlscertificate-gnutls.c b/tls/gnutls/gtlscertificate-gnutls.c
index 9d81cce..358b554 100644
--- a/tls/gnutls/gtlscertificate-gnutls.c
+++ b/tls/gnutls/gtlscertificate-gnutls.c
@@ -464,11 +464,11 @@ g_tls_certificate_gnutls_has_key (GTlsCertificateGnutls *gnutls)
}
void
-g_tls_certificate_gnutls_copy (GTlsCertificateGnutls *gnutls,
- const gchar *interaction_id,
- gnutls_pcert_st **pcert,
- unsigned int *pcert_length,
- gnutls_privkey_t *pkey)
+g_tls_certificate_gnutls_copy_internals (GTlsCertificateGnutls *gnutls,
+ const gchar *interaction_id,
+ gnutls_pcert_st **pcert,
+ unsigned int *pcert_length,
+ gnutls_privkey_t *pkey)
{
GTlsCertificateGnutls *chain;
guint num_certs = 0;
@@ -538,9 +538,9 @@ g_tls_certificate_gnutls_copy (GTlsCertificateGnutls *gnutls,
}
void
-g_tls_certificate_gnutls_copy_free (gnutls_pcert_st *pcert,
- unsigned int pcert_length,
- gnutls_privkey_t pkey)
+g_tls_certificate_gnutls_internals_free (gnutls_pcert_st *pcert,
+ unsigned int pcert_length,
+ gnutls_privkey_t pkey)
{
if (pcert)
{
@@ -743,7 +743,7 @@ error:
return NULL;
}
-GTlsCertificateGnutls *
+GTlsCertificate *
g_tls_certificate_gnutls_build_chain (const gnutls_datum_t *certs,
guint num_certs,
gnutls_x509_crt_fmt_t format)
@@ -805,5 +805,5 @@ g_tls_certificate_gnutls_build_chain (const gnutls_datum_t *certs,
gnutls_x509_crt_deinit (gnutls_certs[i]);
g_free (gnutls_certs);
- return result;
+ return G_TLS_CERTIFICATE (result);
}
diff --git a/tls/gnutls/gtlscertificate-gnutls.h b/tls/gnutls/gtlscertificate-gnutls.h
index 838f7db..c74584b 100644
--- a/tls/gnutls/gtlscertificate-gnutls.h
+++ b/tls/gnutls/gtlscertificate-gnutls.h
@@ -46,13 +46,13 @@ const gnutls_x509_crt_t g_tls_certificate_gnutls_get_cert (GTlsCerti
gboolean g_tls_certificate_gnutls_has_key (GTlsCertificateGnutls *gnutls);
gboolean g_tls_certificate_gnutls_is_pkcs11_backed (GTlsCertificateGnutls *gnutls);
-void g_tls_certificate_gnutls_copy (GTlsCertificateGnutls *gnutls,
+void g_tls_certificate_gnutls_copy_internals (GTlsCertificateGnutls *gnutls,
const gchar
*interaction_id,
gnutls_pcert_st **pcert,
unsigned int *pcert_length,
gnutls_privkey_t *pkey);
-void g_tls_certificate_gnutls_copy_free (gnutls_pcert_st *pcert,
+void g_tls_certificate_gnutls_internals_free (gnutls_pcert_st *pcert,
unsigned int pcert_length,
gnutls_privkey_t pkey);
@@ -66,7 +66,7 @@ void g_tls_certificate_gnutls_set_issuer (GTlsCerti
GTlsCertificateGnutls* g_tls_certificate_gnutls_steal_issuer (GTlsCertificateGnutls *gnutls);
-GTlsCertificateGnutls* g_tls_certificate_gnutls_build_chain (const gnutls_datum_t *certs,
+GTlsCertificate * g_tls_certificate_gnutls_build_chain (const gnutls_datum_t *certs,
guint num_certs,
gnutls_x509_crt_fmt_t format);
diff --git a/tls/gnutls/gtlsclientconnection-gnutls.c b/tls/gnutls/gtlsclientconnection-gnutls.c
index 734ad75..373f8cb 100644
--- a/tls/gnutls/gtlsclientconnection-gnutls.c
+++ b/tls/gnutls/gtlsclientconnection-gnutls.c
@@ -3,6 +3,8 @@
* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2010 Red Hat, Inc
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -23,19 +25,20 @@
*/
#include "config.h"
-#include "glib.h"
+#include "gtlsclientconnection-gnutls.h"
+
+#include "gtlsbackend-gnutls.h"
+#include "gtlsconnection-base.h"
+#include "gtlscertificate-gnutls.h"
+#include "gtlsoperationsthread-base.h"
#include <errno.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <string.h>
-#include "gtlsconnection-base.h"
-#include "gtlsclientconnection-gnutls.h"
-#include "gtlsbackend-gnutls.h"
-#include "gtlscertificate-gnutls.h"
-#include <glib/gi18n-lib.h>
-
enum
{
PROP_0,
@@ -53,21 +56,7 @@ struct _GTlsClientConnectionGnutls
GSocketConnectable *server_identity;
gboolean use_ssl3;
- /* session_data is either the session ticket that was used to resume this
- * connection, or the most recent session ticket received from the server.
- * Because session ticket reuse is generally undesirable, it should only be
- * accessed if session_data_override is set.
- */
- GBytes *session_id;
- GBytes *session_data;
- gboolean session_data_override;
-
- GPtrArray *accepted_cas;
- gboolean accepted_cas_changed;
-
- gnutls_pcert_st *pcert;
- unsigned int pcert_length;
- gnutls_privkey_t pkey;
+ GList *accepted_cas;
};
static void g_tls_client_connection_gnutls_initable_interface_init (GInitableIface *iface);
@@ -75,15 +64,6 @@ static void g_tls_client_connection_gnutls_initable_interface_init (GInitableIfa
static void g_tls_client_connection_gnutls_client_connection_interface_init (GTlsClientConnectionInterface
*iface);
static void g_tls_client_connection_gnutls_dtls_client_connection_interface_init
(GDtlsClientConnectionInterface *iface);
-static int g_tls_client_connection_gnutls_handshake_thread_retrieve_function (gnutls_session_t
session,
- const gnutls_datum_t
*req_ca_rdn,
- int
nreqs,
- const gnutls_pk_algorithm_t
*pk_algos,
- int
pk_algos_length,
- gnutls_pcert_st
**pcert,
- unsigned int
*pcert_length,
- gnutls_privkey_t
*pkey);
-
static GInitableIface *g_tls_client_connection_gnutls_parent_initable_iface;
G_DEFINE_TYPE_WITH_CODE (GTlsClientConnectionGnutls, g_tls_client_connection_gnutls,
G_TYPE_TLS_CONNECTION_GNUTLS,
@@ -94,16 +74,6 @@ G_DEFINE_TYPE_WITH_CODE (GTlsClientConnectionGnutls, g_tls_client_connection_gnu
G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_CLIENT_CONNECTION,
g_tls_client_connection_gnutls_dtls_client_connection_interface_init));
-static void
-clear_gnutls_certificate_copy (GTlsClientConnectionGnutls *gnutls)
-{
- g_tls_certificate_gnutls_copy_free (gnutls->pcert, gnutls->pcert_length, gnutls->pkey);
-
- gnutls->pcert = NULL;
- gnutls->pcert_length = 0;
- gnutls->pkey = NULL;
-}
-
static void
g_tls_client_connection_gnutls_init (GTlsClientConnectionGnutls *gnutls)
{
@@ -120,126 +90,18 @@ get_server_identity (GTlsClientConnectionGnutls *gnutls)
return NULL;
}
-static void
-g_tls_client_connection_gnutls_compute_session_id (GTlsClientConnectionGnutls *gnutls)
-{
- GSocketConnection *base_conn;
- GSocketAddress *remote_addr;
- GInetAddress *iaddr;
- guint port;
-
- /* The testsuite expects handshakes to actually happen. E.g. a test might
- * check to see that a handshake succeeds and then later check that a new
- * handshake fails. If we get really unlucky and the same port number is
- * reused for the server socket between connections, then we'll accidentally
- * resume the old session and skip certificate verification. Such failures
- * are difficult to debug because they require running the tests hundreds of
- * times simultaneously to reproduce (the port number does not get reused
- * quickly enough if the tests are run sequentially).
- *
- * So session resumption will just need to be tested manually.
- */
- if (g_test_initialized ())
- return;
-
- /* Create a TLS "session ID." We base it on the IP address since
- * different hosts serving the same hostname/service will probably
- * not share the same session cache. We base it on the
- * server-identity because at least some servers will fail (rather
- * than just failing to resume the session) if we don't.
- * (https://bugs.launchpad.net/bugs/823325)
- *
- * Note that our session IDs have no relation to TLS protocol
- * session IDs, e.g. as provided by gnutls_session_get_id2(). Unlike
- * our session IDs, actual TLS session IDs can no longer be used for
- * session resumption.
- */
- g_object_get (G_OBJECT (gnutls), "base-io-stream", &base_conn, NULL);
- if (G_IS_SOCKET_CONNECTION (base_conn))
- {
- remote_addr = g_socket_connection_get_remote_address (base_conn, NULL);
- if (G_IS_INET_SOCKET_ADDRESS (remote_addr))
- {
- GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
- const gchar *server_hostname;
- gchar *addrstr, *session_id;
- GTlsCertificate *cert = NULL;
- gchar *cert_hash = NULL;
-
- iaddr = g_inet_socket_address_get_address (isaddr);
- port = g_inet_socket_address_get_port (isaddr);
-
- addrstr = g_inet_address_to_string (iaddr);
- server_hostname = get_server_identity (gnutls);
-
- /* If we have a certificate, make its hash part of the session ID, so
- * that different connections to the same server can use different
- * certificates.
- */
- g_object_get (G_OBJECT (gnutls), "certificate", &cert, NULL);
- if (cert)
- {
- GByteArray *der = NULL;
- g_object_get (G_OBJECT (cert), "certificate", &der, NULL);
- if (der)
- {
- cert_hash = g_compute_checksum_for_data (G_CHECKSUM_SHA256, der->data, der->len);
- g_byte_array_unref (der);
- }
- g_object_unref (cert);
- }
- session_id = g_strdup_printf ("%s/%s/%d/%s", addrstr,
- server_hostname ? server_hostname : "",
- port,
- cert_hash ? cert_hash : "");
- gnutls->session_id = g_bytes_new_take (session_id, strlen (session_id));
- g_free (addrstr);
- g_free (cert_hash);
- }
- g_object_unref (remote_addr);
- }
- g_clear_object (&base_conn);
-}
-
-static int
-handshake_thread_session_ticket_received_cb (gnutls_session_t session,
- guint htype,
- guint when,
- guint incoming,
- const gnutls_datum_t *msg)
-{
- GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (gnutls_session_get_ptr (session));
- gnutls_datum_t session_datum;
-
- if (gnutls_session_get_data2 (session, &session_datum) == GNUTLS_E_SUCCESS)
- {
- g_clear_pointer (&gnutls->session_data, g_bytes_unref);
- gnutls->session_data = g_bytes_new_with_free_func (session_datum.data,
- session_datum.size,
- (GDestroyNotify)gnutls_free,
- session_datum.data);
-
- if (gnutls->session_id)
- {
- g_tls_backend_gnutls_store_session_data (gnutls->session_id,
- gnutls->session_data);
- }
- }
-
- return 0;
-}
-
static void
g_tls_client_connection_gnutls_finalize (GObject *object)
{
GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
g_clear_object (&gnutls->server_identity);
- g_clear_pointer (&gnutls->accepted_cas, g_ptr_array_unref);
- g_clear_pointer (&gnutls->session_id, g_bytes_unref);
- g_clear_pointer (&gnutls->session_data, g_bytes_unref);
- clear_gnutls_certificate_copy (gnutls);
+ if (gnutls->accepted_cas)
+ {
+ g_list_free_full (gnutls->accepted_cas, (GDestroyNotify)g_byte_array_unref);
+ gnutls->accepted_cas = NULL;
+ }
G_OBJECT_CLASS (g_tls_client_connection_gnutls_parent_class)->finalize (object);
}
@@ -250,33 +112,17 @@ g_tls_client_connection_gnutls_initable_init (GInitable *initable,
GError **error)
{
GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable);
- gnutls_session_t session;
+ GTlsOperationsThreadBase *thread;
const gchar *hostname;
- gnutls_certificate_credentials_t creds;
if (!g_tls_client_connection_gnutls_parent_initable_iface->init (initable, cancellable, error))
return FALSE;
- creds = g_tls_connection_gnutls_get_credentials (G_TLS_CONNECTION_GNUTLS (gnutls));
- gnutls_certificate_set_retrieve_function2 (creds,
g_tls_client_connection_gnutls_handshake_thread_retrieve_function);
+ thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (gnutls));
- session = g_tls_connection_gnutls_get_session (gnutls);
hostname = get_server_identity (G_TLS_CLIENT_CONNECTION_GNUTLS (gnutls));
if (hostname)
- {
- gchar *normalized_hostname = g_strdup (hostname);
-
- if (hostname[strlen (hostname) - 1] == '.')
- normalized_hostname[strlen (hostname) - 1] = '\0';
-
- gnutls_server_name_set (session, GNUTLS_NAME_DNS,
- normalized_hostname, strlen (normalized_hostname));
-
- g_free (normalized_hostname);
- }
-
- gnutls_handshake_set_hook_function (session, GNUTLS_HANDSHAKE_NEW_SESSION_TICKET,
- GNUTLS_HOOK_POST, handshake_thread_session_ticket_received_cb);
+ g_tls_operations_thread_base_set_server_identity (thread, hostname);
return TRUE;
}
@@ -288,8 +134,6 @@ g_tls_client_connection_gnutls_get_property (GObject *object,
GParamSpec *pspec)
{
GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (object);
- GList *accepted_cas;
- gint i;
switch (prop_id)
{
@@ -306,17 +150,7 @@ g_tls_client_connection_gnutls_get_property (GObject *object,
break;
case PROP_ACCEPTED_CAS:
- accepted_cas = NULL;
- if (gnutls->accepted_cas)
- {
- for (i = 0; i < gnutls->accepted_cas->len; ++i)
- {
- accepted_cas = g_list_prepend (accepted_cas, g_byte_array_ref (
- gnutls->accepted_cas->pdata[i]));
- }
- accepted_cas = g_list_reverse (accepted_cas);
- }
- g_value_set_pointer (value, accepted_cas);
+ g_value_set_pointer (value, g_list_copy (gnutls->accepted_cas));
break;
default:
@@ -347,22 +181,11 @@ g_tls_client_connection_gnutls_set_property (GObject *object,
hostname = get_server_identity (gnutls);
if (hostname)
{
- gnutls_session_t session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (gnutls));
-
- /* This will only be triggered if the identity is set after
- * initialization */
- if (session)
- {
- gchar *normalized_hostname = g_strdup (hostname);
-
- if (hostname[strlen (hostname) - 1] == '.')
- normalized_hostname[strlen (hostname) - 1] = '\0';
+ GTlsOperationsThreadBase *thread;
- gnutls_server_name_set (session, GNUTLS_NAME_DNS,
- normalized_hostname, strlen (normalized_hostname));
-
- g_free (normalized_hostname);
- }
+ thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (gnutls));
+ if (thread)
+ g_tls_operations_thread_base_set_server_identity (thread, hostname);
}
break;
@@ -375,161 +198,26 @@ g_tls_client_connection_gnutls_set_property (GObject *object,
}
}
-static int
-g_tls_client_connection_gnutls_handshake_thread_retrieve_function (gnutls_session_t session,
- const gnutls_datum_t *req_ca_rdn,
- int nreqs,
- const gnutls_pk_algorithm_t *pk_algos,
- int
pk_algos_length,
- gnutls_pcert_st **pcert,
- unsigned int
*pcert_length,
- gnutls_privkey_t *pkey)
-{
- GTlsConnectionBase *tls = gnutls_transport_get_ptr (session);
- GTlsClientConnectionGnutls *gnutls = gnutls_transport_get_ptr (session);
- GTlsConnectionGnutls *conn = G_TLS_CONNECTION_GNUTLS (gnutls);
- GPtrArray *accepted_cas;
- gboolean had_accepted_cas;
- GByteArray *dn;
- int i;
-
- /* FIXME: Here we are supposed to ensure that the certificate supports one of
- * the algorithms given in pk_algos.
- */
-
- had_accepted_cas = gnutls->accepted_cas != NULL;
-
- accepted_cas = g_ptr_array_new_with_free_func ((GDestroyNotify)g_byte_array_unref);
- for (i = 0; i < nreqs; i++)
- {
- dn = g_byte_array_new ();
- g_byte_array_append (dn, req_ca_rdn[i].data, req_ca_rdn[i].size);
- g_ptr_array_add (accepted_cas, dn);
- }
-
- if (gnutls->accepted_cas)
- g_ptr_array_unref (gnutls->accepted_cas);
- gnutls->accepted_cas = accepted_cas;
-
- gnutls->accepted_cas_changed = gnutls->accepted_cas || had_accepted_cas;
-
- clear_gnutls_certificate_copy (gnutls);
- g_tls_connection_gnutls_handshake_thread_get_certificate (conn, pcert, pcert_length, pkey);
-
- if (*pcert_length == 0)
- {
- g_tls_certificate_gnutls_copy_free (*pcert, *pcert_length, *pkey);
-
- if (g_tls_connection_base_handshake_thread_request_certificate (tls))
- g_tls_connection_gnutls_handshake_thread_get_certificate (conn, pcert, pcert_length, pkey);
-
- if (*pcert_length == 0)
- {
- g_tls_certificate_gnutls_copy_free (*pcert, *pcert_length, *pkey);
-
- /* If there is still no client certificate, this connection will
- * probably fail, but we must not give up yet. The certificate might
- * be optional, e.g. if the server is using
- * G_TLS_AUTHENTICATION_REQUESTED, not G_TLS_AUTHENTICATION_REQUIRED.
- */
- g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate (tls);
- return 0;
- }
- }
-
- if (!*pkey)
- {
- g_tls_certificate_gnutls_copy_free (*pcert, *pcert_length, *pkey);
-
- /* No private key. GnuTLS expects it to be non-null if pcert_length is
- * nonzero, so we have to abort now.
- */
- g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate (tls);
- return -1;
- }
-
- gnutls->pcert = *pcert;
- gnutls->pcert_length = *pcert_length;
- gnutls->pkey = *pkey;
-
- return 0;
-}
-
static void
-g_tls_client_connection_gnutls_prepare_handshake (GTlsConnectionBase *tls,
- gchar **advertised_protocols)
+g_tls_client_connection_gnutls_set_accepted_cas (GTlsConnectionBase *tls,
+ GList *accepted_cas)
{
GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (tls);
- g_tls_client_connection_gnutls_compute_session_id (gnutls);
-
- if (gnutls->session_data_override)
- {
- g_assert (gnutls->session_data);
- gnutls_session_set_data (g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (tls)),
- g_bytes_get_data (gnutls->session_data, NULL),
- g_bytes_get_size (gnutls->session_data));
- }
- else if (gnutls->session_id)
- {
- GBytes *session_data;
-
- session_data = g_tls_backend_gnutls_lookup_session_data (gnutls->session_id);
- if (session_data)
- {
- gnutls_session_set_data (g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (tls)),
- g_bytes_get_data (session_data, NULL),
- g_bytes_get_size (session_data));
- g_clear_pointer (&gnutls->session_data, g_bytes_unref);
- gnutls->session_data = g_steal_pointer (&session_data);
- }
- }
-
- G_TLS_CONNECTION_BASE_CLASS (g_tls_client_connection_gnutls_parent_class)->
- prepare_handshake (tls, advertised_protocols);
-}
-
-static void
-g_tls_client_connection_gnutls_complete_handshake (GTlsConnectionBase *tls,
- gchar **negotiated_protocol,
- GError **error)
-{
- GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (tls);
-
- G_TLS_CONNECTION_BASE_CLASS (g_tls_client_connection_gnutls_parent_class)->complete_handshake (tls,
negotiated_protocol, error);
+ if (gnutls->accepted_cas)
+ g_list_free_full (gnutls->accepted_cas, (GDestroyNotify)g_byte_array_unref);
- /* It may have changed during the handshake, but we have to wait until here
- * because we can't emit notifies on the handshake thread.
- */
- if (gnutls->accepted_cas_changed)
- g_object_notify (G_OBJECT (gnutls), "accepted-cas");
+ gnutls->accepted_cas = g_steal_pointer (&accepted_cas);
}
static void
g_tls_client_connection_gnutls_copy_session_state (GTlsClientConnection *conn,
GTlsClientConnection *source)
{
- GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn);
- GTlsClientConnectionGnutls *gnutls_source = G_TLS_CLIENT_CONNECTION_GNUTLS (source);
-
- /* Precondition: source has handshaked, conn has not. */
- g_return_if_fail (!gnutls->session_id);
- g_return_if_fail (gnutls_source->session_id);
-
- /* Prefer to use a new session ticket, if possible. */
- gnutls->session_data = g_tls_backend_gnutls_lookup_session_data (gnutls_source->session_id);
-
- if (!gnutls->session_data && gnutls_source->session_data)
- {
- /* If it's not possible, we'll try to reuse the old ticket, even though
- * this is a privacy risk since TLS 1.3. Applications should not use this
- * function unless they need us to try as hard as possible to resume a
- * session, even at the cost of privacy.
- */
- gnutls->session_data = g_bytes_ref (gnutls_source->session_data);
- }
+ GTlsOperationsThreadBase *thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (conn));
+ GTlsOperationsThreadBase *source_thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE
(source));
- gnutls->session_data_override = !!gnutls->session_data;
+ g_tls_operations_thread_base_copy_client_session_state (thread, source_thread);
}
static void
@@ -542,8 +230,7 @@ g_tls_client_connection_gnutls_class_init (GTlsClientConnectionGnutlsClass *klas
gobject_class->set_property = g_tls_client_connection_gnutls_set_property;
gobject_class->finalize = g_tls_client_connection_gnutls_finalize;
- base_class->prepare_handshake = g_tls_client_connection_gnutls_prepare_handshake;
- base_class->complete_handshake = g_tls_client_connection_gnutls_complete_handshake;
+ base_class->set_accepted_cas = g_tls_client_connection_gnutls_set_accepted_cas;
g_object_class_override_property (gobject_class, PROP_VALIDATION_FLAGS, "validation-flags");
g_object_class_override_property (gobject_class, PROP_SERVER_IDENTITY, "server-identity");
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index 39cc7c5..7708fa9 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -4,6 +4,8 @@
*
* Copyright 2009 Red Hat, Inc
* Copyright 2015, 2016 Collabora, Ltd.
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -24,90 +26,25 @@
*/
#include "config.h"
-#include "glib.h"
-
-#include <errno.h>
-#include <stdarg.h>
-#include <gnutls/dtls.h>
-#include <gnutls/gnutls.h>
-#include <gnutls/x509.h>
-
#include "gtlsconnection-gnutls.h"
-#include "gtlsbackend-gnutls.h"
-#include "gtlscertificate-gnutls.h"
-#include "gtlsclientconnection-gnutls.h"
-
-#ifdef G_OS_WIN32
-#include <winsock2.h>
-#include <winerror.h>
-
-/* It isn’t clear whether MinGW always defines EMSGSIZE. */
-#ifndef EMSGSIZE
-#define EMSGSIZE WSAEMSGSIZE
-#endif
-#endif
-#include <glib/gi18n-lib.h>
-#include <glib/gprintf.h>
+#include "gtlsoperationsthread-gnutls.h"
-static ssize_t g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t transport_data,
- const void *buf,
- size_t buflen);
-static ssize_t g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t transport_data,
- const giovec_t *iov,
- int iovcnt);
-static ssize_t g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t transport_data,
- void *buf,
- size_t buflen);
+#include <glib.h>
+#include <gnutls/gnutls.h>
-static int g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
- unsigned int ms);
+static GInitableIface *g_tls_connection_gnutls_parent_initable_iface;
static void g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface);
-static int verify_certificate_cb (gnutls_session_t session);
-
-static gnutls_priority_t priority;
-
-typedef struct
-{
- gnutls_certificate_credentials_t creds;
- gnutls_session_t session;
- gchar *interaction_id;
- GCancellable *cancellable;
-} GTlsConnectionGnutlsPrivate;
-
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls, G_TYPE_TLS_CONNECTION_BASE,
- G_ADD_PRIVATE (GTlsConnectionGnutls);
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
g_tls_connection_gnutls_initable_iface_init);
);
-static gint unique_interaction_id = 0;
-
static void
g_tls_connection_gnutls_init (GTlsConnectionGnutls *gnutls)
{
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- int unique_id;
-
- unique_id = g_atomic_int_add (&unique_interaction_id, 1);
- priv->interaction_id = g_strdup_printf ("gtls:%d", unique_id);
-
- priv->cancellable = g_cancellable_new ();
-}
-
-static void
-g_tls_connection_gnutls_set_handshake_priority (GTlsConnectionGnutls *gnutls)
-{
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- int ret;
-
- g_assert (priority);
-
- ret = gnutls_priority_set (priv->session, priority);
- if (ret != GNUTLS_E_SUCCESS)
- g_warning ("Failed to set GnuTLS session priority: %s", gnutls_strerror (ret));
}
static gboolean
@@ -115,1051 +52,57 @@ g_tls_connection_gnutls_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
+ return g_tls_connection_gnutls_parent_initable_iface->init (initable, cancellable, error);
+}
+
+static GTlsOperationsThreadBase *
+g_tls_connection_gnutls_create_op_thread (GTlsConnectionBase *tls)
+{
GIOStream *base_io_stream = NULL;
GDatagramBased *base_socket = NULL;
- gboolean client = G_IS_TLS_CLIENT_CONNECTION (gnutls);
- guint flags = client ? GNUTLS_CLIENT : GNUTLS_SERVER;
- int status;
- int ret;
+ gboolean client = G_IS_TLS_CLIENT_CONNECTION (tls);
+ guint flags = GNUTLS_NONBLOCK;
+ GTlsOperationsThreadBase *thread;
- g_object_get (gnutls,
+ g_object_get (tls,
"base-io-stream", &base_io_stream,
"base-socket", &base_socket,
NULL);
/* Ensure we are in TLS mode or DTLS mode. */
- g_return_val_if_fail (!!base_io_stream != !!base_socket, FALSE);
-
- if (base_socket)
- flags |= GNUTLS_DATAGRAM;
+ g_assert (!!base_io_stream != !!base_socket);
- ret = gnutls_certificate_allocate_credentials (&priv->creds);
- if (ret != GNUTLS_E_SUCCESS)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS connection: %s"),
- gnutls_strerror (ret));
- g_clear_object (&base_io_stream);
- g_clear_object (&base_socket);
- return FALSE;
- }
-
- 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);
- if (status != 0)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS connection: %s"),
- gnutls_strerror (status));
- g_clear_object (&base_io_stream);
- g_clear_object (&base_socket);
- return FALSE;
- }
-
- gnutls_transport_set_push_function (priv->session,
- g_tls_connection_gnutls_push_func);
- gnutls_transport_set_pull_function (priv->session,
- g_tls_connection_gnutls_pull_func);
- gnutls_transport_set_pull_timeout_function (priv->session,
- g_tls_connection_gnutls_pull_timeout_func);
- gnutls_transport_set_ptr (priv->session, gnutls);
+ if (client)
+ flags |= GNUTLS_CLIENT;
+ else
+ flags |= GNUTLS_SERVER;
- /* GDatagramBased supports vectored I/O; GPollableOutputStream does not. */
if (base_socket)
- {
- gnutls_transport_set_vec_push_function (priv->session,
- g_tls_connection_gnutls_vec_push_func);
- }
+ flags |= GNUTLS_DATAGRAM;
- /* Set reasonable MTU */
- if (flags & GNUTLS_DATAGRAM)
- gnutls_dtls_set_mtu (priv->session, 1400);
+ thread = g_tls_operations_thread_gnutls_new (G_TLS_CONNECTION_GNUTLS (tls),
+ base_io_stream,
+ base_socket,
+ flags);
g_clear_object (&base_io_stream);
g_clear_object (&base_socket);
- return TRUE;
-}
-
-static void
-g_tls_connection_gnutls_finalize (GObject *object)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
- if (priv->session)
- gnutls_deinit (priv->session);
- if (priv->creds)
- gnutls_certificate_free_credentials (priv->creds);
-
- if (priv->cancellable)
- {
- g_cancellable_cancel (priv->cancellable);
- g_clear_object (&priv->cancellable);
- }
-
- g_free (priv->interaction_id);
-
- G_OBJECT_CLASS (g_tls_connection_gnutls_parent_class)->finalize (object);
-}
-
-gnutls_certificate_credentials_t
-g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *gnutls)
-{
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
- return priv->creds;
-}
-
-gnutls_session_t
-g_tls_connection_gnutls_get_session (GTlsConnectionGnutls *gnutls)
-{
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
- return priv->session;
-}
-
-static int
-on_pin_request (void *userdata,
- int attempt,
- const char *token_url,
- const char *token_label,
- unsigned int callback_flags,
- char *pin,
- size_t pin_max)
-{
- GTlsConnection *connection = G_TLS_CONNECTION (userdata);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (G_TLS_CONNECTION_GNUTLS
(connection));
- GTlsInteraction *interaction = g_tls_connection_get_interaction (connection);
- GTlsInteractionResult result;
- GTlsPassword *password;
- GTlsPasswordFlags password_flags = 0;
- GError *error = NULL;
- gchar *description;
- int ret = -1;
-
- if (!interaction)
- return -1;
-
- if (callback_flags & GNUTLS_PIN_WRONG)
- password_flags |= G_TLS_PASSWORD_RETRY;
- if (callback_flags & GNUTLS_PIN_COUNT_LOW)
- password_flags |= G_TLS_PASSWORD_MANY_TRIES;
- if (callback_flags & GNUTLS_PIN_FINAL_TRY || attempt > 5) /* Give up at some point */
- password_flags |= G_TLS_PASSWORD_FINAL_TRY;
-
- description = g_strdup_printf (" %s (%s)", token_label, token_url);
- password = g_tls_password_new (password_flags, description);
- result = g_tls_interaction_invoke_ask_password (interaction, password,
- priv->cancellable,
- &error);
- g_free (description);
-
- switch (result)
- {
- case G_TLS_INTERACTION_FAILED:
- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- g_warning ("Error getting PIN: %s", error->message);
- g_error_free (error);
- break;
- case G_TLS_INTERACTION_UNHANDLED:
- break;
- case G_TLS_INTERACTION_HANDLED:
- {
- gsize password_size;
- const guchar *password_data = g_tls_password_get_value (password, &password_size);
- if (password_size > pin_max)
- g_warning ("PIN is larger than max PIN size");
-
- memcpy (pin, password_data, MIN (password_size, pin_max));
- ret = GNUTLS_E_SUCCESS;
- break;
- }
- default:
- g_assert_not_reached ();
- }
-
- g_object_unref (password);
-
- return ret;
-}
-
-void
-g_tls_connection_gnutls_handshake_thread_get_certificate (GTlsConnectionGnutls *gnutls,
- gnutls_pcert_st **pcert,
- unsigned int *pcert_length,
- gnutls_privkey_t *pkey)
-{
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsCertificate *cert;
-
- cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (gnutls));
-
- if (cert)
- {
- /* Send along a pre-initialized privkey so we can handle the callback here. */
- gnutls_privkey_t privkey;
- gnutls_privkey_init (&privkey);
- gnutls_privkey_set_pin_function (privkey, on_pin_request, gnutls);
-
- g_tls_certificate_gnutls_copy (G_TLS_CERTIFICATE_GNUTLS (cert),
- priv->interaction_id,
- pcert, pcert_length, &privkey);
- *pkey = privkey;
- }
- else
- {
- *pcert = NULL;
- *pcert_length = 0;
- *pkey = NULL;
- }
-}
-
-static GTlsConnectionBaseStatus
-end_gnutls_io (GTlsConnectionGnutls *gnutls,
- GIOCondition direction,
- int ret,
- GError **error,
- const char *err_prefix)
-{
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (gnutls);
- GTlsConnectionBaseStatus status;
- gboolean handshaking;
- gboolean ever_handshaked;
- GError *my_error = NULL;
-
- /* We intentionally do not check for GNUTLS_E_INTERRUPTED here
- * Instead, the caller may poll for the source to become ready again.
- * (Note that GTlsOutputStreamGnutls and GTlsInputStreamGnutls inherit
- * from GPollableOutputStream and GPollableInputStream, respectively.)
- * See also the comment in set_gnutls_error().
- */
- if (ret == GNUTLS_E_AGAIN ||
- ret == GNUTLS_E_WARNING_ALERT_RECEIVED)
- return G_TLS_CONNECTION_BASE_TRY_AGAIN;
-
- status = g_tls_connection_base_pop_io (tls, direction, ret >= 0, &my_error);
- if (status == G_TLS_CONNECTION_BASE_OK ||
- status == G_TLS_CONNECTION_BASE_WOULD_BLOCK ||
- status == G_TLS_CONNECTION_BASE_TIMED_OUT)
- {
- if (my_error)
- g_propagate_error (error, my_error);
- return status;
- }
-
- g_assert (status == G_TLS_CONNECTION_BASE_ERROR);
-
- handshaking = g_tls_connection_base_is_handshaking (tls);
- ever_handshaked = g_tls_connection_base_ever_handshaked (tls);
-
- if (handshaking && !ever_handshaked)
- {
- if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_FAILED) ||
- g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE))
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
- _("Peer failed to perform TLS handshake: %s"), my_error->message);
- g_clear_error (&my_error);
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
- status == GNUTLS_E_DECRYPTION_FAILED ||
- status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET)
- {
- g_clear_error (&my_error);
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
- _("Peer failed to perform TLS handshake: %s"), gnutls_strerror (ret));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
- }
-
- if (ret == GNUTLS_E_REHANDSHAKE)
- return G_TLS_CONNECTION_BASE_REHANDSHAKE;
-
- if (ret == GNUTLS_E_PREMATURE_TERMINATION)
- {
- if (handshaking && !ever_handshaked)
- {
- g_clear_error (&my_error);
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
- _("Peer failed to perform TLS handshake: %s"), gnutls_strerror (ret));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (g_tls_connection_get_require_close_notify (G_TLS_CONNECTION (gnutls)))
- {
- g_clear_error (&my_error);
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
- _("TLS connection closed unexpectedly"));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- return G_TLS_CONNECTION_BASE_OK;
- }
-
- if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND
-#ifdef GNUTLS_E_CERTIFICATE_REQUIRED
- || ret == GNUTLS_E_CERTIFICATE_REQUIRED /* Added in GnuTLS 3.6.7 */
-#endif
- )
- {
- g_clear_error (&my_error);
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
- _("TLS connection peer did not send a certificate"));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (ret == GNUTLS_E_CERTIFICATE_ERROR)
- {
- g_clear_error (&my_error);
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Unacceptable TLS certificate"));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED)
- {
- g_clear_error (&my_error);
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Peer sent fatal TLS alert: %s"),
- gnutls_alert_get_name (gnutls_alert_get (priv->session)));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (ret == GNUTLS_E_INAPPROPRIATE_FALLBACK)
- {
- g_clear_error (&my_error);
- g_set_error_literal (error, G_TLS_ERROR,
- G_TLS_ERROR_INAPPROPRIATE_FALLBACK,
- _("Protocol version downgrade attack detected"));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (ret == GNUTLS_E_LARGE_PACKET)
- {
- guint mtu = gnutls_dtls_get_data_mtu (priv->session);
- g_clear_error (&my_error);
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
- ngettext ("Message is too large for DTLS connection; maximum is %u byte",
- "Message is too large for DTLS connection; maximum is %u bytes", mtu), mtu);
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (ret == GNUTLS_E_TIMEDOUT)
- {
- g_clear_error (&my_error);
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
- _("The operation timed out"));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (error && my_error)
- g_propagate_error (error, my_error);
-
- if (error && !*error)
- {
- *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s",
- err_prefix, gnutls_strerror (ret));
- }
-
- return G_TLS_CONNECTION_BASE_ERROR;
-}
-
-#define BEGIN_GNUTLS_IO(gnutls, direction, timeout, cancellable) \
- g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (gnutls), \
- direction, timeout, cancellable); \
- do {
-
-#define END_GNUTLS_IO(gnutls, direction, ret, status, errmsg, err) \
- status = end_gnutls_io (gnutls, direction, ret, err, errmsg); \
- } while (status == G_TLS_CONNECTION_BASE_TRY_AGAIN);
-
-static void
-set_gnutls_error (GTlsConnectionGnutls *gnutls,
- GError *error)
-{
- GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (gnutls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
- /* We set EINTR rather than EAGAIN for G_IO_ERROR_WOULD_BLOCK so
- * that GNUTLS_E_AGAIN only gets returned for gnutls-internal
- * reasons, not for actual socket EAGAINs (and we have access
- * to @error at the higher levels, so we can distinguish them
- * that way later).
- */
-
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- gnutls_transport_set_errno (priv->session, EINTR);
- else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
- {
- /* Return EAGAIN while handshaking so that GnuTLS handles retries for us
- * internally in its handshaking code. */
- if (g_tls_connection_base_is_dtls (tls) && g_tls_connection_base_is_handshaking (tls))
- gnutls_transport_set_errno (priv->session, EAGAIN);
- else
- gnutls_transport_set_errno (priv->session, EINTR);
- }
- else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
- gnutls_transport_set_errno (priv->session, EINTR);
- else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE))
- gnutls_transport_set_errno (priv->session, EMSGSIZE);
- else
- gnutls_transport_set_errno (priv->session, EIO);
-}
-
-static ssize_t
-g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t transport_data,
- void *buf,
- size_t buflen)
-{
- GTlsConnectionBase *tls = transport_data;
- GTlsConnectionGnutls *gnutls = transport_data;
- ssize_t ret;
-
- /* If read_error is nonnull when we're called, it means
- * that an error previously occurred, but GnuTLS decided not to
- * propagate it. So it's correct for us to just clear it. (Usually
- * this means it ignored an EAGAIN after a short read, and now
- * we'll return EAGAIN again, which it will obey this time.)
- */
- g_clear_error (g_tls_connection_base_get_read_error (tls));
-
- if (g_tls_connection_base_is_dtls (tls))
- {
- GInputVector vector = { buf, buflen };
- GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL };
-
- ret = g_datagram_based_receive_messages (g_tls_connection_base_get_base_socket (tls),
- &message, 1, 0,
- g_tls_connection_base_is_handshaking (tls) ? 0 :
g_tls_connection_base_get_read_timeout (tls),
- g_tls_connection_base_get_read_cancellable (tls),
- g_tls_connection_base_get_read_error (tls));
-
- if (ret > 0)
- ret = message.bytes_received;
- }
- else
- {
- ret = g_pollable_stream_read (G_INPUT_STREAM (g_tls_connection_base_get_base_istream (tls)),
- buf, buflen,
- g_tls_connection_base_get_read_timeout (tls) != 0,
- g_tls_connection_base_get_read_cancellable (tls),
- g_tls_connection_base_get_read_error (tls));
- }
-
- if (ret < 0)
- set_gnutls_error (gnutls, *g_tls_connection_base_get_read_error (tls));
-
- return ret;
-}
-
-static ssize_t
-g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t transport_data,
- const void *buf,
- size_t buflen)
-{
- GTlsConnectionBase *tls = transport_data;
- GTlsConnectionGnutls *gnutls = transport_data;
- ssize_t ret;
-
- /* See comment in pull_func. */
- g_clear_error (g_tls_connection_base_get_write_error (tls));
-
- if (g_tls_connection_base_is_dtls (tls))
- {
- GOutputVector vector = { buf, buflen };
- GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 };
-
- ret = g_datagram_based_send_messages (g_tls_connection_base_get_base_socket (tls),
- &message, 1, 0,
- g_tls_connection_base_get_write_timeout (tls),
- g_tls_connection_base_get_write_cancellable (tls),
- g_tls_connection_base_get_write_error (tls));
-
- if (ret > 0)
- ret = message.bytes_sent;
- }
- else
- {
- ret = g_pollable_stream_write (G_OUTPUT_STREAM (g_tls_connection_base_get_base_ostream (tls)),
- buf, buflen,
- g_tls_connection_base_get_write_timeout (tls) != 0,
- g_tls_connection_base_get_write_cancellable (tls),
- g_tls_connection_base_get_write_error (tls));
- }
-
- if (ret < 0)
- set_gnutls_error (gnutls, *g_tls_connection_base_get_write_error (tls));
-
- return ret;
-}
-
-static ssize_t
-g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t transport_data,
- const giovec_t *iov,
- int iovcnt)
-{
- GTlsConnectionBase *tls = transport_data;
- GTlsConnectionGnutls *gnutls = transport_data;
- ssize_t ret;
- GOutputMessage message = { NULL, };
- GOutputVector *vectors;
-
- g_assert (g_tls_connection_base_is_dtls (tls));
-
- /* See comment in pull_func. */
- g_clear_error (g_tls_connection_base_get_write_error (tls));
-
- /* this entire expression will be evaluated at compile time */
- if (sizeof *iov == sizeof *vectors &&
- sizeof iov->iov_base == sizeof vectors->buffer &&
- G_STRUCT_OFFSET (giovec_t, iov_base) ==
- G_STRUCT_OFFSET (GOutputVector, buffer) &&
- sizeof iov->iov_len == sizeof vectors->size &&
- G_STRUCT_OFFSET (giovec_t, iov_len) ==
- G_STRUCT_OFFSET (GOutputVector, size))
- /* ABI is compatible */
- {
- message.vectors = (GOutputVector *)iov;
- message.num_vectors = iovcnt;
- }
- else
- /* ABI is incompatible */
- {
- gint i;
-
- message.vectors = g_newa (GOutputVector, iovcnt);
- for (i = 0; i < iovcnt; i++)
- {
- message.vectors[i].buffer = (void *)iov[i].iov_base;
- message.vectors[i].size = iov[i].iov_len;
- }
- message.num_vectors = iovcnt;
- }
-
- ret = g_datagram_based_send_messages (g_tls_connection_base_get_base_socket (tls),
- &message, 1, 0,
- g_tls_connection_base_get_write_timeout (tls),
- g_tls_connection_base_get_write_cancellable (tls),
- g_tls_connection_base_get_write_error (tls));
-
- if (ret > 0)
- ret = message.bytes_sent;
- else if (ret < 0)
- set_gnutls_error (gnutls, *g_tls_connection_base_get_write_error (tls));
-
- return ret;
-}
-
-static gboolean
-read_pollable_cb (GPollableInputStream *istream,
- gpointer user_data)
-{
- gboolean *read_done = user_data;
-
- *read_done = TRUE;
-
- return G_SOURCE_CONTINUE;
-}
-
-static gboolean
-read_datagram_based_cb (GDatagramBased *datagram_based,
- GIOCondition condition,
- gpointer user_data)
-{
- gboolean *read_done = user_data;
-
- *read_done = TRUE;
-
- return G_SOURCE_CONTINUE;
-}
-
-static gboolean
-read_timeout_cb (gpointer user_data)
-{
- gboolean *timed_out = user_data;
-
- *timed_out = TRUE;
-
- return G_SOURCE_REMOVE;
-}
-
-static int
-g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
- unsigned int ms)
-{
- GTlsConnectionBase *tls = transport_data;
-
- /* Fast path. */
- if (g_tls_connection_base_base_check (tls, G_IO_IN) ||
- g_cancellable_is_cancelled (g_tls_connection_base_get_read_cancellable (tls)))
- return 1;
-
- /* If @ms is 0, GnuTLS wants an instant response, so there’s no need to
- * construct and query a #GSource. */
- if (ms > 0)
- {
- GMainContext *ctx = NULL;
- GSource *read_source = NULL, *timeout_source = NULL;
- gboolean read_done = FALSE, timed_out = FALSE;
-
- ctx = g_main_context_new ();
-
- /* Create a timeout source. */
- timeout_source = g_timeout_source_new (ms);
- g_source_set_callback (timeout_source, (GSourceFunc)read_timeout_cb,
- &timed_out, NULL);
-
- /* Create a read source. We cannot use g_source_set_ready_time() on this
- * to combine it with the @timeout_source, as that could mess with the
- * internals of the #GDatagramBased’s #GSource implementation. */
- if (g_tls_connection_base_is_dtls (tls))
- {
- read_source = g_datagram_based_create_source (g_tls_connection_base_get_base_socket (tls),
- G_IO_IN, NULL);
- g_source_set_callback (read_source, (GSourceFunc)read_datagram_based_cb,
- &read_done, NULL);
- }
- else
- {
- read_source = g_pollable_input_stream_create_source (g_tls_connection_base_get_base_istream (tls),
- NULL);
- g_source_set_callback (read_source, (GSourceFunc)read_pollable_cb,
- &read_done, NULL);
- }
-
- g_source_attach (read_source, ctx);
- g_source_attach (timeout_source, ctx);
-
- while (!read_done && !timed_out)
- g_main_context_iteration (ctx, TRUE);
-
- g_source_destroy (read_source);
- g_source_destroy (timeout_source);
-
- g_main_context_unref (ctx);
- g_source_unref (read_source);
- g_source_unref (timeout_source);
-
- /* If @read_source was dispatched due to cancellation, the resulting error
- * will be handled in g_tls_connection_gnutls_pull_func(). */
- if (g_tls_connection_base_base_check (tls, G_IO_IN) ||
- g_cancellable_is_cancelled (g_tls_connection_base_get_read_cancellable (tls)))
- return 1;
- }
-
- return 0;
-}
-
-static GTlsSafeRenegotiationStatus
-g_tls_connection_gnutls_handshake_thread_safe_renegotiation_status (GTlsConnectionBase *tls)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
- return gnutls_safe_renegotiation_status (priv->session) ? G_TLS_SAFE_RENEGOTIATION_SUPPORTED_BY_PEER
- : G_TLS_SAFE_RENEGOTIATION_UNSUPPORTED;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_handshake_thread_request_rehandshake (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsConnectionBaseStatus status;
- int ret;
-
- /* On a client-side connection, gnutls_handshake() itself will start
- * a rehandshake, so we only need to do something special here for
- * server-side connections.
- */
- if (!G_IS_TLS_SERVER_CONNECTION (tls))
- return G_TLS_CONNECTION_BASE_OK;
-
- BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
- ret = gnutls_rehandshake (priv->session);
- END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, status, _("Error performing TLS handshake: %s"), error);
-
- return status;
-}
-
-static GTlsCertificate *
-g_tls_connection_gnutls_retrieve_peer_certificate (GTlsConnectionBase *tls)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- const gnutls_datum_t *certs;
- GTlsCertificateGnutls *chain;
- unsigned int num_certs;
-
- if (gnutls_certificate_type_get (priv->session) != GNUTLS_CRT_X509)
- return NULL;
-
- certs = gnutls_certificate_get_peers (priv->session, &num_certs);
- if (!certs || !num_certs)
- return NULL;
-
- chain = g_tls_certificate_gnutls_build_chain (certs, num_certs, GNUTLS_X509_FMT_DER);
- if (!chain)
- return NULL;
-
- return G_TLS_CERTIFICATE (chain);
-}
-
-static int
-verify_certificate_cb (gnutls_session_t session)
-{
- GTlsConnectionBase *tls = gnutls_session_get_ptr (session);
-
- /* Return 0 for the handshake to continue, non-zero to terminate.
- * Complete opposite of what OpenSSL does. */
- return !g_tls_connection_base_handshake_thread_verify_certificate (tls);
-}
-
-static void
-g_tls_connection_gnutls_prepare_handshake (GTlsConnectionBase *tls,
- gchar **advertised_protocols)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
- if (advertised_protocols)
- {
- gnutls_datum_t *protocols;
- int n_protos, i;
-
- n_protos = g_strv_length (advertised_protocols);
- protocols = g_new (gnutls_datum_t, n_protos);
- for (i = 0; advertised_protocols[i]; i++)
- {
- protocols[i].size = strlen (advertised_protocols[i]);
- protocols[i].data = (guchar *)advertised_protocols[i];
- }
- gnutls_alpn_set_protocols (priv->session, protocols, n_protos, 0);
- g_free (protocols);
- }
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_handshake_thread_handshake (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsConnectionBaseStatus status;
- int ret;
-
- if (!g_tls_connection_base_ever_handshaked (tls))
- g_tls_connection_gnutls_set_handshake_priority (gnutls);
-
- if (timeout > 0)
- {
- unsigned int timeout_ms;
-
- /* Convert from microseconds to milliseconds, but ensure the timeout
- * remains positive. */
- timeout_ms = (timeout + 999) / 1000;
-
- gnutls_handshake_set_timeout (priv->session, timeout_ms);
- gnutls_dtls_set_timeouts (priv->session, 1000 /* default */, timeout_ms);
- }
-
- BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
- ret = gnutls_handshake (priv->session);
- if (ret == GNUTLS_E_GOT_APPLICATION_DATA)
- {
- guint8 buf[1024];
-
- /* Got app data while waiting for rehandshake; buffer it and try again */
- ret = gnutls_record_recv (priv->session, buf, sizeof (buf));
- if (ret > -1)
- {
- g_tls_connection_base_handshake_thread_buffer_application_data (tls, buf, ret);
- ret = GNUTLS_E_AGAIN;
- }
- }
- END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, status,
- _("Error performing TLS handshake"), error);
-
- return status;
-}
-
-static void
-g_tls_connection_gnutls_complete_handshake (GTlsConnectionBase *tls,
- gchar **negotiated_protocol,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- gnutls_datum_t protocol;
-
- if (gnutls_alpn_get_selected_protocol (priv->session, &protocol) == 0 && protocol.size > 0)
- {
- g_assert (!*negotiated_protocol);
- *negotiated_protocol = g_strndup ((gchar *)protocol.data, protocol.size);
- }
-}
-
-static gboolean
-g_tls_connection_gnutls_is_session_resumed (GTlsConnectionBase *tls)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
-
- return gnutls_session_is_resumed (priv->session);
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_read (GTlsConnectionBase *tls,
- void *buffer,
- gsize count,
- gint64 timeout,
- gssize *nread,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsConnectionBaseStatus status;
- gssize ret;
-
- BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
- ret = gnutls_record_recv (priv->session, buffer, count);
- END_GNUTLS_IO (gnutls, G_IO_IN, ret, status, _("Error reading data from TLS socket"), error);
-
- *nread = MAX (ret, 0);
- return status;
-}
-
-static gsize
-input_vectors_from_gnutls_datum_t (GInputVector *vectors,
- guint num_vectors,
- const gnutls_datum_t *datum)
-{
- guint i;
- gsize total = 0;
-
- /* Copy into the receive vectors. */
- for (i = 0; i < num_vectors && total < datum->size; i++)
- {
- gsize count;
- GInputVector *vec = &vectors[i];
-
- count = MIN (vec->size, datum->size - total);
-
- memcpy (vec->buffer, datum->data + total, count);
- total += count;
- }
-
- g_assert (total <= datum->size);
-
- return total;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_read_message (GTlsConnectionBase *tls,
- GInputVector *vectors,
- guint num_vectors,
- gint64 timeout,
- gssize *nread,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsConnectionBaseStatus status;
- gssize ret;
- gnutls_packet_t packet = { 0, };
-
- BEGIN_GNUTLS_IO (gnutls, G_IO_IN, timeout, cancellable);
-
- /* Receive the entire datagram (zero-copy). */
- ret = gnutls_record_recv_packet (priv->session, &packet);
-
- if (ret > 0)
- {
- gnutls_datum_t data = { 0, };
-
- gnutls_packet_get (packet, &data, NULL);
- ret = input_vectors_from_gnutls_datum_t (vectors, num_vectors, &data);
- gnutls_packet_deinit (packet);
- }
-
- END_GNUTLS_IO (gnutls, G_IO_IN, ret, status, _("Error reading data from TLS socket"), error);
-
- *nread = MAX (ret, 0);
- return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_write (GTlsConnectionBase *tls,
- const void *buffer,
- gsize count,
- gint64 timeout,
- gssize *nwrote,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsConnectionBaseStatus status;
- gssize ret;
-
- BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
- ret = gnutls_record_send (priv->session, buffer, count);
- END_GNUTLS_IO (gnutls, G_IO_OUT, ret, status, _("Error writing data to TLS socket"), error);
-
- *nwrote = MAX (ret, 0);
- return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_write_message (GTlsConnectionBase *tls,
- GOutputVector *vectors,
- guint num_vectors,
- gint64 timeout,
- gssize *nwrote,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsConnectionBaseStatus status;
- gssize ret;
- guint i;
- gsize total_message_size;
-
- /* Calculate the total message size and check it’s not too big. */
- for (i = 0, total_message_size = 0; i < num_vectors; i++)
- total_message_size += vectors[i].size;
-
- if (g_tls_connection_base_is_dtls (tls) &&
- gnutls_dtls_get_data_mtu (priv->session) < total_message_size)
- {
- char *message;
- guint mtu = gnutls_dtls_get_data_mtu (priv->session);
-
- ret = GNUTLS_E_LARGE_PACKET;
- message = g_strdup_printf("%s %s",
- ngettext ("Message of size %lu byte is too large for DTLS connection",
- "Message of size %lu bytes is too large for DTLS connection",
total_message_size),
- ngettext ("(maximum is %u byte)", "(maximum is %u bytes)", mtu));
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
- message,
- total_message_size,
- mtu);
- g_free (message);
-
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- /* Queue up the data from all the vectors. */
- gnutls_record_cork (priv->session);
-
- for (i = 0; i < num_vectors; i++)
- {
- ret = gnutls_record_send (priv->session,
- vectors[i].buffer, vectors[i].size);
-
- if (ret < 0 || ret < vectors[i].size)
- {
- /* Uncork to restore state, then bail. The peer will receive a
- * truncated datagram. */
- break;
- }
- }
-
- BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, timeout, cancellable);
- ret = gnutls_record_uncork (priv->session, 0 /* flags */);
- END_GNUTLS_IO (gnutls, G_IO_OUT, ret, status, _("Error writing data to TLS socket"), error);
-
- *nwrote = MAX (ret, 0);
- return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_gnutls_close (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (tls);
- GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls);
- GTlsConnectionBaseStatus status;
- int ret;
-
- BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, timeout, cancellable);
- ret = gnutls_bye (priv->session, GNUTLS_SHUT_WR);
- END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, status, _("Error performing TLS close: %s"), error);
-
- return status;
-}
-
-static void
-initialize_gnutls_priority (void)
-{
- const gchar *priority_override;
- const gchar *error_pos = NULL;
- int ret;
-
- g_assert (!priority);
-
- priority_override = g_getenv ("G_TLS_GNUTLS_PRIORITY");
- if (priority_override)
- {
- ret = gnutls_priority_init2 (&priority, priority_override, &error_pos, 0);
- if (ret != GNUTLS_E_SUCCESS)
- g_warning ("Failed to set GnuTLS session priority with beginning at %s: %s", error_pos,
gnutls_strerror (ret));
- return;
- }
-
- ret = gnutls_priority_init2 (&priority, "%COMPAT:-VERS-TLS1.1:-VERS-TLS1.0", &error_pos,
GNUTLS_PRIORITY_INIT_DEF_APPEND);
- if (ret != GNUTLS_E_SUCCESS)
- g_warning ("Failed to set GnuTLS session priority with error beginning at %s: %s", error_pos,
gnutls_strerror (ret));
+ return thread;
}
static void
g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS (klass);
- gobject_class->finalize = g_tls_connection_gnutls_finalize;
-
- base_class->prepare_handshake = g_tls_connection_gnutls_prepare_handshake;
- base_class->handshake_thread_safe_renegotiation_status =
g_tls_connection_gnutls_handshake_thread_safe_renegotiation_status;
- base_class->handshake_thread_request_rehandshake =
g_tls_connection_gnutls_handshake_thread_request_rehandshake;
- base_class->handshake_thread_handshake =
g_tls_connection_gnutls_handshake_thread_handshake;
- base_class->retrieve_peer_certificate = g_tls_connection_gnutls_retrieve_peer_certificate;
- base_class->complete_handshake = g_tls_connection_gnutls_complete_handshake;
- base_class->is_session_resumed = g_tls_connection_gnutls_is_session_resumed;
- base_class->read_fn = g_tls_connection_gnutls_read;
- base_class->read_message_fn = g_tls_connection_gnutls_read_message;
- base_class->write_fn = g_tls_connection_gnutls_write;
- base_class->write_message_fn = g_tls_connection_gnutls_write_message;
- base_class->close_fn = g_tls_connection_gnutls_close;
-
- initialize_gnutls_priority ();
+ base_class->create_op_thread = g_tls_connection_gnutls_create_op_thread;
}
static void
g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface)
{
+ g_tls_connection_gnutls_parent_initable_iface = g_type_interface_peek_parent (iface);
+
iface->init = g_tls_connection_gnutls_initable_init;
}
diff --git a/tls/gnutls/gtlsconnection-gnutls.h b/tls/gnutls/gtlsconnection-gnutls.h
index 55ae5ee..d7747d9 100644
--- a/tls/gnutls/gtlsconnection-gnutls.h
+++ b/tls/gnutls/gtlsconnection-gnutls.h
@@ -41,13 +41,4 @@ struct _GTlsConnectionGnutlsClass
GTlsConnectionBaseClass parent_class;
};
-gnutls_certificate_credentials_t g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *connection);
-
-gnutls_session_t g_tls_connection_gnutls_get_session (GTlsConnectionGnutls *connection);
-
-void g_tls_connection_gnutls_handshake_thread_get_certificate (GTlsConnectionGnutls *gnutls,
- gnutls_pcert_st **pcert,
- unsigned int *pcert_length,
- gnutls_privkey_t *pkey);
-
G_END_DECLS
diff --git a/tls/gnutls/gtlsoperationsthread-gnutls.c b/tls/gnutls/gtlsoperationsthread-gnutls.c
new file mode 100644
index 0000000..196125f
--- /dev/null
+++ b/tls/gnutls/gtlsoperationsthread-gnutls.c
@@ -0,0 +1,1458 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2009 Red Hat, Inc
+ * Copyright 2015, 2016 Collabora, Ltd.
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#include "config.h"
+#include "gtlsoperationsthread-gnutls.h"
+
+#include "gtlsbackend-gnutls.h"
+#include "gtlscertificate-gnutls.h"
+#include "gtlsconnection-gnutls.h"
+
+#include <errno.h>
+#include <glib/gi18n-lib.h>
+#include <gnutls/dtls.h>
+#include <limits.h>
+
+struct _GTlsOperationsThreadGnutls {
+ GTlsOperationsThreadBase parent_instance;
+
+ guint init_flags;
+ gnutls_certificate_credentials_t creds;
+
+ /* session_data is either the session ticket that was used to resume this
+ * connection, or the most recent session ticket received from the server.
+ * Because session ticket reuse is generally undesirable, it should only be
+ * accessed if session_data_override is set.
+ */
+ GBytes *session_id;
+ GBytes *session_data;
+ gboolean session_data_override;
+
+ gnutls_session_t session;
+
+ GIOStream *base_iostream;
+ GInputStream *base_istream;
+ GOutputStream *base_ostream;
+ GDatagramBased *base_socket;
+
+ HandshakeContext *handshake_context;
+ gboolean handshaking;
+ gboolean ever_handshaked;
+
+ /* This data is valid only during current operation */
+ GTlsAuthenticationMode op_auth_mode;
+ GTlsCertificate *op_own_certificate;
+ GTlsCertificate *op_peer_certificate;
+ GCancellable *op_cancellable;
+ GError *op_error;
+
+ /* Certificate internals, must be kept alive here. */
+ gnutls_pcert_st *pcert;
+ unsigned int pcert_length;
+ gnutls_privkey_t pkey;
+
+ GList *accepted_cas;
+
+ gchar *server_identity;
+
+ gchar *interaction_id;
+};
+
+enum
+{
+ PROP_0,
+ PROP_GNUTLS_FLAGS,
+ LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+static gnutls_priority_t priority;
+
+static GInitableIface *g_tls_operations_thread_gnutls_parent_initable_iface;
+
+static void g_tls_operations_thread_gnutls_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GTlsOperationsThreadGnutls, g_tls_operations_thread_gnutls,
G_TYPE_TLS_OPERATIONS_THREAD_BASE,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ g_tls_operations_thread_gnutls_initable_iface_init);
+ )
+
+static inline gboolean
+is_dtls (GTlsOperationsThreadGnutls *self)
+{
+ return self->init_flags & GNUTLS_DATAGRAM;
+}
+
+static inline gboolean
+is_client (GTlsOperationsThreadGnutls *self)
+{
+ return self->init_flags & GNUTLS_CLIENT;
+}
+
+static inline gboolean
+is_server (GTlsOperationsThreadGnutls *self)
+{
+ return self->init_flags & GNUTLS_SERVER;
+}
+
+static void
+begin_gnutls_io (GTlsOperationsThreadGnutls *self,
+ GCancellable *cancellable)
+{
+ g_assert (!self->op_error);
+ g_assert (!self->op_cancellable);
+
+ self->op_cancellable = cancellable;
+}
+
+static GTlsOperationStatus
+end_gnutls_io (GTlsOperationsThreadGnutls *self,
+ GIOCondition direction,
+ int ret,
+ GError **error,
+ const char *err_prefix)
+{
+ GTlsOperationStatus status;
+ GError *my_error = NULL;
+
+ /* We intentionally do not check for GNUTLS_E_INTERRUPTED here
+ * Instead, the caller may poll for the source to become ready again.
+ * (Note that GTlsOutputStreamGnutls and GTlsInputStreamGnutls inherit
+ * from GPollableOutputStream and GPollableInputStream, respectively.)
+ * See also the comment in set_gnutls_error().
+ */
+ if (ret == GNUTLS_E_AGAIN ||
+ ret == GNUTLS_E_WARNING_ALERT_RECEIVED)
+ return G_TLS_OPERATION_TRY_AGAIN;
+
+ self->op_cancellable = NULL;
+
+ status = g_tls_operations_thread_base_pop_io (G_TLS_OPERATIONS_THREAD_BASE (self),
+ direction,
+ ret >= 0,
+ g_steal_pointer (&self->op_error),
+ &my_error);
+
+ if (status == G_TLS_OPERATION_SUCCESS ||
+ status == G_TLS_OPERATION_WOULD_BLOCK ||
+ status == G_TLS_OPERATION_TIMED_OUT)
+ {
+ if (my_error)
+ g_propagate_error (error, my_error);
+ return status;
+ }
+
+ g_assert (status == G_TLS_OPERATION_ERROR);
+
+ if (self->handshaking && !self->ever_handshaked)
+ {
+ if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_FAILED) ||
+ g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE))
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+ _("Peer failed to perform TLS handshake: %s"), my_error->message);
+ g_clear_error (&my_error);
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
+ status == GNUTLS_E_DECRYPTION_FAILED ||
+ status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET)
+ {
+ g_clear_error (&my_error);
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+ _("Peer failed to perform TLS handshake: %s"), gnutls_strerror (ret));
+ return G_TLS_OPERATION_ERROR;
+ }
+ }
+
+ if (ret == GNUTLS_E_REHANDSHAKE)
+ {
+ if (is_client (self))
+ {
+ /* Ignore server's request for rehandshake, because we no longer
+ * support obsolete TLS rehandshakes.
+ *
+ * TODO: Send GNUTLS_A_NO_RENEGOTIATION here once we support alerts.
+ */
+ return G_TLS_OPERATION_SUCCESS;
+ }
+ else
+ {
+ /* Are you hitting this error? If so, we may need to restore support
+ * for obsolete TLS rehandshakes. Hopefully not, because not many
+ * applications use GTlsServerConnection, and presumably not many
+ * clients request rehandshakes.
+ *
+ * The server cannot simply ignore a rehandshake request like clients
+ * can, so this is fatal.
+ */
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Client requested TLS rehandshake, which is no longer supported"));
+ return G_TLS_OPERATION_ERROR;
+ }
+ }
+
+ if (ret == GNUTLS_E_PREMATURE_TERMINATION)
+ {
+ if (self->handshaking && !self->ever_handshaked)
+ {
+ g_clear_error (&my_error);
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+ _("Peer failed to perform TLS handshake: %s"), gnutls_strerror (ret));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (g_tls_operations_thread_base_get_close_notify_required (G_TLS_OPERATIONS_THREAD_BASE (self)))
+ {
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF,
+ _("TLS connection closed unexpectedly"));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ return G_TLS_OPERATION_SUCCESS;
+ }
+
+ if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND || ret == GNUTLS_E_CERTIFICATE_REQUIRED)
+ {
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
+ _("TLS connection peer did not send a certificate"));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (ret == GNUTLS_E_CERTIFICATE_ERROR)
+ {
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Unacceptable TLS certificate"));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED)
+ {
+ g_clear_error (&my_error);
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Peer sent fatal TLS alert: %s"),
+ gnutls_alert_get_name (gnutls_alert_get (self->session)));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (ret == GNUTLS_E_INAPPROPRIATE_FALLBACK)
+ {
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_TLS_ERROR,
+ G_TLS_ERROR_INAPPROPRIATE_FALLBACK,
+ _("Protocol version downgrade attack detected"));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (ret == GNUTLS_E_LARGE_PACKET)
+ {
+ guint mtu = gnutls_dtls_get_data_mtu (self->session);
+ g_clear_error (&my_error);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+ ngettext ("Message is too large for DTLS connection; maximum is %u byte",
+ "Message is too large for DTLS connection; maximum is %u bytes", mtu), mtu);
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (ret == GNUTLS_E_TIMEDOUT)
+ {
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+ _("The operation timed out"));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (error && my_error)
+ g_propagate_error (error, my_error);
+
+ if (error && !*error)
+ {
+ *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s",
+ err_prefix, gnutls_strerror (ret));
+ }
+
+ return G_TLS_OPERATION_ERROR;
+}
+
+#define BEGIN_GNUTLS_IO(self, cancellable) \
+ begin_gnutls_io (self, cancellable); \
+ do {
+
+#define END_GNUTLS_IO(self, direction, ret, status, errmsg, err) \
+ status = end_gnutls_io (self, direction, ret, err, errmsg); \
+ } while (status == G_TLS_OPERATION_TRY_AGAIN);
+
+static void
+initialize_gnutls_priority (void)
+{
+ const gchar *priority_override;
+ const gchar *error_pos = NULL;
+ int ret;
+
+ g_assert (!priority);
+
+ priority_override = g_getenv ("G_TLS_GNUTLS_PRIORITY");
+ if (priority_override)
+ {
+ ret = gnutls_priority_init2 (&priority, priority_override, &error_pos, 0);
+ if (ret != GNUTLS_E_SUCCESS)
+ g_warning ("Failed to set GnuTLS session priority with beginning at %s: %s", error_pos,
gnutls_strerror (ret));
+ return;
+ }
+
+ ret = gnutls_priority_init2 (&priority, "%COMPAT:-VERS-TLS1.1:-VERS-TLS1.0", &error_pos,
GNUTLS_PRIORITY_INIT_DEF_APPEND);
+ if (ret != GNUTLS_E_SUCCESS)
+ g_warning ("Failed to set GnuTLS session priority with error beginning at %s: %s", error_pos,
gnutls_strerror (ret));
+}
+
+static GTlsCertificate *
+g_tls_operations_thread_gnutls_copy_certificate (GTlsOperationsThreadBase *base,
+ GTlsCertificate *cert)
+{
+ /* FIXME: need a real copy to avoid sharing the certificate across threads.
+ * Copy must copy private key. Must copy ENTIRE CHAIN including issuers.
+ */
+
+ return cert ? g_object_ref (cert) : NULL;
+}
+
+static void
+g_tls_operations_thread_gnutls_copy_client_session_state (GTlsOperationsThreadBase *base,
+ GTlsOperationsThreadBase *base_source)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+ GTlsOperationsThreadGnutls *source = G_TLS_OPERATIONS_THREAD_GNUTLS (base_source);
+
+ g_assert (is_client (self));
+
+ /* Precondition: source has handshaked, conn has not. */
+ g_return_if_fail (!self->session_id);
+ g_return_if_fail (source->session_id);
+
+ /* Prefer to use a new session ticket, if possible. */
+ self->session_data = g_tls_backend_gnutls_lookup_session_data (source->session_id);
+
+ if (!self->session_data && source->session_data)
+ {
+ /* If it's not possible, we'll try to reuse the old ticket, even though
+ * this is a privacy risk since TLS 1.3. Applications should not use this
+ * function unless they need us to try as hard as possible to resume a
+ * session, even at the cost of privacy.
+ */
+ self->session_data = g_bytes_ref (source->session_data);
+ }
+
+ self->session_data_override = !!self->session_data;
+}
+
+static void
+g_tls_operations_thread_gnutls_set_server_identity (GTlsOperationsThreadBase *base,
+ const gchar *server_identity)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+ gchar *normalized_hostname;
+ size_t len;
+
+ g_assert (is_client (self));
+
+ normalized_hostname = g_strdup (server_identity);
+ len = strlen (server_identity);
+
+ if (server_identity[len - 1] == '.')
+ {
+ normalized_hostname[len - 1] = '\0';
+ len--;
+ }
+
+ gnutls_server_name_set (self->session, GNUTLS_NAME_DNS,
+ normalized_hostname, len);
+
+ g_clear_pointer (&self->server_identity, g_free);
+ self->server_identity = g_steal_pointer (&normalized_hostname);
+}
+
+static void
+set_handshake_priority (GTlsOperationsThreadGnutls *self)
+{
+ int ret;
+
+ g_assert (priority);
+
+ ret = gnutls_priority_set (self->session, priority);
+ if (ret != GNUTLS_E_SUCCESS)
+ g_warning ("Failed to set GnuTLS session priority: %s", gnutls_strerror (ret));
+}
+
+static void
+set_handshake_timeout (GTlsOperationsThreadGnutls *self,
+ gint64 timeout)
+{
+ unsigned int timeout_ms;
+
+ /* Convert from microseconds to milliseconds, but ensure the timeout
+ * remains positive.
+ */
+ timeout_ms = (timeout + 999) / 1000;
+
+ if (is_dtls (self))
+ gnutls_dtls_set_timeouts (self->session, 1000 /* default */, timeout_ms);
+ else
+ gnutls_handshake_set_timeout (self->session, timeout_ms);
+}
+
+static void
+set_advertised_protocols (GTlsOperationsThreadGnutls *self,
+ const gchar **advertised_protocols)
+{
+ gnutls_datum_t *protocols;
+ int n_protos, i;
+
+ n_protos = g_strv_length ((gchar **)advertised_protocols);
+ protocols = g_new (gnutls_datum_t, n_protos);
+ for (i = 0; advertised_protocols[i]; i++)
+ {
+ protocols[i].size = strlen (advertised_protocols[i]);
+ protocols[i].data = (guchar *)advertised_protocols[i];
+ }
+ gnutls_alpn_set_protocols (self->session, protocols, n_protos, 0);
+ g_free (protocols);
+}
+
+static void
+compute_session_id (GTlsOperationsThreadGnutls *self)
+{
+ GSocketAddress *remote_addr;
+ GInetAddress *iaddr;
+ guint port;
+
+ g_assert (is_client (self));
+
+ /* The testsuite expects handshakes to actually happen. E.g. a test might
+ * check to see that a handshake succeeds and then later check that a new
+ * handshake fails. If we get really unlucky and the same port number is
+ * reused for the server socket between connections, then we'll accidentally
+ * resume the old session and skip certificate verification. Such failures
+ * are difficult to debug because they require running the tests hundreds of
+ * times simultaneously to reproduce (the port number does not get reused
+ * quickly enough if the tests are run sequentially).
+ *
+ * So session resumption will just need to be tested manually.
+ */
+ if (g_test_initialized ())
+ return;
+
+ /* Create a TLS "session ID." We base it on the IP address since
+ * different hosts serving the same hostname/service will probably
+ * not share the same session cache. We base it on the
+ * server-identity because at least some servers will fail (rather
+ * than just failing to resume the session) if we don't.
+ * (https://bugs.launchpad.net/bugs/823325)
+ *
+ * Note that our session IDs have no relation to TLS protocol
+ * session IDs, e.g. as provided by gnutls_session_get_id2(). Unlike
+ * our session IDs, actual TLS session IDs can no longer be used for
+ * session resumption.
+ */
+ if (G_IS_SOCKET_CONNECTION (self->base_iostream))
+ {
+ remote_addr = g_socket_connection_get_remote_address (G_SOCKET_CONNECTION (self->base_iostream), NULL);
+ if (G_IS_INET_SOCKET_ADDRESS (remote_addr))
+ {
+ GInetSocketAddress *isaddr = G_INET_SOCKET_ADDRESS (remote_addr);
+ const gchar *server_hostname;
+ gchar *addrstr;
+ gchar *session_id;
+ gchar *cert_hash = NULL;
+
+ iaddr = g_inet_socket_address_get_address (isaddr);
+ port = g_inet_socket_address_get_port (isaddr);
+
+ addrstr = g_inet_address_to_string (iaddr);
+ server_hostname = self->server_identity;
+
+ /* If we have a certificate, make its hash part of the session ID, so
+ * that different connections to the same server can use different
+ * certificates.
+ */
+ if (self->op_own_certificate)
+ {
+ GByteArray *der = NULL;
+ g_object_get (self->op_own_certificate,
+ "certificate", &der,
+ NULL);
+ if (der)
+ {
+ cert_hash = g_compute_checksum_for_data (G_CHECKSUM_SHA256, der->data, der->len);
+ g_byte_array_unref (der);
+ }
+ }
+
+ session_id = g_strdup_printf ("%s/%s/%d/%s", addrstr,
+ server_hostname ? server_hostname : "",
+ port,
+ cert_hash ? cert_hash : "");
+ self->session_id = g_bytes_new_take (session_id, strlen (session_id));
+ g_free (addrstr);
+ g_free (cert_hash);
+ }
+ g_object_unref (remote_addr);
+ }
+}
+
+static void
+set_session_data (GTlsOperationsThreadGnutls *self)
+{
+ g_assert (is_client (self));
+
+ compute_session_id (self);
+
+ if (self->session_data_override)
+ {
+ g_assert (self->session_data);
+ gnutls_session_set_data (self->session,
+ g_bytes_get_data (self->session_data, NULL),
+ g_bytes_get_size (self->session_data));
+ }
+ else if (self->session_id)
+ {
+ GBytes *session_data;
+
+ session_data = g_tls_backend_gnutls_lookup_session_data (self->session_id);
+ if (session_data)
+ {
+ gnutls_session_set_data (self->session,
+ g_bytes_get_data (session_data, NULL),
+ g_bytes_get_size (session_data));
+ g_clear_pointer (&self->session_data, g_bytes_unref);
+ self->session_data = g_steal_pointer (&session_data);
+ }
+ }
+}
+
+static void
+set_authentication_mode (GTlsOperationsThreadGnutls *self,
+ GTlsAuthenticationMode auth_mode)
+{
+ gnutls_certificate_request_t req = GNUTLS_CERT_IGNORE;
+
+ g_assert (is_server (self));
+
+ switch (auth_mode)
+ {
+ case G_TLS_AUTHENTICATION_REQUESTED:
+ req = GNUTLS_CERT_REQUEST;
+ break;
+ case G_TLS_AUTHENTICATION_REQUIRED:
+ req = GNUTLS_CERT_REQUIRE;
+ break;
+ default:
+ break;
+ }
+
+ gnutls_certificate_server_set_request (self->session, req);
+}
+
+static GTlsCertificate *
+get_peer_certificate (GTlsOperationsThreadGnutls *self)
+{
+ const gnutls_datum_t *certs;
+ unsigned int num_certs;
+
+ if (gnutls_certificate_type_get (self->session) == GNUTLS_CRT_X509)
+ {
+ certs = gnutls_certificate_get_peers (self->session, &num_certs);
+ if (certs && num_certs > 0)
+ return g_tls_certificate_gnutls_build_chain (certs, num_certs, GNUTLS_X509_FMT_DER);
+ }
+
+ return NULL;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_handshake (GTlsOperationsThreadBase *base,
+ HandshakeContext *context,
+ GTlsCertificate *own_certificate,
+ const gchar **advertised_protocols,
+ GTlsAuthenticationMode auth_mode,
+ gint64 timeout,
+ gchar **negotiated_protocol,
+ GList **accepted_cas,
+ GTlsCertificate **peer_certificate,
+ gboolean *session_resumed,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+ GTlsOperationStatus status;
+ gnutls_datum_t protocol;
+ int ret;
+
+ self->op_own_certificate = own_certificate;
+ self->op_auth_mode = auth_mode;
+
+ if (!self->ever_handshaked)
+ set_handshake_priority (self);
+
+ if (timeout > 0)
+ set_handshake_timeout (self, timeout);
+
+ if (advertised_protocols)
+ set_advertised_protocols (self, advertised_protocols);
+
+ if (is_client (self))
+ set_session_data (self);
+
+ if (is_server (self))
+ set_authentication_mode (self, auth_mode);
+
+ self->handshaking = TRUE;
+ self->handshake_context = context;
+
+ BEGIN_GNUTLS_IO (self, cancellable);
+ ret = gnutls_handshake (self->session);
+ END_GNUTLS_IO (self, G_IO_IN | G_IO_OUT, ret, status,
+ _("Error performing TLS handshake"), error);
+
+ self->op_own_certificate = NULL;
+ self->op_auth_mode = G_TLS_AUTHENTICATION_NONE;
+ self->handshake_context = NULL;
+ self->handshaking = FALSE;
+
+ if (status == G_TLS_OPERATION_SUCCESS)
+ self->ever_handshaked = TRUE;
+
+ if (gnutls_alpn_get_selected_protocol (self->session, &protocol) == 0 && protocol.size > 0)
+ *negotiated_protocol = g_strndup ((gchar *)protocol.data, protocol.size);
+ else
+ *negotiated_protocol = NULL;
+
+ *accepted_cas = g_list_copy (self->accepted_cas);
+
+ if (!self->op_peer_certificate)
+ self->op_peer_certificate = get_peer_certificate (self);
+ *peer_certificate = g_steal_pointer (&self->op_peer_certificate);
+
+ *session_resumed = gnutls_session_is_resumed (self->session);
+
+ return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_read (GTlsOperationsThreadBase *base,
+ void *buffer,
+ gsize size,
+ gssize *nread,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+ GTlsOperationStatus status;
+ gssize ret;
+
+ BEGIN_GNUTLS_IO (self, cancellable);
+ ret = gnutls_record_recv (self->session, buffer, size);
+ END_GNUTLS_IO (self, G_IO_IN, ret, status, _("Error reading data from TLS socket"), error);
+
+ *nread = MAX (ret, 0);
+ return status;
+}
+
+static gsize
+input_vectors_from_gnutls_datum_t (GInputVector *vectors,
+ guint num_vectors,
+ const gnutls_datum_t *datum)
+{
+ guint i;
+ gsize total = 0;
+
+ /* Copy into the receive vectors. */
+ for (i = 0; i < num_vectors && total < datum->size; i++)
+ {
+ gsize count;
+ GInputVector *vec = &vectors[i];
+
+ count = MIN (vec->size, datum->size - total);
+
+ memcpy (vec->buffer, datum->data + total, count);
+ total += count;
+ }
+
+ g_assert (total <= datum->size);
+
+ return total;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_read_message (GTlsOperationsThreadBase *base,
+ GInputVector *vectors,
+ guint num_vectors,
+ gssize *nread,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+ GTlsOperationStatus status;
+ gssize ret;
+ gnutls_packet_t packet = { 0, };
+
+ BEGIN_GNUTLS_IO (self, cancellable);
+
+ /* Receive the entire datagram (zero-copy). */
+ ret = gnutls_record_recv_packet (self->session, &packet);
+
+ if (ret > 0)
+ {
+ gnutls_datum_t data = { 0, };
+
+ gnutls_packet_get (packet, &data, NULL);
+ ret = input_vectors_from_gnutls_datum_t (vectors, num_vectors, &data);
+ gnutls_packet_deinit (packet);
+ }
+
+ END_GNUTLS_IO (self, G_IO_IN, ret, status, _("Error reading data from TLS socket"), error);
+
+ *nread = MAX (ret, 0);
+ return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_write (GTlsOperationsThreadBase *base,
+ const void *buffer,
+ gsize size,
+ gssize *nwrote,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+ GTlsOperationStatus status;
+ gssize ret;
+
+ BEGIN_GNUTLS_IO (self, cancellable);
+ ret = gnutls_record_send (self->session, buffer, size);
+ END_GNUTLS_IO (self, G_IO_OUT, ret, status, _("Error writing data to TLS socket"), error);
+
+ *nwrote = MAX (ret, 0);
+ return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_write_message (GTlsOperationsThreadBase *base,
+ GOutputVector *vectors,
+ guint num_vectors,
+ gssize *nwrote,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+ GTlsOperationStatus status;
+ gssize ret;
+ guint i;
+ gsize total_message_size;
+
+ /* Calculate the total message size and check it’s not too big. */
+ for (i = 0, total_message_size = 0; i < num_vectors; i++)
+ total_message_size += vectors[i].size;
+
+ if (is_dtls (self) &&
+ gnutls_dtls_get_data_mtu (self->session) < total_message_size)
+ {
+ char *message;
+ guint mtu = gnutls_dtls_get_data_mtu (self->session);
+
+ ret = GNUTLS_E_LARGE_PACKET;
+ message = g_strdup_printf("%s %s",
+ ngettext ("Message of size %lu byte is too large for DTLS connection",
+ "Message of size %lu bytes is too large for DTLS connection",
total_message_size),
+ ngettext ("(maximum is %u byte)", "(maximum is %u bytes)", mtu));
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
+ message,
+ total_message_size,
+ mtu);
+ g_free (message);
+
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ /* Queue up the data from all the vectors. */
+ gnutls_record_cork (self->session);
+
+ for (i = 0; i < num_vectors; i++)
+ {
+ ret = gnutls_record_send (self->session,
+ vectors[i].buffer, vectors[i].size);
+
+ if (ret < 0 || ret < vectors[i].size)
+ {
+ /* Uncork to restore state, then bail. The peer will receive a
+ * truncated datagram.
+ */
+ break;
+ }
+ }
+
+ BEGIN_GNUTLS_IO (self, cancellable);
+ ret = gnutls_record_uncork (self->session, 0 /* flags */);
+ END_GNUTLS_IO (self, G_IO_OUT, ret, status, _("Error writing data to TLS socket"), error);
+
+ *nwrote = MAX (ret, 0);
+ return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_gnutls_close (GTlsOperationsThreadBase *base,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (base);
+ GTlsOperationStatus status;
+ int ret;
+
+ BEGIN_GNUTLS_IO (self, cancellable);
+ ret = gnutls_bye (self->session, GNUTLS_SHUT_WR);
+ END_GNUTLS_IO (self, G_IO_IN | G_IO_OUT, ret, status, _("Error performing TLS close: %s"), error);
+
+ return status;
+}
+
+static void
+set_gnutls_error (GTlsOperationsThreadGnutls *self,
+ GError *error)
+{
+ /* We set EINTR rather than EAGAIN for G_IO_ERROR_WOULD_BLOCK so
+ * that GNUTLS_E_AGAIN only gets returned for gnutls-internal
+ * reasons, not for actual socket EAGAINs (and we have access
+ * to @error at the higher levels, so we can distinguish them
+ * that way later).
+ */
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ gnutls_transport_set_errno (self->session, EINTR);
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ {
+ /* Return EAGAIN while handshaking so that GnuTLS handles retries for us
+ * internally in its handshaking code.
+ */
+ if (is_dtls (self) && self->handshaking)
+ gnutls_transport_set_errno (self->session, EAGAIN);
+ else
+ gnutls_transport_set_errno (self->session, EINTR);
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+ gnutls_transport_set_errno (self->session, EINTR);
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE))
+ gnutls_transport_set_errno (self->session, EMSGSIZE);
+ else
+ gnutls_transport_set_errno (self->session, EIO);
+}
+
+static ssize_t
+g_tls_operations_thread_gnutls_pull_func (gnutls_transport_ptr_t transport_data,
+ void *buf,
+ size_t buflen)
+{
+ GTlsOperationsThreadGnutls *self = transport_data;
+ ssize_t ret;
+
+ /* If op_error is nonnull when we're called, it means
+ * that an error previously occurred, but GnuTLS decided not to
+ * propagate it. So it's correct for us to just clear it. (Usually
+ * this means it ignored an EAGAIN after a short read, and now
+ * we'll return EAGAIN again, which it will obey this time.)
+ */
+ g_clear_error (&self->op_error);
+
+ if (is_dtls (self))
+ {
+ GInputVector vector = { buf, buflen };
+ GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL };
+
+ ret = g_datagram_based_receive_messages (self->base_socket,
+ &message, 1,
+ 0, 0,
+ self->op_cancellable,
+ &self->op_error);
+
+ if (ret > 0)
+ ret = message.bytes_received;
+ }
+ else
+ {
+ ret = g_pollable_stream_read (self->base_istream,
+ buf, buflen,
+ FALSE,
+ self->op_cancellable,
+ &self->op_error);
+ }
+
+ if (ret < 0)
+ set_gnutls_error (self, self->op_error);
+
+ return ret;
+}
+
+static ssize_t
+g_tls_operations_thread_gnutls_push_func (gnutls_transport_ptr_t transport_data,
+ const void *buf,
+ size_t buflen)
+{
+ GTlsOperationsThreadGnutls *self = transport_data;
+ ssize_t ret;
+
+ /* See comment in pull_func. */
+ g_clear_error (&self->op_error);
+
+ if (is_dtls (self))
+ {
+ GOutputVector vector = { buf, buflen };
+ GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 };
+
+ ret = g_datagram_based_send_messages (self->base_socket,
+ &message, 1,
+ 0, 0,
+ self->op_cancellable,
+ &self->op_error);
+
+ if (ret > 0)
+ ret = message.bytes_sent;
+ }
+ else
+ {
+ ret = g_pollable_stream_write (self->base_ostream,
+ buf, buflen,
+ FALSE,
+ self->op_cancellable,
+ &self->op_error);
+ }
+
+ if (ret < 0)
+ set_gnutls_error (self, self->op_error);
+
+ return ret;
+}
+
+static ssize_t
+g_tls_operations_thread_gnutls_vec_push_func (gnutls_transport_ptr_t transport_data,
+ const giovec_t *iov,
+ int iovcnt)
+{
+ GTlsOperationsThreadGnutls *self = transport_data;
+ ssize_t ret;
+ GOutputMessage message = { NULL, };
+ GOutputVector *vectors;
+
+ g_assert (is_dtls (self));
+
+ /* See comment in pull_func. */
+ g_clear_error (&self->op_error);
+
+ /* this entire expression will be evaluated at compile time */
+ if (sizeof *iov == sizeof *vectors &&
+ sizeof iov->iov_base == sizeof vectors->buffer &&
+ G_STRUCT_OFFSET (giovec_t, iov_base) == G_STRUCT_OFFSET (GOutputVector, buffer) &&
+ sizeof iov->iov_len == sizeof vectors->size &&
+ G_STRUCT_OFFSET (giovec_t, iov_len) == G_STRUCT_OFFSET (GOutputVector, size))
+ /* ABI is compatible */
+ {
+ message.vectors = (GOutputVector *)iov;
+ message.num_vectors = iovcnt;
+ }
+ else
+ /* ABI is incompatible */
+ {
+ gint i;
+
+ message.vectors = g_newa (GOutputVector, iovcnt);
+ for (i = 0; i < iovcnt; i++)
+ {
+ message.vectors[i].buffer = (void *)iov[i].iov_base;
+ message.vectors[i].size = iov[i].iov_len;
+ }
+ message.num_vectors = iovcnt;
+ }
+
+ ret = g_datagram_based_send_messages (self->base_socket,
+ &message, 1,
+ 0, 0,
+ self->op_cancellable,
+ &self->op_error);
+
+ if (ret > 0)
+ ret = message.bytes_sent;
+ else if (ret < 0)
+ set_gnutls_error (self, self->op_error);
+
+ return ret;
+}
+
+static int
+g_tls_operations_thread_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data,
+ unsigned int ms)
+{
+ GTlsOperationsThreadGnutls *self = transport_data;
+
+ /* When using GNUTLS_NONBLOCK, this function will only be called for DTLS,
+ * and only with 0 timeout.
+ */
+ g_assert (is_dtls (self));
+ g_assert (ms == 0);
+
+ if (g_tls_operations_thread_base_check (G_TLS_OPERATIONS_THREAD_BASE (self), G_IO_IN) ||
+ g_cancellable_is_cancelled (self->op_cancellable))
+ return 1;
+
+ return 0;
+}
+
+static int
+verify_certificate_cb (gnutls_session_t session)
+{
+ GTlsOperationsThreadGnutls *self = gnutls_session_get_ptr (session);
+ gboolean accepted;
+
+ g_assert (!self->op_peer_certificate);
+ self->op_peer_certificate = get_peer_certificate (self);
+
+ if (self->op_peer_certificate)
+ {
+ accepted = g_tls_operations_thread_base_verify_certificate (G_TLS_OPERATIONS_THREAD_BASE (self),
+ self->op_peer_certificate,
+ self->handshake_context);
+ }
+ else
+ {
+ accepted = is_server (self) && self->op_auth_mode != G_TLS_AUTHENTICATION_REQUIRED;
+ }
+
+ /* Return 0 for the handshake to continue, non-zero to terminate.
+ * Complete opposite of what OpenSSL does.
+ */
+ return !accepted;
+}
+
+static int
+pin_request_cb (void *userdata,
+ int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int callback_flags,
+ char *pin,
+ size_t pin_max)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (userdata);
+ GTlsInteraction *interaction = g_tls_operations_thread_base_ref_interaction (G_TLS_OPERATIONS_THREAD_BASE
(self));
+ GTlsInteractionResult result;
+ GTlsPassword *password;
+ GTlsPasswordFlags password_flags = 0;
+ GError *error = NULL;
+ gchar *description;
+ int ret = -1;
+
+ if (!interaction)
+ return -1;
+
+ if (callback_flags & GNUTLS_PIN_WRONG)
+ password_flags |= G_TLS_PASSWORD_RETRY;
+ if (callback_flags & GNUTLS_PIN_COUNT_LOW)
+ password_flags |= G_TLS_PASSWORD_MANY_TRIES;
+ if (callback_flags & GNUTLS_PIN_FINAL_TRY || attempt > 5) /* Give up at some point */
+ password_flags |= G_TLS_PASSWORD_FINAL_TRY;
+
+ description = g_strdup_printf (" %s (%s)", token_label, token_url);
+ password = g_tls_password_new (password_flags, description);
+ result = g_tls_interaction_invoke_ask_password (interaction, password,
+ self->op_cancellable,
+ &error);
+ g_free (description);
+ g_object_unref (interaction);
+
+ switch (result)
+ {
+ case G_TLS_INTERACTION_FAILED:
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Error getting PIN: %s", error->message);
+ g_error_free (error);
+ break;
+ case G_TLS_INTERACTION_UNHANDLED:
+ break;
+ case G_TLS_INTERACTION_HANDLED:
+ {
+ gsize password_size;
+ const guchar *password_data = g_tls_password_get_value (password, &password_size);
+ if (password_size > pin_max)
+ g_warning ("PIN is larger than max PIN size");
+
+ memcpy (pin, password_data, MIN (password_size, pin_max));
+ ret = GNUTLS_E_SUCCESS;
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_object_unref (password);
+
+ return ret;
+}
+
+static void
+clear_own_certificate_internals (GTlsOperationsThreadGnutls *self)
+{
+ g_tls_certificate_gnutls_internals_free (self->pcert, self->pcert_length, self->pkey);
+
+ self->pcert = NULL;
+ self->pcert_length = 0;
+ self->pkey = NULL;
+}
+
+static void
+get_own_certificate_internals (GTlsOperationsThreadGnutls *self,
+ gnutls_pcert_st **pcert,
+ unsigned int *pcert_length,
+ gnutls_privkey_t *pkey)
+{
+ clear_own_certificate_internals (self);
+
+ if (self->op_own_certificate)
+ {
+ gnutls_privkey_t privkey;
+ gnutls_privkey_init (&privkey);
+ gnutls_privkey_set_pin_function (privkey, pin_request_cb, self);
+
+ g_tls_certificate_gnutls_copy_internals (G_TLS_CERTIFICATE_GNUTLS (self->op_own_certificate),
+ self->interaction_id,
+ pcert, pcert_length, &privkey);
+ *pkey = privkey;
+ }
+ else
+ {
+ *pcert = NULL;
+ *pcert_length = 0;
+ *pkey = NULL;
+ }
+}
+
+static int
+retrieve_own_certificate_cb (gnutls_session_t session,
+ const gnutls_datum_t *req_ca_rdn,
+ int nreqs,
+ const gnutls_pk_algorithm_t *pk_algos,
+ int pk_algos_length,
+ gnutls_pcert_st **pcert,
+ unsigned int *pcert_length,
+ gnutls_privkey_t *pkey)
+{
+ GTlsOperationsThreadGnutls *self = gnutls_transport_get_ptr (session);
+ GByteArray *dn;
+ int i;
+
+ if (is_client (self))
+ {
+ /* FIXME: Here we are supposed to ensure that the certificate supports one
+ * of the algorithms given in pk_algos.
+ */
+
+ if (self->accepted_cas)
+ {
+ g_list_free_full (self->accepted_cas, (GDestroyNotify)g_byte_array_unref);
+ self->accepted_cas = NULL;
+ }
+
+ for (i = 0; i < nreqs; i++)
+ {
+ dn = g_byte_array_new ();
+ g_byte_array_append (dn, req_ca_rdn[i].data, req_ca_rdn[i].size);
+ self->accepted_cas = g_list_prepend (self->accepted_cas, dn);
+ }
+
+ self->accepted_cas = g_list_reverse (self->accepted_cas);
+ }
+
+ get_own_certificate_internals (self, pcert, pcert_length, pkey);
+
+ if (is_client (self))
+ {
+ if (*pcert_length == 0)
+ {
+ g_clear_object (&self->op_own_certificate);
+ g_tls_certificate_gnutls_internals_free (*pcert, *pcert_length, *pkey);
+
+ if (g_tls_operations_thread_base_request_certificate (G_TLS_OPERATIONS_THREAD_BASE (self),
+ self->op_cancellable,
+ &self->op_own_certificate))
+ get_own_certificate_internals (self, pcert, pcert_length, pkey);
+
+ if (*pcert_length == 0)
+ {
+ g_tls_certificate_gnutls_internals_free (*pcert, *pcert_length, *pkey);
+
+ /* If there is still no client certificate, this connection will
+ * probably fail, but we must not give up yet. The certificate might
+ * be optional, e.g. if the server is using
+ * G_TLS_AUTHENTICATION_REQUESTED, not G_TLS_AUTHENTICATION_REQUIRED.
+ */
+ g_tls_operations_thread_base_set_missing_requested_client_certificate
(G_TLS_OPERATIONS_THREAD_BASE (self));
+ return 0;
+ }
+ }
+
+ if (!*pkey)
+ {
+ g_tls_certificate_gnutls_internals_free (*pcert, *pcert_length, *pkey);
+
+ /* No private key. GnuTLS expects it to be non-null if pcert_length is
+ * nonzero, so we have to abort now.
+ */
+ g_tls_operations_thread_base_set_missing_requested_client_certificate
(G_TLS_OPERATIONS_THREAD_BASE (self));
+ return -1;
+ }
+ }
+
+ self->pcert = *pcert;
+ self->pcert_length = *pcert_length;
+ self->pkey = *pkey;
+
+ return 0;
+}
+
+static int
+session_ticket_received_cb (gnutls_session_t session,
+ guint htype,
+ guint when,
+ guint incoming,
+ const gnutls_datum_t *msg)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (gnutls_session_get_ptr (session));
+ gnutls_datum_t session_datum;
+
+ if (gnutls_session_get_data2 (session, &session_datum) == GNUTLS_E_SUCCESS)
+ {
+ g_clear_pointer (&self->session_data, g_bytes_unref);
+ self->session_data = g_bytes_new_with_free_func (session_datum.data,
+ session_datum.size,
+ (GDestroyNotify)gnutls_free,
+ session_datum.data);
+
+ if (self->session_id)
+ {
+ g_tls_backend_gnutls_store_session_data (self->session_id,
+ self->session_data);
+ }
+ }
+
+ return 0;
+}
+
+static void
+g_tls_operations_thread_gnutls_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (object);
+
+ switch (prop_id)
+ {
+ case PROP_GNUTLS_FLAGS:
+ self->init_flags = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+g_tls_operations_thread_gnutls_finalize (GObject *object)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (object);
+
+ g_clear_pointer (&self->session, gnutls_deinit);
+ g_clear_pointer (&self->creds, gnutls_certificate_free_credentials);
+ g_clear_pointer (&self->session_id, g_bytes_unref);
+ g_clear_pointer (&self->session_data, g_bytes_unref);
+ g_clear_pointer (&self->server_identity, g_free);
+ g_clear_pointer (&self->interaction_id, g_free);
+
+ clear_own_certificate_internals (self);
+
+ if (self->accepted_cas)
+ {
+ g_list_free_full (self->accepted_cas, (GDestroyNotify)g_byte_array_unref);
+ self->accepted_cas = NULL;
+ }
+
+ g_assert (!self->op_peer_certificate);
+ g_assert (!self->op_own_certificate);
+
+ g_assert (!self->op_cancellable);
+ g_assert (!self->op_error);
+
+ G_OBJECT_CLASS (g_tls_operations_thread_gnutls_parent_class)->finalize (object);
+}
+
+static gboolean
+g_tls_operations_thread_gnutls_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadGnutls *self = G_TLS_OPERATIONS_THREAD_GNUTLS (initable);
+ int ret;
+
+ if (!g_tls_operations_thread_gnutls_parent_initable_iface->init (initable, cancellable, error))
+ return FALSE;
+
+ self->base_iostream = g_tls_operations_thread_base_get_base_iostream (G_TLS_OPERATIONS_THREAD_BASE (self));
+ if (self->base_iostream)
+ {
+ self->base_istream = g_io_stream_get_input_stream (self->base_iostream);
+ self->base_ostream = g_io_stream_get_output_stream (self->base_iostream);
+ }
+ else
+ self->base_socket = g_tls_operations_thread_base_get_base_socket (G_TLS_OPERATIONS_THREAD_BASE (self));
+
+ ret = gnutls_certificate_allocate_credentials (&self->creds);
+ if (ret != 0)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Failed to allocate credentials: %s"),
+ gnutls_strerror (ret));
+ return FALSE;
+ }
+ gnutls_certificate_set_retrieve_function2 (self->creds, retrieve_own_certificate_cb);
+
+ gnutls_init (&self->session, self->init_flags);
+
+ gnutls_session_set_ptr (self->session, self);
+ gnutls_session_set_verify_function (self->session, verify_certificate_cb);
+
+ ret = gnutls_credentials_set (self->session,
+ GNUTLS_CRD_CERTIFICATE,
+ self->creds);
+ if (ret != 0)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Could not create TLS connection: %s"),
+ gnutls_strerror (ret));
+ return FALSE;
+ }
+
+ gnutls_transport_set_push_function (self->session,
+ g_tls_operations_thread_gnutls_push_func);
+ gnutls_transport_set_pull_function (self->session,
+ g_tls_operations_thread_gnutls_pull_func);
+ gnutls_transport_set_pull_timeout_function (self->session,
+ g_tls_operations_thread_gnutls_pull_timeout_func);
+ gnutls_transport_set_ptr (self->session, self);
+
+ if (is_dtls (self))
+ {
+ /* GDatagramBased supports vectored I/O; GPollableOutputStream does not. */
+ gnutls_transport_set_vec_push_function (self->session,
+ g_tls_operations_thread_gnutls_vec_push_func);
+
+ /* Set reasonable MTU */
+ gnutls_dtls_set_mtu (self->session, 1400);
+ }
+
+ if (is_client (self))
+ {
+ gnutls_handshake_set_hook_function (self->session,
+ GNUTLS_HANDSHAKE_NEW_SESSION_TICKET,
+ GNUTLS_HOOK_POST,
+ session_ticket_received_cb);
+ }
+
+ return TRUE;
+}
+
+static void
+g_tls_operations_thread_gnutls_init (GTlsOperationsThreadGnutls *self)
+{
+ static int unique_interaction_id = 0;
+
+ self->interaction_id = g_strdup_printf ("gtls:%d", unique_interaction_id++);
+}
+
+static void
+g_tls_operations_thread_gnutls_class_init (GTlsOperationsThreadGnutlsClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GTlsOperationsThreadBaseClass *base_class = G_TLS_OPERATIONS_THREAD_BASE_CLASS (klass);
+
+ gobject_class->finalize = g_tls_operations_thread_gnutls_finalize;
+ gobject_class->set_property = g_tls_operations_thread_gnutls_set_property;
+
+ base_class->copy_certificate = g_tls_operations_thread_gnutls_copy_certificate;
+ base_class->copy_client_session_state = g_tls_operations_thread_gnutls_copy_client_session_state;
+ base_class->set_server_identity = g_tls_operations_thread_gnutls_set_server_identity;
+ base_class->handshake_fn = g_tls_operations_thread_gnutls_handshake;
+ base_class->read_fn = g_tls_operations_thread_gnutls_read;
+ base_class->read_message_fn = g_tls_operations_thread_gnutls_read_message;
+ base_class->write_fn = g_tls_operations_thread_gnutls_write;
+ base_class->write_message_fn = g_tls_operations_thread_gnutls_write_message;
+ base_class->close_fn = g_tls_operations_thread_gnutls_close;
+
+ obj_properties[PROP_GNUTLS_FLAGS] =
+ g_param_spec_uint ("gnutls-flags",
+ "GnuTLS flags",
+ "Flags for initializing GnuTLS session",
+ 0, UINT_MAX, 0,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
+
+ initialize_gnutls_priority ();
+}
+
+GTlsOperationsThreadBase *
+g_tls_operations_thread_gnutls_new (GTlsConnectionGnutls *connection,
+ GIOStream *base_iostream,
+ GDatagramBased *base_socket,
+ guint flags)
+{
+ return g_initable_new (G_TYPE_TLS_OPERATIONS_THREAD_GNUTLS,
+ NULL, NULL,
+ "base-io-stream", base_iostream,
+ "base-socket", base_socket,
+ "gnutls-flags", flags,
+ "thread-type", (flags & GNUTLS_CLIENT) ? G_TLS_OPERATIONS_THREAD_CLIENT :
G_TLS_OPERATIONS_THREAD_SERVER,
+ NULL);
+}
+
+static void
+g_tls_operations_thread_gnutls_initable_iface_init (GInitableIface *iface)
+{
+ g_tls_operations_thread_gnutls_parent_initable_iface = g_type_interface_peek_parent (iface);
+
+ iface->init = g_tls_operations_thread_gnutls_initable_init;
+}
diff --git a/tls/gnutls/gtlsoperationsthread-gnutls.h b/tls/gnutls/gtlsoperationsthread-gnutls.h
new file mode 100644
index 0000000..ba0990c
--- /dev/null
+++ b/tls/gnutls/gtlsoperationsthread-gnutls.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#pragma once
+
+#include "gtlsconnection-gnutls.h"
+#include "gtlsoperationsthread-base.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TLS_OPERATIONS_THREAD_GNUTLS (g_tls_operations_thread_gnutls_get_type ())
+
+G_DECLARE_FINAL_TYPE (GTlsOperationsThreadGnutls, g_tls_operations_thread_gnutls, G,
TLS_OPERATIONS_THREAD_GNUTLS, GTlsOperationsThreadBase)
+
+GTlsOperationsThreadBase *g_tls_operations_thread_gnutls_new (GTlsConnectionGnutls *connection,
+ GIOStream *base_iostream,
+ GDatagramBased *base_socket,
+ guint flags);
+
+G_END_DECLS
diff --git a/tls/gnutls/gtlsserverconnection-gnutls.c b/tls/gnutls/gtlsserverconnection-gnutls.c
index 090b57d..13f3f92 100644
--- a/tls/gnutls/gtlsserverconnection-gnutls.c
+++ b/tls/gnutls/gtlsserverconnection-gnutls.c
@@ -3,6 +3,8 @@
* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2010 Red Hat, Inc
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -45,25 +47,12 @@ struct _GTlsServerConnectionGnutls
GTlsConnectionGnutls parent_instance;
GTlsAuthenticationMode authentication_mode;
-
- gnutls_pcert_st *pcert;
- unsigned int pcert_length;
- gnutls_privkey_t pkey;
};
static void g_tls_server_connection_gnutls_initable_interface_init (GInitableIface *iface);
static void g_tls_server_connection_gnutls_server_connection_interface_init (GTlsServerConnectionInterface
*iface);
-static int g_tls_server_connection_gnutls_handshake_thread_retrieve_function (gnutls_session_t
session,
- const gnutls_datum_t
*req_ca_rdn,
- int
nreqs,
- const gnutls_pk_algorithm_t
*pk_algos,
- int
pk_algos_length,
- gnutls_pcert_st
**pcert,
- unsigned int
*pcert_length,
- gnutls_privkey_t
*pkey);
-
static GInitableIface *g_tls_server_connection_gnutls_parent_initable_iface;
G_DEFINE_TYPE_WITH_CODE (GTlsServerConnectionGnutls, g_tls_server_connection_gnutls,
G_TYPE_TLS_CONNECTION_GNUTLS,
@@ -75,46 +64,21 @@ G_DEFINE_TYPE_WITH_CODE (GTlsServerConnectionGnutls, g_tls_server_connection_gnu
NULL)
)
-static void
-clear_gnutls_certificate_copy (GTlsServerConnectionGnutls *gnutls)
-{
- g_tls_certificate_gnutls_copy_free (gnutls->pcert, gnutls->pcert_length, gnutls->pkey);
-
- gnutls->pcert = NULL;
- gnutls->pcert_length = 0;
- gnutls->pkey = NULL;
-}
-
static void
g_tls_server_connection_gnutls_init (GTlsServerConnectionGnutls *gnutls)
{
}
-static void
-g_tls_server_connection_gnutls_finalize (GObject *object)
-{
- GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (object);
-
- clear_gnutls_certificate_copy (gnutls);
-
- G_OBJECT_CLASS (g_tls_server_connection_gnutls_parent_class)->finalize (object);
-}
-
static gboolean
g_tls_server_connection_gnutls_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
- GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable);
GTlsCertificate *cert;
- gnutls_certificate_credentials_t creds;
if (!g_tls_server_connection_gnutls_parent_initable_iface->init (initable, cancellable, error))
return FALSE;
- creds = g_tls_connection_gnutls_get_credentials (G_TLS_CONNECTION_GNUTLS (gnutls));
- gnutls_certificate_set_retrieve_function2 (creds,
g_tls_server_connection_gnutls_handshake_thread_retrieve_function);
-
/* Currently we don't know ahead of time if a PKCS #11 backed certificate has a private key. */
cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (initable));
if (cert && !g_tls_certificate_gnutls_has_key (G_TLS_CERTIFICATE_GNUTLS (cert)) &&
@@ -166,70 +130,14 @@ g_tls_server_connection_gnutls_set_property (GObject *object,
}
}
-static int
-g_tls_server_connection_gnutls_handshake_thread_retrieve_function (gnutls_session_t session,
- const gnutls_datum_t *req_ca_rdn,
- int nreqs,
- const gnutls_pk_algorithm_t *pk_algos,
- int
pk_algos_length,
- gnutls_pcert_st **pcert,
- unsigned int
*pcert_length,
- gnutls_privkey_t *pkey)
-{
- GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (gnutls_transport_get_ptr (session));
-
- clear_gnutls_certificate_copy (gnutls);
-
- g_tls_connection_gnutls_handshake_thread_get_certificate (G_TLS_CONNECTION_GNUTLS (gnutls),
- pcert, pcert_length, pkey);
-
- gnutls->pcert = *pcert;
- gnutls->pcert_length = *pcert_length;
- gnutls->pkey = *pkey;
-
- return 0;
-}
-
-static void
-g_tls_server_connection_gnutls_prepare_handshake (GTlsConnectionBase *tls,
- gchar **advertised_protocols)
-{
- GTlsServerConnectionGnutls *gnutls = G_TLS_SERVER_CONNECTION_GNUTLS (tls);
- gnutls_session_t session;
- gnutls_certificate_request_t req_mode;
-
- switch (gnutls->authentication_mode)
- {
- case G_TLS_AUTHENTICATION_REQUESTED:
- req_mode = GNUTLS_CERT_REQUEST;
- break;
- case G_TLS_AUTHENTICATION_REQUIRED:
- req_mode = GNUTLS_CERT_REQUIRE;
- break;
- case G_TLS_AUTHENTICATION_NONE:
- default:
- req_mode = GNUTLS_CERT_IGNORE;
- break;
- }
-
- session = g_tls_connection_gnutls_get_session (G_TLS_CONNECTION_GNUTLS (tls));
- gnutls_certificate_server_set_request (session, req_mode);
-
- G_TLS_CONNECTION_BASE_CLASS (g_tls_server_connection_gnutls_parent_class)->prepare_handshake (tls,
advertised_protocols);
-}
-
static void
g_tls_server_connection_gnutls_class_init (GTlsServerConnectionGnutlsClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS (klass);
- gobject_class->finalize = g_tls_server_connection_gnutls_finalize;
gobject_class->get_property = g_tls_server_connection_gnutls_get_property;
gobject_class->set_property = g_tls_server_connection_gnutls_set_property;
- base_class->prepare_handshake = g_tls_server_connection_gnutls_prepare_handshake;
-
g_object_class_override_property (gobject_class, PROP_AUTHENTICATION_MODE, "authentication-mode");
}
diff --git a/tls/gnutls/meson.build b/tls/gnutls/meson.build
index ac46981..e993301 100644
--- a/tls/gnutls/meson.build
+++ b/tls/gnutls/meson.build
@@ -6,6 +6,7 @@ sources = files(
'gtlsconnection-gnutls.c',
'gtlsdatabase-gnutls.c',
'gtlsfiledatabase-gnutls.c',
+ 'gtlsoperationsthread-gnutls.c',
'gtlsserverconnection-gnutls.c'
)
diff --git a/tls/openssl/gtlsbio.c b/tls/openssl/gtlsbio.c
index b138432..74bf2d0 100644
--- a/tls/openssl/gtlsbio.c
+++ b/tls/openssl/gtlsbio.c
@@ -29,14 +29,8 @@
typedef struct {
GIOStream *io_stream;
- GCancellable *read_cancellable;
- GCancellable *write_cancellable;
- gboolean read_blocking;
- gboolean write_blocking;
- GError **read_error;
- GError **write_error;
- GMainContext *context;
- GMainLoop *loop;
+ GCancellable *cancellable;
+ GError **error;
} GTlsBio;
static void
@@ -44,9 +38,9 @@ free_gbio (gpointer user_data)
{
GTlsBio *bio = (GTlsBio *)user_data;
+ g_assert (!bio->cancellable);
+
g_object_unref (bio->io_stream);
- g_main_context_unref (bio->context);
- g_main_loop_unref (bio->loop);
g_free (bio);
}
@@ -147,7 +141,6 @@ gtls_bio_write (BIO *bio,
{
GTlsBio *gbio;
gssize written;
- GError *error = NULL;
if (
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
@@ -167,16 +160,14 @@ gtls_bio_write (BIO *bio,
BIO_clear_retry_flags (bio);
written = g_pollable_stream_write (g_io_stream_get_output_stream (gbio->io_stream),
in, inl,
- gbio->write_blocking,
- gbio->write_cancellable,
- &error);
+ FALSE,
+ gbio->cancellable,
+ gbio->error);
if (written == -1)
{
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ if (g_error_matches (*gbio->error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
BIO_set_retry_write (bio);
-
- g_propagate_error (gbio->write_error, error);
}
return written;
@@ -189,7 +180,6 @@ gtls_bio_read (BIO *bio,
{
GTlsBio *gbio;
gssize read;
- GError *error = NULL;
if (
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
@@ -209,32 +199,30 @@ gtls_bio_read (BIO *bio,
BIO_clear_retry_flags (bio);
read = g_pollable_stream_read (g_io_stream_get_input_stream (gbio->io_stream),
out, outl,
- gbio->read_blocking,
- gbio->read_cancellable,
- &error);
+ FALSE,
+ gbio->cancellable,
+ gbio->error);
if (read == -1)
{
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ if (g_error_matches (*gbio->error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
BIO_set_retry_read (bio);
-
- g_propagate_error (gbio->read_error, error);
}
return read;
}
static int
-gtls_bio_puts(BIO *bio,
- const char *str)
+gtls_bio_puts (BIO *bio,
+ const char *str)
{
return gtls_bio_write (bio, str, (int)strlen (str));
}
static int
-gtls_bio_gets(BIO *bio,
- char *buf,
- int len)
+gtls_bio_gets (BIO *bio,
+ char *buf,
+ int len)
{
return -1;
}
@@ -294,8 +282,6 @@ g_tls_bio_new (GIOStream *io_stream)
gbio = g_new0 (GTlsBio, 1);
gbio->io_stream = g_object_ref (io_stream);
- gbio->context = g_main_context_new ();
- gbio->loop = g_main_loop_new (gbio->context, FALSE);
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
ret->ptr = gbio;
@@ -309,72 +295,8 @@ g_tls_bio_new (GIOStream *io_stream)
}
void
-g_tls_bio_set_read_cancellable (BIO *bio,
- GCancellable *cancellable)
-{
- GTlsBio *gbio;
-
- g_return_if_fail (bio);
-
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
- gbio = (GTlsBio *)bio->ptr;
-#else
- gbio = BIO_get_data (bio);
-#endif
- gbio->read_cancellable = cancellable;
-}
-
-void
-g_tls_bio_set_read_blocking (BIO *bio,
- gboolean blocking)
-{
- GTlsBio *gbio;
-
- g_return_if_fail (bio);
-
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
- gbio = (GTlsBio *)bio->ptr;
-#else
- gbio = BIO_get_data (bio);
-#endif
- gbio->read_blocking = blocking;
-}
-
-void
-g_tls_bio_set_read_error (BIO *bio,
- GError **error)
-{
- GTlsBio *gbio;
-
- g_return_if_fail (bio);
-
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
- gbio = (GTlsBio *)bio->ptr;
-#else
- gbio = BIO_get_data (bio);
-#endif
- gbio->read_error = error;
-}
-
-void
-g_tls_bio_set_write_cancellable (BIO *bio,
- GCancellable *cancellable)
-{
- GTlsBio *gbio;
-
- g_return_if_fail (bio);
-
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
- gbio = (GTlsBio *)bio->ptr;
-#else
- gbio = BIO_get_data (bio);
-#endif
- gbio->write_cancellable = cancellable;
-}
-
-void
-g_tls_bio_set_write_blocking (BIO *bio,
- gboolean blocking)
+g_tls_bio_set_cancellable (BIO *bio,
+ GCancellable *cancellable)
{
GTlsBio *gbio;
@@ -385,12 +307,12 @@ g_tls_bio_set_write_blocking (BIO *bio,
#else
gbio = BIO_get_data (bio);
#endif
- gbio->write_blocking = blocking;
+ gbio->cancellable = cancellable;
}
void
-g_tls_bio_set_write_error (BIO *bio,
- GError **error)
+g_tls_bio_set_error (BIO *bio,
+ GError **error)
{
GTlsBio *gbio;
@@ -401,51 +323,5 @@ g_tls_bio_set_write_error (BIO *bio,
#else
gbio = BIO_get_data (bio);
#endif
- gbio->write_error = error;
-}
-
-static gboolean
-on_source_ready (GObject *pollable_stream,
- gpointer user_data)
-{
- GMainLoop *loop = user_data;
-
- g_main_loop_quit (loop);
-
- return G_SOURCE_REMOVE;
-}
-
-void
-g_tls_bio_wait_available (BIO *bio,
- GIOCondition condition,
- GCancellable *cancellable)
-{
- GTlsBio *gbio;
- GSource *source;
-
- g_return_if_fail (bio);
-
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
- gbio = (GTlsBio *)bio->ptr;
-#else
- gbio = BIO_get_data (bio);
-#endif
-
- g_main_context_push_thread_default (gbio->context);
-
- if (condition & G_IO_IN)
- source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (g_io_stream_get_input_stream
(gbio->io_stream)),
- cancellable);
- else
- source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (g_io_stream_get_output_stream
(gbio->io_stream)),
- cancellable);
-
- g_source_set_callback (source, (GSourceFunc)on_source_ready, gbio->loop, NULL);
- g_source_attach (source, gbio->context);
-
- g_main_loop_run (gbio->loop);
- g_main_context_pop_thread_default (gbio->context);
-
- g_source_destroy (source);
- g_source_unref (source);
+ gbio->error = error;
}
diff --git a/tls/openssl/gtlsbio.h b/tls/openssl/gtlsbio.h
index 09dccd3..f2122dd 100644
--- a/tls/openssl/gtlsbio.h
+++ b/tls/openssl/gtlsbio.h
@@ -32,22 +32,10 @@ G_BEGIN_DECLS
BIO *g_tls_bio_new (GIOStream *io_stream);
-void g_tls_bio_set_read_cancellable (BIO *bio,
+void g_tls_bio_set_cancellable (BIO *bio,
GCancellable *cancellable);
-void g_tls_bio_set_read_blocking (BIO *bio,
- gboolean blocking);
-
-void g_tls_bio_set_read_error (BIO *bio,
- GError **error);
-
-void g_tls_bio_set_write_cancellable (BIO *bio,
- GCancellable *cancellable);
-
-void g_tls_bio_set_write_blocking (BIO *bio,
- gboolean blocking);
-
-void g_tls_bio_set_write_error (BIO *bio,
+void g_tls_bio_set_error (BIO *bio,
GError **error);
void g_tls_bio_wait_available (BIO *bio,
diff --git a/tls/openssl/gtlsclientconnection-openssl.c b/tls/openssl/gtlsclientconnection-openssl.c
index 56e279f..2e0c2ce 100644
--- a/tls/openssl/gtlsclientconnection-openssl.c
+++ b/tls/openssl/gtlsclientconnection-openssl.c
@@ -3,6 +3,8 @@
* gtlsclientconnection-openssl.c
*
* Copyright (C) 2015 NICE s.r.l.
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This file is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -24,19 +26,18 @@
*/
#include "config.h"
-#include "glib.h"
-
-#include <errno.h>
-#include <string.h>
+#include "gtlsclientconnection-openssl.h"
#include "openssl-include.h"
#include "gtlsconnection-base.h"
-#include "gtlsclientconnection-openssl.h"
#include "gtlsbackend-openssl.h"
#include "gtlscertificate-openssl.h"
-#include <glib/gi18n-lib.h>
+#include "gtlsoperationsthread-base.h"
-#define DEFAULT_CIPHER_LIST "HIGH:!DSS:!aNULL@STRENGTH"
+#include <errno.h>
+#include <glib.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
struct _GTlsClientConnectionOpenssl
{
@@ -46,11 +47,7 @@ struct _GTlsClientConnectionOpenssl
GSocketConnectable *server_identity;
gboolean use_ssl3;
- STACK_OF (X509_NAME) *ca_list;
-
- SSL_SESSION *session;
- SSL *ssl;
- SSL_CTX *ssl_ctx;
+ GList *accepted_cas;
};
enum
@@ -74,20 +71,6 @@ G_DEFINE_TYPE_WITH_CODE (GTlsClientConnectionOpenssl, g_tls_client_connection_op
G_IMPLEMENT_INTERFACE (G_TYPE_TLS_CLIENT_CONNECTION,
g_tls_client_connection_openssl_client_connection_interface_init))
-static void
-g_tls_client_connection_openssl_finalize (GObject *object)
-{
- GTlsClientConnectionOpenssl *openssl = G_TLS_CLIENT_CONNECTION_OPENSSL (object);
-
- g_clear_object (&openssl->server_identity);
-
- SSL_free (openssl->ssl);
- SSL_CTX_free (openssl->ssl_ctx);
- SSL_SESSION_free (openssl->session);
-
- G_OBJECT_CLASS (g_tls_client_connection_openssl_parent_class)->finalize (object);
-}
-
static const gchar *
get_server_identity (GTlsClientConnectionOpenssl *openssl)
{
@@ -99,15 +82,31 @@ get_server_identity (GTlsClientConnectionOpenssl *openssl)
return NULL;
}
+static void
+g_tls_client_connection_openssl_set_accepted_cas (GTlsConnectionBase *tls,
+ GList *accepted_cas)
+{
+ GTlsClientConnectionOpenssl *openssl = G_TLS_CLIENT_CONNECTION_OPENSSL (tls);
+
+ if (openssl->accepted_cas)
+ g_list_free_full (openssl->accepted_cas, (GDestroyNotify)g_byte_array_unref);
+
+ openssl->accepted_cas = g_steal_pointer (&accepted_cas);
+}
+
+static void
+g_tls_client_connection_openssl_copy_session_state (GTlsClientConnection *conn,
+ GTlsClientConnection *source)
+{
+}
+
static void
g_tls_client_connection_openssl_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
GTlsClientConnectionOpenssl *openssl = G_TLS_CLIENT_CONNECTION_OPENSSL (object);
- GList *accepted_cas;
- gint i;
switch (prop_id)
{
@@ -124,30 +123,7 @@ g_tls_client_connection_openssl_get_property (GObject *object,
break;
case PROP_ACCEPTED_CAS:
- accepted_cas = NULL;
- if (openssl->ca_list)
- {
- for (i = 0; i < sk_X509_NAME_num (openssl->ca_list); ++i)
- {
- int size;
-
- size = i2d_X509_NAME (sk_X509_NAME_value (openssl->ca_list, i), NULL);
- if (size > 0)
- {
- unsigned char *ca;
-
- ca = g_malloc (size);
- size = i2d_X509_NAME (sk_X509_NAME_value (openssl->ca_list, i), &ca);
- if (size > 0)
- accepted_cas = g_list_prepend (accepted_cas, g_byte_array_new_take (
- ca, size));
- else
- g_free (ca);
- }
- }
- accepted_cas = g_list_reverse (accepted_cas);
- }
- g_value_set_pointer (value, accepted_cas);
+ g_value_set_pointer (value, g_list_copy (openssl->accepted_cas));
break;
default:
@@ -157,11 +133,12 @@ g_tls_client_connection_openssl_get_property (GObject *object,
static void
g_tls_client_connection_openssl_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
{
GTlsClientConnectionOpenssl *openssl = G_TLS_CLIENT_CONNECTION_OPENSSL (object);
+ const gchar *hostname;
switch (prop_id)
{
@@ -173,6 +150,18 @@ g_tls_client_connection_openssl_set_property (GObject *object,
if (openssl->server_identity)
g_object_unref (openssl->server_identity);
openssl->server_identity = g_value_dup_object (value);
+
+ // FIXME: this only sets the server identinty.
+ // We must also allow unsetting server identity on op thread, and for GnuTLS too
+ hostname = get_server_identity (openssl);
+ if (hostname)
+ {
+ GTlsOperationsThreadBase *thread;
+
+ thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (openssl));
+ if (thread)
+ g_tls_operations_thread_base_set_server_identity (thread, hostname);
+ }
break;
case PROP_USE_SSL3:
@@ -185,25 +174,24 @@ g_tls_client_connection_openssl_set_property (GObject *object,
}
static void
-g_tls_client_connection_openssl_complete_handshake (GTlsConnectionBase *tls,
- gchar **negotiated_protocol,
- GError **error)
+g_tls_client_connection_openssl_finalize (GObject *object)
{
- GTlsClientConnectionOpenssl *client = G_TLS_CLIENT_CONNECTION_OPENSSL (tls);
+ GTlsClientConnectionOpenssl *openssl = G_TLS_CLIENT_CONNECTION_OPENSSL (object);
- if (G_TLS_CONNECTION_BASE_CLASS (g_tls_client_connection_openssl_parent_class)->complete_handshake)
- G_TLS_CONNECTION_BASE_CLASS (g_tls_client_connection_openssl_parent_class)->complete_handshake (tls,
negotiated_protocol, error);
+ g_clear_object (&openssl->server_identity);
+
+ if (openssl->accepted_cas)
+ {
+ g_list_free_full (openssl->accepted_cas, (GDestroyNotify)g_byte_array_unref);
+ openssl->accepted_cas = NULL;
+ }
- /* It may have changed during the handshake, but we have to wait until here
- * because we can't emit notifies on the handshake thread.
- */
- g_object_notify (G_OBJECT (client), "accepted-cas");
+ G_OBJECT_CLASS (g_tls_client_connection_openssl_parent_class)->finalize (object);
}
-static SSL *
-g_tls_client_connection_openssl_get_ssl (GTlsConnectionOpenssl *connection)
+static void
+g_tls_client_connection_openssl_init (GTlsClientConnectionOpenssl *openssl)
{
- return G_TLS_CLIENT_CONNECTION_OPENSSL (connection)->ssl;
}
static void
@@ -211,15 +199,12 @@ g_tls_client_connection_openssl_class_init (GTlsClientConnectionOpensslClass *kl
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS (klass);
- GTlsConnectionOpensslClass *openssl_class = G_TLS_CONNECTION_OPENSSL_CLASS (klass);
gobject_class->finalize = g_tls_client_connection_openssl_finalize;
gobject_class->get_property = g_tls_client_connection_openssl_get_property;
gobject_class->set_property = g_tls_client_connection_openssl_set_property;
- base_class->complete_handshake = g_tls_client_connection_openssl_complete_handshake;
-
- openssl_class->get_ssl = g_tls_client_connection_openssl_get_ssl;
+ base_class->set_accepted_cas = g_tls_client_connection_openssl_set_accepted_cas;
g_object_class_override_property (gobject_class, PROP_VALIDATION_FLAGS, "validation-flags");
g_object_class_override_property (gobject_class, PROP_SERVER_IDENTITY, "server-identity");
@@ -227,204 +212,30 @@ g_tls_client_connection_openssl_class_init (GTlsClientConnectionOpensslClass *kl
g_object_class_override_property (gobject_class, PROP_ACCEPTED_CAS, "accepted-cas");
}
-static void
-g_tls_client_connection_openssl_init (GTlsClientConnectionOpenssl *openssl)
-{
-}
-
-static void
-g_tls_client_connection_openssl_copy_session_state (GTlsClientConnection *conn,
- GTlsClientConnection *source)
-{
-}
-
static void
g_tls_client_connection_openssl_client_connection_interface_init (GTlsClientConnectionInterface *iface)
{
iface->copy_session_state = g_tls_client_connection_openssl_copy_session_state;
}
-static int data_index = -1;
-
-static int
-handshake_thread_retrieve_certificate (SSL *ssl,
- X509 **x509,
- EVP_PKEY **pkey)
-{
- GTlsClientConnectionOpenssl *client;
- GTlsConnectionBase *tls;
- GTlsCertificate *cert;
-
- client = SSL_get_ex_data (ssl, data_index);
- tls = G_TLS_CONNECTION_BASE (client);
-
- client->ca_list = SSL_get_client_CA_list (client->ssl);
-
- cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (client));
- if (!cert)
- {
- if (g_tls_connection_base_handshake_thread_request_certificate (tls))
- cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (client));
- }
-
- if (cert)
- {
- EVP_PKEY *key;
-
- key = g_tls_certificate_openssl_get_key (G_TLS_CERTIFICATE_OPENSSL (cert));
- /* increase ref count */
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
- CRYPTO_add (&key->references, 1, CRYPTO_LOCK_EVP_PKEY);
-#else
- EVP_PKEY_up_ref (key);
-#endif
- *pkey = key;
-
- *x509 = X509_dup (g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (cert)));
-
- return 1;
- }
-
- g_tls_connection_base_handshake_thread_set_missing_requested_client_certificate (tls);
-
- return 0;
-}
-
-static gboolean
-set_cipher_list (GTlsClientConnectionOpenssl *client,
- GError **error)
-{
- const gchar *cipher_list;
-
- cipher_list = g_getenv ("G_TLS_OPENSSL_CIPHER_LIST");
- if (!cipher_list)
- cipher_list = DEFAULT_CIPHER_LIST;
-
- if (!SSL_CTX_set_cipher_list (client->ssl_ctx, cipher_list))
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS context: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- return TRUE;
-}
-
-#ifdef SSL_CTX_set1_sigalgs_list
-static void
-set_signature_algorithm_list (GTlsClientConnectionOpenssl *client)
-{
- const gchar *signature_algorithm_list;
-
- signature_algorithm_list = g_getenv ("G_TLS_OPENSSL_SIGNATURE_ALGORITHM_LIST");
- if (!signature_algorithm_list)
- return;
-
- SSL_CTX_set1_sigalgs_list (client->ssl_ctx, signature_algorithm_list);
-}
-#endif
-
-#ifdef SSL_CTX_set1_curves_list
-static void
-set_curve_list (GTlsClientConnectionOpenssl *client)
-{
- const gchar *curve_list;
-
- curve_list = g_getenv ("G_TLS_OPENSSL_CURVE_LIST");
- if (!curve_list)
- return;
-
- SSL_CTX_set1_curves_list (client->ssl_ctx, curve_list);
-}
-#endif
-
static gboolean
g_tls_client_connection_openssl_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
GTlsClientConnectionOpenssl *client = G_TLS_CLIENT_CONNECTION_OPENSSL (initable);
- long options;
- const char *hostname;
-
- client->session = SSL_SESSION_new ();
+ GTlsOperationsThreadBase *thread;
+ const gchar *hostname;
- client->ssl_ctx = SSL_CTX_new (SSLv23_client_method ());
- if (!client->ssl_ctx)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS context: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- if (!set_cipher_list (client, error))
+ if (!g_tls_client_connection_openssl_parent_initable_iface->init (initable, cancellable, error))
return FALSE;
- /* Only TLS 1.2 or higher */
- options = SSL_OP_NO_TICKET |
- SSL_OP_NO_COMPRESSION |
-#ifdef SSL_OP_NO_TLSv1_1
- SSL_OP_NO_TLSv1_1 |
-#endif
- SSL_OP_NO_SSLv2 |
- SSL_OP_NO_SSLv3 |
- SSL_OP_NO_TLSv1;
- SSL_CTX_set_options (client->ssl_ctx, options);
-
- SSL_CTX_clear_options (client->ssl_ctx, SSL_OP_LEGACY_SERVER_CONNECT);
-
hostname = get_server_identity (client);
-
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined (LIBRESSL_VERSION_NUMBER)
if (hostname)
{
- X509_VERIFY_PARAM *param;
-
- param = X509_VERIFY_PARAM_new ();
- X509_VERIFY_PARAM_set1_host (param, hostname, 0);
- SSL_CTX_set1_param (client->ssl_ctx, param);
- X509_VERIFY_PARAM_free (param);
+ thread = g_tls_connection_base_get_op_thread (G_TLS_CONNECTION_BASE (client));
+ g_tls_operations_thread_base_set_server_identity (thread, hostname);
}
-#endif
-
- SSL_CTX_add_session (client->ssl_ctx, client->session);
-
- SSL_CTX_set_client_cert_cb (client->ssl_ctx, handshake_thread_retrieve_certificate);
-
-#ifdef SSL_CTX_set1_sigalgs_list
- set_signature_algorithm_list (client);
-#endif
-
-#ifdef SSL_CTX_set1_curves_list
- set_curve_list (client);
-#endif
-
- client->ssl = SSL_new (client->ssl_ctx);
- if (!client->ssl)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS connection: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- if (data_index == -1) {
- data_index = SSL_get_ex_new_index (0, (void *)"gtlsclientconnection", NULL, NULL, NULL);
- }
- SSL_set_ex_data (client->ssl, data_index, client);
-
-#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
- if (hostname)
- SSL_set_tlsext_host_name (client->ssl, hostname);
-#endif
-
- SSL_set_connect_state (client->ssl);
-
- if (!g_tls_client_connection_openssl_parent_initable_iface->
- init (initable, cancellable, error))
- return FALSE;
return TRUE;
}
diff --git a/tls/openssl/gtlsconnection-openssl.c b/tls/openssl/gtlsconnection-openssl.c
index d751344..19cafe4 100644
--- a/tls/openssl/gtlsconnection-openssl.c
+++ b/tls/openssl/gtlsconnection-openssl.c
@@ -3,6 +3,8 @@
* gtlsconnection-openssl.c
*
* Copyright (C) 2015 NICE s.r.l.
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This file is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -26,6 +28,8 @@
#include "config.h"
#include "glib.h"
+/* FIXME: audit includes throughout the project */
+
#include <errno.h>
#include <stdarg.h>
#include "openssl-include.h"
@@ -34,548 +38,67 @@
#include "gtlsbackend-openssl.h"
#include "gtlscertificate-openssl.h"
#include "gtlsdatabase-openssl.h"
+#include "gtlsoperationsthread-openssl.h"
#include "gtlsbio.h"
#include <glib/gi18n-lib.h>
-typedef struct _GTlsConnectionOpensslPrivate
-{
- BIO *bio;
- GMutex ssl_mutex;
-
- gboolean shutting_down;
-} GTlsConnectionOpensslPrivate;
+static GInitableIface *g_tls_connection_openssl_parent_initable_iface;
static void g_tls_connection_openssl_initable_iface_init (GInitableIface *iface);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionOpenssl, g_tls_connection_openssl,
G_TYPE_TLS_CONNECTION_BASE,
- G_ADD_PRIVATE (GTlsConnectionOpenssl)
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
g_tls_connection_openssl_initable_iface_init))
-static void
-g_tls_connection_openssl_finalize (GObject *object)
-{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (object);
- GTlsConnectionOpensslPrivate *priv;
-
- priv = g_tls_connection_openssl_get_instance_private (openssl);
-
- g_mutex_clear (&priv->ssl_mutex);
-
- G_OBJECT_CLASS (g_tls_connection_openssl_parent_class)->finalize (object);
-}
-
-static GTlsSafeRenegotiationStatus
-g_tls_connection_openssl_handshake_thread_safe_renegotiation_status (GTlsConnectionBase *tls)
-{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
- SSL *ssl;
-
- ssl = g_tls_connection_openssl_get_ssl (openssl);
-
- return SSL_get_secure_renegotiation_support (ssl) ? G_TLS_SAFE_RENEGOTIATION_SUPPORTED_BY_PEER
- : G_TLS_SAFE_RENEGOTIATION_UNSUPPORTED;
-}
-
-static GTlsConnectionBaseStatus
-end_openssl_io (GTlsConnectionOpenssl *openssl,
- GIOCondition direction,
- int ret,
- gboolean blocking,
- GError **error,
- const char *err_prefix,
- const char *err_str)
+static GTlsOperationsThreadBase *
+g_tls_connection_openssl_create_op_thread (GTlsConnectionBase *tls)
{
- GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (openssl);
- GTlsConnectionOpensslPrivate *priv;
- int err_code, err, err_lib, reason;
- GError *my_error = NULL;
- GTlsConnectionBaseStatus status;
- SSL *ssl;
-
- priv = g_tls_connection_openssl_get_instance_private (openssl);
+ GTlsOperationsThreadBase *thread;
+ GTlsOperationsThreadType thread_type;
+ GIOStream *base_iostream = NULL;
- ssl = g_tls_connection_openssl_get_ssl (openssl);
-
- err_code = SSL_get_error (ssl, ret);
-
- status = g_tls_connection_base_pop_io (tls, direction, ret > 0, &my_error);
-
- if ((err_code == SSL_ERROR_WANT_READ ||
- err_code == SSL_ERROR_WANT_WRITE) &&
- blocking)
- {
- if (my_error)
- g_error_free (my_error);
- return G_TLS_CONNECTION_BASE_TRY_AGAIN;
- }
-
- if (err_code == SSL_ERROR_ZERO_RETURN)
- return G_TLS_CONNECTION_BASE_OK;
-
- if (status == G_TLS_CONNECTION_BASE_OK ||
- status == G_TLS_CONNECTION_BASE_WOULD_BLOCK ||
- status == G_TLS_CONNECTION_BASE_TIMED_OUT)
- {
- if (my_error)
- g_propagate_error (error, my_error);
- return status;
- }
-
- /* This case is documented that it may happen and that is perfectly fine */
- if (err_code == SSL_ERROR_SYSCALL &&
- ((priv->shutting_down && !my_error) ||
- g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE)))
- {
- g_clear_error (&my_error);
- return G_TLS_CONNECTION_BASE_OK;
- }
-
- err = ERR_get_error ();
- err_lib = ERR_GET_LIB (err);
- reason = ERR_GET_REASON (err);
-
- if (g_tls_connection_base_is_handshaking (tls) && !g_tls_connection_base_ever_handshaked (tls))
- {
- if (reason == SSL_R_BAD_PACKET_LENGTH ||
- reason == SSL_R_UNKNOWN_ALERT_TYPE ||
- reason == SSL_R_DECRYPTION_FAILED ||
- reason == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC ||
- reason == SSL_R_BAD_PROTOCOL_VERSION_NUMBER ||
- reason == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE ||
- reason == SSL_R_UNKNOWN_PROTOCOL)
- {
- g_clear_error (&my_error);
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
- _("Peer failed to perform TLS handshake: %s"), ERR_reason_error_string (err));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
- }
-
-#ifdef SSL_R_SHUTDOWN_WHILE_IN_INIT
- /* XXX: this error happens on ubuntu when shutting down the connection, it
- * seems to be a bug in a specific version of openssl, so let's handle it
- * gracefully
- */
- if (reason == SSL_R_SHUTDOWN_WHILE_IN_INIT)
- {
- g_clear_error (&my_error);
- return G_TLS_CONNECTION_BASE_OK;
- }
-#endif
-
- if (reason == SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE
-#ifdef SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
- || reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
-#endif
- )
- {
- g_clear_error (&my_error);
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
- _("TLS connection peer did not send a certificate"));
- return status;
- }
-
- if (reason == SSL_R_CERTIFICATE_VERIFY_FAILED)
- {
- g_clear_error (&my_error);
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Unacceptable TLS certificate"));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (reason == SSL_R_TLSV1_ALERT_UNKNOWN_CA)
- {
- g_clear_error (&my_error);
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Unacceptable TLS certificate authority"));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- if (err_lib == ERR_LIB_RSA && reason == RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY)
- {
- g_clear_error (&my_error);
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Digest too big for RSA key"));
- return G_TLS_CONNECTION_BASE_ERROR;
- }
+ g_object_get (tls,
+ "base-io-stream", &base_iostream,
+ NULL);
- if (my_error)
- g_propagate_error (error, my_error);
+ if (G_IS_TLS_CLIENT_CONNECTION (tls))
+ thread_type = G_TLS_OPERATIONS_THREAD_CLIENT;
else
- /* FIXME: this is just for debug */
- g_message ("end_openssl_io %s: %d, %d, %d", G_IS_TLS_CLIENT_CONNECTION (openssl) ? "client" : "server",
err_code, err_lib, reason);
-
- if (error && !*error)
- *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s", err_prefix, err_str);
-
- return G_TLS_CONNECTION_BASE_ERROR;
-}
-
-#define BEGIN_OPENSSL_IO(openssl, direction, timeout, cancellable) \
- do { \
- char error_str[256]; \
- g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl), \
- direction, timeout, cancellable);
-
-#define END_OPENSSL_IO(openssl, direction, ret, timeout, status, errmsg, err) \
- ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof(error_str)); \
- status = end_openssl_io (openssl, direction, ret, timeout == -1, err, errmsg, error_str); \
- } while (status == G_TLS_CONNECTION_BASE_TRY_AGAIN);
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_handshake_thread_request_rehandshake (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionOpenssl *openssl;
- GTlsConnectionBaseStatus status;
- SSL *ssl;
- int ret;
-
- /* On a client-side connection, SSL_renegotiate() itself will start
- * a rehandshake, so we only need to do something special here for
- * server-side connections.
- */
- if (!G_IS_TLS_SERVER_CONNECTION (tls))
- return G_TLS_CONNECTION_BASE_OK;
-
- openssl = G_TLS_CONNECTION_OPENSSL (tls);
-
- ssl = g_tls_connection_openssl_get_ssl (openssl);
-
- BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
- ret = SSL_renegotiate (ssl);
- END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
- _("Error performing TLS handshake"), error);
-
- return status;
-}
-
-static GTlsCertificate *
-g_tls_connection_openssl_retrieve_peer_certificate (GTlsConnectionBase *tls)
-{
- X509 *peer;
- STACK_OF (X509) *certs;
- GTlsCertificateOpenssl *chain;
- SSL *ssl;
-
- ssl = g_tls_connection_openssl_get_ssl (G_TLS_CONNECTION_OPENSSL (tls));
-
- peer = SSL_get_peer_certificate (ssl);
- if (!peer)
- return NULL;
-
- certs = SSL_get_peer_cert_chain (ssl);
- if (!certs)
- {
- X509_free (peer);
- return NULL;
- }
-
- chain = g_tls_certificate_openssl_build_chain (peer, certs);
- X509_free (peer);
- if (!chain)
- return NULL;
-
- return G_TLS_CERTIFICATE (chain);
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_handshake_thread_handshake (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
- GTlsConnectionBaseStatus status;
- SSL *ssl;
- int ret;
-
- ssl = g_tls_connection_openssl_get_ssl (openssl);
-
- BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
- ret = SSL_do_handshake (ssl);
- END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
- _("Error performing TLS handshake"), error);
-
- if (ret > 0)
- {
- if (!g_tls_connection_base_handshake_thread_verify_certificate (G_TLS_CONNECTION_BASE (openssl)))
- return G_TLS_CONNECTION_BASE_ERROR;
- }
-
- return status;
-}
-
-static void
-g_tls_connection_openssl_push_io (GTlsConnectionBase *tls,
- GIOCondition direction,
- gint64 timeout,
- GCancellable *cancellable)
-{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
- GTlsConnectionOpensslPrivate *priv;
- GError **error;
-
- priv = g_tls_connection_openssl_get_instance_private (openssl);
-
- G_TLS_CONNECTION_BASE_CLASS (g_tls_connection_openssl_parent_class)->push_io (tls, direction,
- timeout, cancellable);
+ thread_type = G_TLS_OPERATIONS_THREAD_SERVER;
- /* FIXME: need to support timeout > 0
- * This will require changes in GTlsBio */
+ thread = g_tls_operations_thread_openssl_new (base_iostream,
+ thread_type);
+ g_object_unref (base_iostream);
- if (direction & G_IO_IN)
- {
- error = g_tls_connection_base_get_read_error (tls);
- g_tls_bio_set_read_cancellable (priv->bio, cancellable);
- g_tls_bio_set_read_blocking (priv->bio, timeout == -1);
- g_clear_error (error);
- g_tls_bio_set_read_error (priv->bio, error);
- }
-
- if (direction & G_IO_OUT)
- {
- error = g_tls_connection_base_get_write_error (tls);
- g_tls_bio_set_write_cancellable (priv->bio, cancellable);
- g_tls_bio_set_write_blocking (priv->bio, timeout == -1);
- g_clear_error (error);
- g_tls_bio_set_write_error (priv->bio, error);
- }
-
- g_mutex_lock (&priv->ssl_mutex);
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_pop_io (GTlsConnectionBase *tls,
- GIOCondition direction,
- gboolean success,
- GError **error)
-{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
- GTlsConnectionOpensslPrivate *priv;
-
- priv = g_tls_connection_openssl_get_instance_private (openssl);
-
- g_mutex_unlock (&priv->ssl_mutex);
-
- if (direction & G_IO_IN)
- g_tls_bio_set_read_cancellable (priv->bio, NULL);
-
- if (direction & G_IO_OUT)
- g_tls_bio_set_write_cancellable (priv->bio, NULL);
-
- return G_TLS_CONNECTION_BASE_CLASS (g_tls_connection_openssl_parent_class)->pop_io (tls, direction,
- success, error);
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_read (GTlsConnectionBase *tls,
- void *buffer,
- gsize count,
- gint64 timeout,
- gssize *nread,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
- GTlsConnectionOpensslPrivate *priv;
- GTlsConnectionBaseStatus status;
- SSL *ssl;
- gssize ret;
-
- priv = g_tls_connection_openssl_get_instance_private (openssl);
-
- ssl = g_tls_connection_openssl_get_ssl (openssl);
-
- /* FIXME: revert back to use BEGIN/END_OPENSSL_IO once we move all the ssl
- * operations into a worker thread
- */
- while (TRUE)
- {
- char error_str[256];
-
- /* We want to always be non blocking here to avoid deadlocks */
- g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl),
- G_IO_IN, 0, cancellable);
-
- ret = SSL_read (ssl, buffer, count);
-
- ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof (error_str));
- status = end_openssl_io (openssl, G_IO_IN, ret, timeout == -1, error,
- _("Error reading data from TLS socket"), error_str);
-
- if (status != G_TLS_CONNECTION_BASE_TRY_AGAIN)
- break;
-
- /* Wait for the socket to be available again to avoid an infinite loop */
- g_tls_bio_wait_available (priv->bio, G_IO_IN, cancellable);
- }
-
- *nread = MAX (ret, 0);
- return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_write (GTlsConnectionBase *tls,
- const void *buffer,
- gsize count,
- gint64 timeout,
- gssize *nwrote,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
- GTlsConnectionOpensslPrivate *priv;
- GTlsConnectionBaseStatus status;
- SSL *ssl;
- gssize ret;
-
- priv = g_tls_connection_openssl_get_instance_private (openssl);
-
- ssl = g_tls_connection_openssl_get_ssl (openssl);
-
- while (TRUE)
- {
- char error_str[256];
-
- /* We want to always be non blocking here to avoid deadlocks */
- g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl),
- G_IO_OUT, 0, cancellable);
-
- ret = SSL_write (ssl, buffer, count);
-
- ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof (error_str));
- status = end_openssl_io (openssl, G_IO_OUT, ret, timeout == -1, error,
- _("Error writing data to TLS socket"), error_str);
-
- if (status != G_TLS_CONNECTION_BASE_TRY_AGAIN)
- break;
-
- /* Wait for the socket to be available again to avoid an infinite loop */
- g_tls_bio_wait_available (priv->bio, G_IO_OUT, cancellable);
- }
-
- *nwrote = MAX (ret, 0);
- return status;
-}
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_close (GTlsConnectionBase *tls,
- gint64 timeout,
- GCancellable *cancellable,
- GError **error)
-{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
- GTlsConnectionOpensslPrivate *priv;
- GTlsConnectionBaseStatus status;
- SSL *ssl;
- int ret;
-
- ssl = g_tls_connection_openssl_get_ssl (openssl);
- priv = g_tls_connection_openssl_get_instance_private (openssl);
-
- priv->shutting_down = TRUE;
-
- BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
- ret = SSL_shutdown (ssl);
- /* Note it is documented that getting 0 is correct when shutting down since
- * it means it will close the write direction
- */
- ret = ret == 0 ? 1 : ret;
- END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
- _("Error performing TLS close"), error);
-
- return status;
+ return thread;
}
static void
g_tls_connection_openssl_class_init (GTlsConnectionOpensslClass *klass)
{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS (klass);
- object_class->finalize = g_tls_connection_openssl_finalize;
-
- base_class->handshake_thread_safe_renegotiation_status =
g_tls_connection_openssl_handshake_thread_safe_renegotiation_status;
- base_class->handshake_thread_request_rehandshake =
g_tls_connection_openssl_handshake_thread_request_rehandshake;
- base_class->handshake_thread_handshake =
g_tls_connection_openssl_handshake_thread_handshake;
- base_class->retrieve_peer_certificate =
g_tls_connection_openssl_retrieve_peer_certificate;
- base_class->push_io = g_tls_connection_openssl_push_io;
- base_class->pop_io = g_tls_connection_openssl_pop_io;
- base_class->read_fn = g_tls_connection_openssl_read;
- base_class->write_fn = g_tls_connection_openssl_write;
- base_class->close_fn = g_tls_connection_openssl_close;
+ base_class->create_op_thread = g_tls_connection_openssl_create_op_thread;
}
-static int data_index = -1;
-
static gboolean
g_tls_connection_openssl_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
- GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (initable);
- GTlsConnectionOpensslPrivate *priv;
- GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (initable);
- GIOStream *base_io_stream;
- SSL *ssl;
-
- g_object_get (tls,
- "base-io-stream", &base_io_stream,
- NULL);
- g_return_val_if_fail (base_io_stream, FALSE);
-
- priv = g_tls_connection_openssl_get_instance_private (openssl);
-
- ssl = g_tls_connection_openssl_get_ssl (openssl);
- g_assert (ssl);
-
- if (data_index == -1) {
- data_index = SSL_get_ex_new_index (0, (void *)"gtlsconnection", NULL, NULL, NULL);
- }
- SSL_set_ex_data (ssl, data_index, openssl);
-
- priv->bio = g_tls_bio_new (base_io_stream);
-
- SSL_set_bio (ssl, priv->bio, priv->bio);
-
- g_object_unref (base_io_stream);
-
- return TRUE;
+ return g_tls_connection_openssl_parent_initable_iface->init (initable, cancellable, error);
}
static void
g_tls_connection_openssl_initable_iface_init (GInitableIface *iface)
{
+ g_tls_connection_openssl_parent_initable_iface = g_type_interface_peek_parent (iface);
+
iface->init = g_tls_connection_openssl_initable_init;
}
static void
g_tls_connection_openssl_init (GTlsConnectionOpenssl *openssl)
{
- GTlsConnectionOpensslPrivate *priv;
-
- priv = g_tls_connection_openssl_get_instance_private (openssl);
-
- g_mutex_init (&priv->ssl_mutex);
-}
-
-SSL *
-g_tls_connection_openssl_get_ssl (GTlsConnectionOpenssl *openssl)
-{
- g_return_val_if_fail (G_IS_TLS_CONNECTION_OPENSSL (openssl), NULL);
-
- return G_TLS_CONNECTION_OPENSSL_GET_CLASS (openssl)->get_ssl (openssl);
-}
-
-GTlsConnectionOpenssl *
-g_tls_connection_openssl_get_connection_from_ssl (SSL *ssl)
-{
- g_return_val_if_fail (ssl, NULL);
-
- return SSL_get_ex_data (ssl, data_index);
}
diff --git a/tls/openssl/gtlsconnection-openssl.h b/tls/openssl/gtlsconnection-openssl.h
index 7b85fdc..b71263f 100644
--- a/tls/openssl/gtlsconnection-openssl.h
+++ b/tls/openssl/gtlsconnection-openssl.h
@@ -39,12 +39,6 @@ G_DECLARE_DERIVABLE_TYPE (GTlsConnectionOpenssl, g_tls_connection_openssl, G, TL
struct _GTlsConnectionOpensslClass
{
GTlsConnectionBaseClass parent_class;
-
- SSL *(*get_ssl) (GTlsConnectionOpenssl *connection);
};
-SSL *g_tls_connection_openssl_get_ssl (GTlsConnectionOpenssl *connection);
-
-GTlsConnectionOpenssl *g_tls_connection_openssl_get_connection_from_ssl (SSL *ssl);
-
G_END_DECLS
diff --git a/tls/openssl/gtlsoperationsthread-openssl.c b/tls/openssl/gtlsoperationsthread-openssl.c
new file mode 100644
index 0000000..e527232
--- /dev/null
+++ b/tls/openssl/gtlsoperationsthread-openssl.c
@@ -0,0 +1,877 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2015 NICE s.r.l.
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#include "config.h"
+#include "gtlsoperationsthread-openssl.h"
+
+#include "gtlsbio.h"
+#include "gtlscertificate-openssl.h"
+
+#include <glib/gi18n-lib.h>
+
+#define DEFAULT_CIPHER_LIST "HIGH:!DSS:!aNULL@STRENGTH"
+
+static int data_index = -1;
+
+struct _GTlsOperationsThreadOpenssl {
+ GTlsOperationsThreadBase parent_instance;
+
+ GTlsOperationsThreadType thread_type;
+
+ BIO *bio;
+ SSL_SESSION *session;
+ SSL *ssl;
+ SSL_CTX *ssl_ctx;
+
+ STACK_OF (X509_NAME) *ca_list;
+ gboolean ca_list_changed;
+
+ /* Valid only during current operation. */
+ GTlsCertificate *op_own_certificate;
+ GError *op_error;
+
+ gboolean handshaking;
+ gboolean ever_handshaked;
+ gboolean shutting_down;
+};
+
+static GInitableIface *g_tls_operations_thread_openssl_parent_initable_iface;
+
+static void g_tls_operations_thread_openssl_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GTlsOperationsThreadOpenssl, g_tls_operations_thread_openssl,
G_TYPE_TLS_OPERATIONS_THREAD_BASE,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ g_tls_operations_thread_openssl_initable_iface_init);
+ )
+
+static inline gboolean
+is_client (GTlsOperationsThreadOpenssl *self)
+{
+ return self->thread_type == G_TLS_OPERATIONS_THREAD_CLIENT;
+}
+
+static inline gboolean
+is_server (GTlsOperationsThreadOpenssl *self)
+{
+ return self->thread_type == G_TLS_OPERATIONS_THREAD_SERVER;
+}
+
+static GTlsCertificate *
+g_tls_operations_thread_openssl_copy_certificate (GTlsOperationsThreadBase *base,
+ GTlsCertificate *cert)
+{
+ /* FIXME: need a real copy to avoid sharing the certificate across threads.
+ * Copy must copy private key. Must copy ENTIRE CHAIN including issuers.
+ */
+
+ return cert ? g_object_ref (cert) : NULL;
+}
+
+static void
+g_tls_operations_thread_openssl_set_server_identity (GTlsOperationsThreadBase *base,
+ const gchar *server_identity)
+{
+ GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+
+ g_assert (is_client (self));
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined (LIBRESSL_VERSION_NUMBER)
+ if (server_identity)
+ {
+ X509_VERIFY_PARAM *param;
+
+ param = X509_VERIFY_PARAM_new ();
+ X509_VERIFY_PARAM_set1_host (param, server_identity, 0);
+ SSL_CTX_set1_param (self->ssl_ctx, param);
+ X509_VERIFY_PARAM_free (param);
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+ SSL_set_tlsext_host_name (self->ssl, server_identity);
+#endif
+ }
+#endif
+}
+
+static void
+begin_openssl_io (GTlsOperationsThreadOpenssl *self,
+ GCancellable *cancellable)
+{
+ g_tls_bio_set_cancellable (self->bio, cancellable);
+
+ g_assert (!self->op_error);
+ g_tls_bio_set_error (self->bio, &self->op_error);
+}
+
+static GTlsOperationStatus
+end_openssl_io (GTlsOperationsThreadOpenssl *self,
+ GIOCondition direction,
+ int ret,
+ GError **error,
+ const char *err_prefix)
+{
+ int err_code, err, err_lib, reason;
+ GError *my_error = NULL;
+ GTlsOperationStatus status;
+
+ g_tls_bio_set_cancellable (self->bio, NULL);
+
+ status = g_tls_operations_thread_base_pop_io (G_TLS_OPERATIONS_THREAD_BASE (self),
+ direction, ret > 0,
+ g_steal_pointer (&self->op_error),
+ &my_error);
+
+ err_code = SSL_get_error (self->ssl, ret);
+
+ if (err_code == SSL_ERROR_ZERO_RETURN)
+ return G_TLS_OPERATION_SUCCESS;
+
+ if (status == G_TLS_OPERATION_SUCCESS ||
+ status == G_TLS_OPERATION_WOULD_BLOCK ||
+ status == G_TLS_OPERATION_TIMED_OUT)
+ {
+ if (my_error)
+ g_propagate_error (error, my_error);
+ return status;
+ }
+
+ /* This case is documented that it may happen and that is perfectly fine */
+ if (err_code == SSL_ERROR_SYSCALL &&
+ ((self->shutting_down && !my_error) || g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE)))
+ {
+ g_clear_error (&my_error);
+ return G_TLS_OPERATION_SUCCESS;
+ }
+
+ err = ERR_get_error ();
+ err_lib = ERR_GET_LIB (err);
+ reason = ERR_GET_REASON (err);
+
+ if (self->handshaking && !self->ever_handshaked)
+ {
+ if (reason == SSL_R_BAD_PACKET_LENGTH ||
+ reason == SSL_R_UNKNOWN_ALERT_TYPE ||
+ reason == SSL_R_DECRYPTION_FAILED ||
+ reason == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC ||
+ reason == SSL_R_BAD_PROTOCOL_VERSION_NUMBER ||
+ reason == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE ||
+ reason == SSL_R_UNKNOWN_PROTOCOL)
+ {
+ g_clear_error (&my_error);
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS,
+ _("Peer failed to perform TLS handshake: %s"), ERR_reason_error_string (err));
+ return G_TLS_OPERATION_ERROR;
+ }
+ }
+
+#ifdef SSL_R_SHUTDOWN_WHILE_IN_INIT
+ /* XXX: this error happens on ubuntu when shutting down the connection, it
+ * seems to be a bug in a specific version of openssl, so let's handle it
+ * gracefully
+ */
+ if (reason == SSL_R_SHUTDOWN_WHILE_IN_INIT)
+ {
+ g_clear_error (&my_error);
+ return G_TLS_OPERATION_SUCCESS;
+ }
+#endif
+
+ if (reason == SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE
+#ifdef SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
+ || reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED
+#endif
+ )
+ {
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
+ _("TLS connection peer did not send a certificate"));
+ return status;
+ }
+
+ if (reason == SSL_R_CERTIFICATE_VERIFY_FAILED)
+ {
+ g_clear_error (&my_error);
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Unacceptable TLS certificate"));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (reason == SSL_R_TLSV1_ALERT_UNKNOWN_CA)
+ {
+ g_clear_error (&my_error);
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Unacceptable TLS certificate authority"));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (err_lib == ERR_LIB_RSA && reason == RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY)
+ {
+ g_clear_error (&my_error);
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Digest too big for RSA key"));
+ return G_TLS_OPERATION_ERROR;
+ }
+
+ if (my_error)
+ g_propagate_error (error, my_error);
+ else
+ /* FIXME: this is just for debug */
+ g_message ("end_openssl_io %s: %d, %d, %d", is_client (self) ? "client" : "server", err_code, err_lib,
reason);
+
+ if (error && !*error)
+ {
+ char error_str[256];
+ ERR_error_string_n (SSL_get_error (self->ssl, ret), error_str, sizeof (error_str));
+ *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s", err_prefix, error_str);
+ }
+
+ return G_TLS_OPERATION_ERROR;
+}
+
+#define BEGIN_OPENSSL_IO(self, cancellable) \
+ do { \
+ begin_openssl_io (self, cancellable);
+
+#define END_OPENSSL_IO(self, direction, ret, status, errmsg, err) \
+ status = end_openssl_io (self, direction, ret, err, errmsg); \
+ } while (status == G_TLS_OPERATION_TRY_AGAIN);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined (LIBRESSL_VERSION_NUMBER)
+static gboolean
+ssl_set_certificate (SSL *ssl,
+ GTlsCertificate *cert,
+ GError **error)
+{
+ EVP_PKEY *key;
+ X509 *x;
+ GTlsCertificate *issuer;
+
+ key = g_tls_certificate_openssl_get_key (G_TLS_CERTIFICATE_OPENSSL (cert));
+
+ if (!key)
+ {
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Certificate has no private key"));
+ return FALSE;
+ }
+
+ /* Note, order is important. If a certificate has been set previously,
+ * OpenSSL requires that the new certificate is set _before_ the new
+ * private key is set.
+ */
+ x = g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (cert));
+ if (SSL_use_certificate (ssl, x) <= 0)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("There is a problem with the certificate: %s"),
+ ERR_error_string (ERR_get_error (), NULL));
+ return FALSE;
+ }
+
+ if (SSL_use_PrivateKey (ssl, key) <= 0)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("There is a problem with the certificate private key: %s"),
+ ERR_error_string (ERR_get_error (), NULL));
+ return FALSE;
+ }
+
+ if (SSL_clear_chain_certs (ssl) == 0)
+ g_warning ("There was a problem clearing the chain certificates: %s",
+ ERR_error_string (ERR_get_error (), NULL));
+
+ /* Add all the issuers to create the full certificate chain */
+ for (issuer = g_tls_certificate_get_issuer (G_TLS_CERTIFICATE (cert));
+ issuer;
+ issuer = g_tls_certificate_get_issuer (issuer))
+ {
+ X509 *issuer_x;
+
+ issuer_x = g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (issuer));
+
+ /* Be careful here and duplicate the certificate since the ssl object
+ * will take the ownership
+ */
+ if (SSL_add1_chain_cert (ssl, issuer_x) == 0)
+ g_warning ("There was a problem adding the chain certificate: %s",
+ ERR_error_string (ERR_get_error (), NULL));
+ }
+
+ return TRUE;
+}
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+static gboolean
+ssl_ctx_set_certificate (SSL_CTX *ssl_ctx,
+ GTlsCertificate *cert,
+ GError **error)
+{
+ EVP_PKEY *key;
+ X509 *x;
+ GTlsCertificate *issuer;
+
+ key = g_tls_certificate_openssl_get_key (G_TLS_CERTIFICATE_OPENSSL (cert));
+
+ if (!key)
+ {
+ g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("Certificate has no private key"));
+ return FALSE;
+ }
+
+ if (SSL_CTX_use_PrivateKey (ssl_ctx, key) <= 0)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("There is a problem with the certificate private key: %s"),
+ ERR_error_string (ERR_get_error (), NULL));
+ return FALSE;
+ }
+
+ x = g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (cert));
+ if (SSL_CTX_use_certificate (ssl_ctx, x) <= 0)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ _("There is a problem with the certificate: %s"),
+ ERR_error_string (ERR_get_error (), NULL));
+ return FALSE;
+ }
+
+ /* Add all the issuers to create the full certificate chain */
+ for (issuer = g_tls_certificate_get_issuer (G_TLS_CERTIFICATE (cert));
+ issuer;
+ issuer = g_tls_certificate_get_issuer (issuer))
+ {
+ X509 *issuer_x;
+
+ /* Be careful here and duplicate the certificate since the context
+ * will take the ownership
+ */
+ issuer_x = X509_dup (g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (issuer)));
+ if (!SSL_CTX_add_extra_chain_cert (ssl_ctx, issuer_x))
+ g_warning ("There was a problem adding the extra chain certificate: %s",
+ ERR_error_string (ERR_get_error (), NULL));
+ }
+}
+#endif
+
+static GTlsCertificate *
+get_peer_certificate (GTlsOperationsThreadOpenssl *self)
+{
+ X509 *peer;
+ STACK_OF (X509) *certs;
+ GTlsCertificateOpenssl *chain;
+
+ peer = SSL_get_peer_certificate (self->ssl);
+ if (!peer)
+ return NULL;
+
+ certs = SSL_get_peer_cert_chain (self->ssl);
+ if (!certs)
+ {
+ X509_free (peer);
+ return NULL;
+ }
+
+ chain = g_tls_certificate_openssl_build_chain (peer, certs);
+ X509_free (peer);
+ if (!chain)
+ return NULL;
+
+ return G_TLS_CERTIFICATE (chain);
+}
+
+static int
+server_verify_callback (int preverify_ok,
+ X509_STORE_CTX *ctx)
+{
+ /* FIXME: The server connection currently accepts any client certificate.
+ * We should emit accept-certificate here and reject the certificate unless
+ * the callback returns TRUE.
+ */
+ return 1;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_openssl_handshake (GTlsOperationsThreadBase *base,
+ HandshakeContext *context,
+ GTlsCertificate *own_certificate,
+ const gchar **advertised_protocols,
+ GTlsAuthenticationMode auth_mode,
+ gint64 timeout,
+ gchar **negotiated_protocol,
+ GList **accepted_cas,
+ GTlsCertificate **peer_certificate,
+ gboolean *session_resumed,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+ GTlsOperationStatus status;
+ int ret;
+
+ /* FIXME: The handshake doesn't respect timeout. */
+
+ self->op_own_certificate = own_certificate;
+
+ if (is_server (self) && self->op_own_certificate)
+ {
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined (LIBRESSL_VERSION_NUMBER)
+ if (!ssl_set_certificate (self->ssl, self->op_own_certificate, error))
+ return G_TLS_OPERATION_ERROR;
+#else
+ if (!ssl_ctx_set_certificate (server->ssl_ctx, self->op_own_certificate, error))
+ return G_TLS_OPERATION_ERROR;
+#endif
+ }
+
+ if (is_client (self) && !self->op_own_certificate)
+ {
+ g_tls_operations_thread_base_request_certificate (base,
+ cancellable,
+ &self->op_own_certificate);
+ }
+
+ /* TODO: No support yet for ALPN. */
+ g_assert (!advertised_protocols);
+
+ if (is_server (self))
+ {
+ int req_mode = 0;
+
+ switch (auth_mode)
+ {
+ case G_TLS_AUTHENTICATION_REQUIRED:
+ req_mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ break;
+ case G_TLS_AUTHENTICATION_REQUESTED:
+ req_mode = SSL_VERIFY_PEER;
+ break;
+ case G_TLS_AUTHENTICATION_NONE:
+ default:
+ req_mode = SSL_VERIFY_NONE;
+ break;
+ }
+
+ SSL_set_verify (self->ssl, req_mode, server_verify_callback);
+ SSL_set_verify_depth (self->ssl, 0);
+ }
+
+ self->handshaking = TRUE;
+
+ BEGIN_OPENSSL_IO (self, cancellable);
+ ret = SSL_do_handshake (self->ssl);
+ END_OPENSSL_IO (self, G_IO_IN | G_IO_OUT, ret, status,
+ _("Error performing TLS handshake"), error);
+
+ self->handshaking = FALSE;
+
+ if (status == G_TLS_OPERATION_SUCCESS)
+ self->ever_handshaked = TRUE;
+
+ *peer_certificate = get_peer_certificate (self);
+
+ /* FIXME: this is really too late to be performing certificate verification.
+ * We should be doing it during the handshake.
+ */
+ if (ret > 0 &&
+ (!*peer_certificate || !g_tls_operations_thread_base_verify_certificate (G_TLS_OPERATIONS_THREAD_BASE
(self),
+ *peer_certificate,
+ context)))
+ {
+ status = G_TLS_OPERATION_ERROR;
+ }
+
+ self->op_own_certificate = NULL;
+
+ /* TODO: No support yet for ALPN. */
+ *negotiated_protocol = NULL;
+
+ if (self->ca_list)
+ {
+ for (int i = 0; i < sk_X509_NAME_num (self->ca_list); ++i)
+ {
+ int size;
+
+ size = i2d_X509_NAME (sk_X509_NAME_value (self->ca_list, i), NULL);
+ if (size > 0)
+ {
+ unsigned char *ca;
+
+ ca = g_malloc (size);
+ size = i2d_X509_NAME (sk_X509_NAME_value (self->ca_list, i), &ca);
+ if (size > 0)
+ *accepted_cas = g_list_prepend (*accepted_cas,
+ g_byte_array_new_take (ca, size));
+ else
+ g_free (ca);
+ }
+ }
+ *accepted_cas = g_list_reverse (*accepted_cas);
+ }
+
+ /* TODO: No support yet for session resumption. */
+ *session_resumed = FALSE;
+
+ return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_openssl_read (GTlsOperationsThreadBase *base,
+ void *buffer,
+ gsize size,
+ gssize *nread,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+ GTlsOperationStatus status;
+ gssize ret;
+
+ BEGIN_OPENSSL_IO (self, cancellable);
+ ret = SSL_read (self->ssl, buffer, size);
+ END_OPENSSL_IO (self, G_IO_OUT, ret, status,
+ _("Error reading data from TLS socket"), error);
+
+
+ *nread = MAX (ret, 0);
+ return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_openssl_write (GTlsOperationsThreadBase *base,
+ const void *buffer,
+ gsize size,
+ gssize *nwrote,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+ GTlsOperationStatus status;
+ gssize ret;
+
+ BEGIN_OPENSSL_IO (self, cancellable);
+ ret = SSL_write (self->ssl, buffer, size);
+ END_OPENSSL_IO (self, G_IO_OUT, ret, status,
+ _("Error writing data to TLS socket"), error);
+ *nwrote = MAX (ret, 0);
+ return status;
+}
+
+static GTlsOperationStatus
+g_tls_operations_thread_openssl_close (GTlsOperationsThreadBase *base,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (base);
+ GTlsOperationStatus status;
+ int ret;
+
+ self->shutting_down = TRUE;
+
+ BEGIN_OPENSSL_IO (self, cancellable);
+ ret = SSL_shutdown (self->ssl);
+ /* Note it is documented that getting 0 is correct when shutting down since
+ * it means it will close the write direction
+ */
+ ret = ret == 0 ? 1 : ret;
+ END_OPENSSL_IO (self, G_IO_IN | G_IO_OUT, ret, status,
+ _("Error performing TLS close"), error);
+
+ return status;
+}
+
+static gboolean
+set_cipher_list (GTlsOperationsThreadOpenssl *self,
+ GError **error)
+{
+ const gchar *cipher_list;
+
+ cipher_list = g_getenv ("G_TLS_OPENSSL_CIPHER_LIST");
+ if (!cipher_list)
+ cipher_list = DEFAULT_CIPHER_LIST;
+
+ if (!SSL_CTX_set_cipher_list (self->ssl_ctx, cipher_list))
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Could not create TLS context: %s"),
+ ERR_error_string (ERR_get_error (), NULL));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#ifdef SSL_CTX_set1_sigalgs_list
+static void
+set_signature_algorithm_list (GTlsOperationsThreadOpenssl *self)
+{
+ const gchar *signature_algorithm_list;
+
+ signature_algorithm_list = g_getenv ("G_TLS_OPENSSL_SIGNATURE_ALGORITHM_LIST");
+ if (!signature_algorithm_list)
+ return;
+
+ SSL_CTX_set1_sigalgs_list (self->ssl_ctx, signature_algorithm_list);
+}
+#endif
+
+#ifdef SSL_CTX_set1_curves_list
+static void
+set_curve_list (GTlsOperationsThreadOpenssl *self)
+{
+ const gchar *curve_list;
+
+ curve_list = g_getenv ("G_TLS_OPENSSL_CURVE_LIST");
+ if (!curve_list)
+ return;
+
+ SSL_CTX_set1_curves_list (self->ssl_ctx, curve_list);
+}
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
+static void
+ssl_info_callback (const SSL *ssl,
+ int type,
+ int val)
+{
+ g_assert (is_server (self));
+
+ if ((type & SSL_CB_HANDSHAKE_DONE) != 0)
+ {
+ /* Disable renegotiation (CVE-2009-3555) */
+ ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
+ }
+}
+#endif
+
+static int
+retrieve_own_certificate_cb (SSL *ssl,
+ X509 **x509,
+ EVP_PKEY **pkey)
+{
+ GTlsOperationsThreadOpenssl *self;
+ gboolean had_ca_list;
+
+ self = SSL_get_ex_data (ssl, data_index);
+
+ had_ca_list = self->ca_list != NULL;
+ self->ca_list = SSL_get_client_CA_list (self->ssl);
+ self->ca_list_changed = self->ca_list || had_ca_list;
+
+ if (self->op_own_certificate)
+ {
+ EVP_PKEY *key;
+
+ key = g_tls_certificate_openssl_get_key (G_TLS_CERTIFICATE_OPENSSL (self->op_own_certificate));
+ /* increase ref count */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
+ CRYPTO_add (&key->references, 1, CRYPTO_LOCK_EVP_PKEY);
+#else
+ EVP_PKEY_up_ref (key);
+#endif
+ *pkey = key;
+
+ *x509 = X509_dup (g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL
(self->op_own_certificate)));
+
+ return 1;
+ }
+
+ g_tls_operations_thread_base_set_missing_requested_client_certificate (G_TLS_OPERATIONS_THREAD_BASE
(self));
+
+ return 0;
+}
+
+static void
+g_tls_operations_thread_openssl_finalize (GObject *object)
+{
+ GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (object);
+
+ SSL_free (self->ssl);
+ SSL_CTX_free (self->ssl_ctx);
+ SSL_SESSION_free (self->session);
+
+ g_assert (!self->op_own_certificate);
+ g_assert (!self->op_error);
+
+ G_OBJECT_CLASS (g_tls_operations_thread_openssl_parent_class)->finalize (object);
+}
+
+static gboolean
+g_tls_operations_thread_openssl_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GTlsOperationsThreadOpenssl *self = G_TLS_OPERATIONS_THREAD_OPENSSL (initable);
+ GIOStream *base_iostream = NULL;
+ long options;
+
+ if (!g_tls_operations_thread_openssl_parent_initable_iface->init (initable, cancellable, error))
+ return FALSE;
+
+ g_object_get (self,
+ "base-io-stream", &base_iostream,
+ "thread-type", &self->thread_type,
+ NULL);
+ g_assert (base_iostream);
+
+ self->session = SSL_SESSION_new ();
+ self->ssl_ctx = SSL_CTX_new (is_client (self) ? SSLv23_client_method () : SSLv23_server_method ());
+ if (!self->ssl_ctx)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Could not create TLS context: %s"),
+ ERR_error_string (ERR_get_error (), NULL));
+ return FALSE;
+ }
+
+ if (!set_cipher_list (self, error))
+ return FALSE;
+
+ /* Only TLS 1.2 or higher */
+ options = SSL_OP_NO_TICKET |
+ SSL_OP_NO_COMPRESSION |
+#ifdef SSL_OP_NO_TLSv1_1
+ SSL_OP_NO_TLSv1_1 |
+#endif
+ SSL_OP_NO_SSLv2 |
+ SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_TLSv1;
+
+ if (is_server (self))
+ {
+ SSL_CTX_set_options (self->ssl_ctx, options);
+ }
+ else
+ {
+ options |= SSL_OP_CIPHER_SERVER_PREFERENCE |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+ SSL_OP_SINGLE_ECDH_USE;
+#ifdef SSL_OP_NO_RENEGOTIATION
+ options |= SSL_OP_NO_RENEGOTIATION;
+#endif
+ SSL_CTX_set_options (self->ssl_ctx, options);
+ SSL_CTX_clear_options (self->ssl_ctx, SSL_OP_LEGACY_SERVER_CONNECT);
+
+ SSL_CTX_set_client_cert_cb (self->ssl_ctx, retrieve_own_certificate_cb);
+ }
+
+ SSL_CTX_add_session (self->ssl_ctx, self->session);
+
+#ifdef SSL_CTX_set1_sigalgs_list
+ set_signature_algorithm_list (self);
+#endif
+
+#ifdef SSL_CTX_set1_curves_list
+ set_curve_list (self);
+#endif
+
+ if (is_server (self))
+ {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
+# ifdef SSL_CTX_set_ecdh_auto
+ SSL_CTX_set_ecdh_auto (self->ssl_ctx, 1);
+# else
+ {
+ EC_KEY *ecdh;
+
+ ecdh = EC_KEY_new_by_curve_name (NID_X9_62_prime256v1);
+ if (ecdh)
+ {
+ SSL_CTX_set_tmp_ecdh (self->ssl_ctx, ecdh);
+ EC_KEY_free (ecdh);
+ }
+ }
+# endif
+
+ SSL_CTX_set_info_callback (self->ssl_ctx, ssl_info_callback);
+#endif
+ }
+
+ self->ssl = SSL_new (self->ssl_ctx);
+ if (!self->ssl)
+ {
+ g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
+ _("Could not create TLS connection: %s"),
+ ERR_error_string (ERR_get_error (), NULL));
+ return FALSE;
+ }
+
+ self->bio = g_tls_bio_new (base_iostream);
+ SSL_set_bio (self->ssl, self->bio, self->bio);
+ g_object_unref (base_iostream);
+
+ if (data_index == -1)
+ data_index = SSL_get_ex_new_index (0, (void *)"gtlsoperationsthread", NULL, NULL, NULL);
+ SSL_set_ex_data (self->ssl, data_index, self);
+
+ if (is_client (self))
+ SSL_set_connect_state (self->ssl);
+ else
+ SSL_set_accept_state (self->ssl);
+
+ return TRUE;
+}
+
+static void
+g_tls_operations_thread_openssl_init (GTlsOperationsThreadOpenssl *self)
+{
+}
+
+static void
+g_tls_operations_thread_openssl_class_init (GTlsOperationsThreadOpensslClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GTlsOperationsThreadBaseClass *base_class = G_TLS_OPERATIONS_THREAD_BASE_CLASS (klass);
+
+ gobject_class->finalize = g_tls_operations_thread_openssl_finalize;
+
+ base_class->copy_certificate = g_tls_operations_thread_openssl_copy_certificate;
+ base_class->set_server_identity = g_tls_operations_thread_openssl_set_server_identity;
+ base_class->handshake_fn = g_tls_operations_thread_openssl_handshake;
+ base_class->read_fn = g_tls_operations_thread_openssl_read;
+ base_class->write_fn = g_tls_operations_thread_openssl_write;
+ base_class->close_fn = g_tls_operations_thread_openssl_close;
+}
+
+static void
+g_tls_operations_thread_openssl_initable_iface_init (GInitableIface *iface)
+{
+ g_tls_operations_thread_openssl_parent_initable_iface = g_type_interface_peek_parent (iface);
+
+ iface->init = g_tls_operations_thread_openssl_initable_init;
+}
+
+GTlsOperationsThreadBase *
+g_tls_operations_thread_openssl_new (GIOStream *base_iostream,
+ GTlsOperationsThreadType type)
+{
+ return g_initable_new (G_TYPE_TLS_OPERATIONS_THREAD_OPENSSL,
+ NULL, NULL,
+ "base-io-stream", base_iostream,
+ "thread-type", type,
+ NULL);
+}
diff --git a/tls/openssl/gtlsoperationsthread-openssl.h b/tls/openssl/gtlsoperationsthread-openssl.h
new file mode 100644
index 0000000..9a3ecc0
--- /dev/null
+++ b/tls/openssl/gtlsoperationsthread-openssl.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * In addition, when the library is used with OpenSSL, a special
+ * exception applies. Refer to the LICENSE_EXCEPTION file for details.
+ */
+
+#pragma once
+
+#include "gtlsconnection-openssl.h"
+#include "gtlsoperationsthread-base.h"
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TLS_OPERATIONS_THREAD_OPENSSL (g_tls_operations_thread_openssl_get_type ())
+
+G_DECLARE_FINAL_TYPE (GTlsOperationsThreadOpenssl, g_tls_operations_thread_openssl, G,
TLS_OPERATIONS_THREAD_OPENSSL, GTlsOperationsThreadBase)
+
+GTlsOperationsThreadBase *g_tls_operations_thread_openssl_new (GIOStream *base_iostream,
+ GTlsOperationsThreadType type);
+
+G_END_DECLS
diff --git a/tls/openssl/gtlsserverconnection-openssl.c b/tls/openssl/gtlsserverconnection-openssl.c
index df451d4..4e98a73 100644
--- a/tls/openssl/gtlsserverconnection-openssl.c
+++ b/tls/openssl/gtlsserverconnection-openssl.c
@@ -3,6 +3,8 @@
* gtlsserverconnection-openssl.c
*
* Copyright (C) 2015 NICE s.r.l.
+ * Copyright 2019 Igalia S.L.
+ * Copyright 2019 Metrological Group B.V.
*
* This file is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -31,16 +33,11 @@
#include "openssl-include.h"
#include <glib/gi18n-lib.h>
-#define DEFAULT_CIPHER_LIST "HIGH:!DSS:!aNULL@STRENGTH"
-
struct _GTlsServerConnectionOpenssl
{
GTlsConnectionOpenssl parent_instance;
GTlsAuthenticationMode authentication_mode;
- SSL_SESSION *session;
- SSL *ssl;
- SSL_CTX *ssl_ctx;
};
enum
@@ -61,17 +58,6 @@ G_DEFINE_TYPE_WITH_CODE (GTlsServerConnectionOpenssl, g_tls_server_connection_op
G_IMPLEMENT_INTERFACE (G_TYPE_TLS_SERVER_CONNECTION,
g_tls_server_connection_openssl_server_connection_interface_init))
-static void
-g_tls_server_connection_openssl_finalize (GObject *object)
-{
- GTlsServerConnectionOpenssl *openssl = G_TLS_SERVER_CONNECTION_OPENSSL (object);
-
- SSL_free (openssl->ssl);
- SSL_CTX_free (openssl->ssl_ctx);
- SSL_SESSION_free (openssl->session);
-
- G_OBJECT_CLASS (g_tls_server_connection_openssl_parent_class)->finalize (object);
-}
static void
g_tls_server_connection_openssl_get_property (GObject *object,
@@ -111,197 +97,14 @@ g_tls_server_connection_openssl_set_property (GObject *object,
}
}
-static int
-verify_callback (int preverify_ok,
- X509_STORE_CTX *ctx)
-{
- return 1;
-}
-
-static void
-g_tls_server_connection_openssl_prepare_handshake (GTlsConnectionBase *tls,
- gchar **advertised_protocols)
-{
- GTlsServerConnectionOpenssl *openssl = G_TLS_SERVER_CONNECTION_OPENSSL (tls);
- GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS
(g_tls_server_connection_openssl_parent_class);
- int req_mode = 0;
-
- switch (openssl->authentication_mode)
- {
- case G_TLS_AUTHENTICATION_REQUIRED:
- req_mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
- break;
- case G_TLS_AUTHENTICATION_REQUESTED:
- req_mode = SSL_VERIFY_PEER;
- break;
- case G_TLS_AUTHENTICATION_NONE:
- default:
- req_mode = SSL_VERIFY_NONE;
- break;
- }
-
- SSL_set_verify (openssl->ssl, req_mode, verify_callback);
- /* FIXME: is this ok? */
- SSL_set_verify_depth (openssl->ssl, 0);
-
- if (base_class->prepare_handshake)
- base_class->prepare_handshake (tls, advertised_protocols);
-}
-
-static SSL *
-g_tls_server_connection_openssl_get_ssl (GTlsConnectionOpenssl *connection)
-{
- return G_TLS_SERVER_CONNECTION_OPENSSL (connection)->ssl;
-}
-
-#if OPENSSL_VERSION_NUMBER < 0x10002000L
-static gboolean
-ssl_ctx_set_certificate (SSL_CTX *ssl_ctx,
- GTlsCertificate *cert,
- GError **error)
-{
- EVP_PKEY *key;
- X509 *x;
- GTlsCertificate *issuer;
-
- key = g_tls_certificate_openssl_get_key (G_TLS_CERTIFICATE_OPENSSL (cert));
-
- if (!key)
- {
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Certificate has no private key"));
- return FALSE;
- }
-
- if (SSL_CTX_use_PrivateKey (ssl_ctx, key) <= 0)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("There is a problem with the certificate private key: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- x = g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (cert));
- if (SSL_CTX_use_certificate (ssl_ctx, x) <= 0)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("There is a problem with the certificate: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- /* Add all the issuers to create the full certificate chain */
- for (issuer = g_tls_certificate_get_issuer (G_TLS_CERTIFICATE (cert));
- issuer;
- issuer = g_tls_certificate_get_issuer (issuer))
- {
- X509 *issuer_x;
-
- /* Be careful here and duplicate the certificate since the context
- * will take the ownership
- */
- issuer_x = X509_dup (g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (issuer)));
- if (!SSL_CTX_add_extra_chain_cert (ssl_ctx, issuer_x))
- g_warning ("There was a problem adding the extra chain certificate: %s",
- ERR_error_string (ERR_get_error (), NULL));
- }
-}
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined (LIBRESSL_VERSION_NUMBER)
-static gboolean
-ssl_set_certificate (SSL *ssl,
- GTlsCertificate *cert,
- GError **error)
-{
- EVP_PKEY *key;
- X509 *x;
- GTlsCertificate *issuer;
-
- key = g_tls_certificate_openssl_get_key (G_TLS_CERTIFICATE_OPENSSL (cert));
-
- if (!key)
- {
- g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("Certificate has no private key"));
- return FALSE;
- }
-
- /* Note, order is important. If a certificate has been set previously,
- * OpenSSL requires that the new certificate is set _before_ the new
- * private key is set. */
- x = g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (cert));
- if (SSL_use_certificate (ssl, x) <= 0)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("There is a problem with the certificate: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- if (SSL_use_PrivateKey (ssl, key) <= 0)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
- _("There is a problem with the certificate private key: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- if (SSL_clear_chain_certs (ssl) == 0)
- g_warning ("There was a problem clearing the chain certificates: %s",
- ERR_error_string (ERR_get_error (), NULL));
-
- /* Add all the issuers to create the full certificate chain */
- for (issuer = g_tls_certificate_get_issuer (G_TLS_CERTIFICATE (cert));
- issuer;
- issuer = g_tls_certificate_get_issuer (issuer))
- {
- X509 *issuer_x;
-
- issuer_x = g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (issuer));
-
- /* Be careful here and duplicate the certificate since the ssl object
- * will take the ownership
- */
- if (SSL_add1_chain_cert (ssl, issuer_x) == 0)
- g_warning ("There was a problem adding the chain certificate: %s",
- ERR_error_string (ERR_get_error (), NULL));
- }
-
- return TRUE;
-}
-
-static void
-on_certificate_changed (GObject *object,
- GParamSpec *spec,
- gpointer user_data)
-{
- SSL *ssl;
- GTlsCertificate *cert;
-
- ssl = g_tls_server_connection_openssl_get_ssl (G_TLS_CONNECTION_OPENSSL (object));
- cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (object));
-
- if (ssl && cert)
- ssl_set_certificate (ssl, cert, NULL);
-}
-#endif
-
static void
g_tls_server_connection_openssl_class_init (GTlsServerConnectionOpensslClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GTlsConnectionBaseClass *base_class = G_TLS_CONNECTION_BASE_CLASS (klass);
- GTlsConnectionOpensslClass *connection_class = G_TLS_CONNECTION_OPENSSL_CLASS (klass);
- gobject_class->finalize = g_tls_server_connection_openssl_finalize;
gobject_class->get_property = g_tls_server_connection_openssl_get_property;
gobject_class->set_property = g_tls_server_connection_openssl_set_property;
- base_class->prepare_handshake = g_tls_server_connection_openssl_prepare_handshake;
-
- connection_class->get_ssl = g_tls_server_connection_openssl_get_ssl;
-
g_object_class_override_property (gobject_class, PROP_AUTHENTICATION_MODE, "authentication-mode");
}
@@ -315,172 +118,12 @@ g_tls_server_connection_openssl_server_connection_interface_init (GTlsServerConn
{
}
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
-static void
-ssl_info_callback (const SSL *ssl,
- int type,
- int val)
-{
- if ((type & SSL_CB_HANDSHAKE_DONE) != 0)
- {
- /* Disable renegotiation (CVE-2009-3555) */
- ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
- }
-}
-#endif
-
-static gboolean
-set_cipher_list (GTlsServerConnectionOpenssl *server,
- GError **error)
-{
- const gchar *cipher_list;
-
- cipher_list = g_getenv ("G_TLS_OPENSSL_CIPHER_LIST");
- if (!cipher_list)
- cipher_list = DEFAULT_CIPHER_LIST;
-
- if (!SSL_CTX_set_cipher_list (server->ssl_ctx, cipher_list))
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS context: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- return TRUE;
-}
-
-#ifdef SSL_CTX_set1_sigalgs_list
-static void
-set_signature_algorithm_list (GTlsServerConnectionOpenssl *server)
-{
- const gchar *signature_algorithm_list;
-
- signature_algorithm_list = g_getenv ("G_TLS_OPENSSL_SIGNATURE_ALGORITHM_LIST");
- if (!signature_algorithm_list)
- return;
-
- SSL_CTX_set1_sigalgs_list (server->ssl_ctx, signature_algorithm_list);
-}
-#endif
-
-#ifdef SSL_CTX_set1_curves_list
-static void
-set_curve_list (GTlsServerConnectionOpenssl *server)
-{
- const gchar *curve_list;
-
- curve_list = g_getenv ("G_TLS_OPENSSL_CURVE_LIST");
- if (!curve_list)
- return;
-
- SSL_CTX_set1_curves_list (server->ssl_ctx, curve_list);
-}
-#endif
-
static gboolean
g_tls_server_connection_openssl_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
- GTlsServerConnectionOpenssl *server = G_TLS_SERVER_CONNECTION_OPENSSL (initable);
- GTlsCertificate *cert;
- long options;
-
- server->session = SSL_SESSION_new ();
-
- server->ssl_ctx = SSL_CTX_new (SSLv23_server_method ());
- if (!server->ssl_ctx)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS context: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
- if (!set_cipher_list (server, error))
- return FALSE;
-
- /* Only TLS 1.2 or higher */
- options = SSL_OP_NO_TICKET |
- SSL_OP_NO_COMPRESSION |
- SSL_OP_CIPHER_SERVER_PREFERENCE |
- SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
- SSL_OP_SINGLE_ECDH_USE |
-#ifdef SSL_OP_NO_TLSv1_1
- SSL_OP_NO_TLSv1_1 |
-#endif
- SSL_OP_NO_SSLv2 |
- SSL_OP_NO_SSLv3 |
- SSL_OP_NO_TLSv1;
-
-#ifdef SSL_OP_NO_RENEGOTIATION
- options |= SSL_OP_NO_RENEGOTIATION;
-#endif
-
- SSL_CTX_set_options (server->ssl_ctx, options);
-
- SSL_CTX_add_session (server->ssl_ctx, server->session);
-
-#ifdef SSL_CTX_set1_sigalgs_list
- set_signature_algorithm_list (server);
-#endif
-
-#ifdef SSL_CTX_set1_curves_list
- set_curve_list (server);
-#endif
-
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
-# ifdef SSL_CTX_set_ecdh_auto
- SSL_CTX_set_ecdh_auto (server->ssl_ctx, 1);
-# else
- {
- EC_KEY *ecdh;
-
- ecdh = EC_KEY_new_by_curve_name (NID_X9_62_prime256v1);
- if (ecdh)
- {
- SSL_CTX_set_tmp_ecdh (server->ssl_ctx, ecdh);
- EC_KEY_free (ecdh);
- }
- }
-# endif
-
- SSL_CTX_set_info_callback (server->ssl_ctx, ssl_info_callback);
-#endif
-
- cert = g_tls_connection_get_certificate (G_TLS_CONNECTION (initable));
-
-#if OPENSSL_VERSION_NUMBER < 0x10002000L
- if (cert && !ssl_ctx_set_certificate (server->ssl_ctx, cert, error))
- return FALSE;
-#endif
-
- server->ssl = SSL_new (server->ssl_ctx);
- if (!server->ssl)
- {
- g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
- _("Could not create TLS connection: %s"),
- ERR_error_string (ERR_get_error (), NULL));
- return FALSE;
- }
-
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined (LIBRESSL_VERSION_NUMBER)
- if (cert && !ssl_set_certificate (server->ssl, cert, error))
- return FALSE;
-#endif
-
- SSL_set_accept_state (server->ssl);
-
- if (!g_tls_server_connection_openssl_parent_initable_iface->
- init (initable, cancellable, error))
- return FALSE;
-
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined (LIBRESSL_VERSION_NUMBER)
- g_signal_connect (server, "notify::certificate", G_CALLBACK (on_certificate_changed), NULL);
-#endif
-
- return TRUE;
+ return g_tls_server_connection_openssl_parent_initable_iface->init (initable, cancellable, error);
}
static void
diff --git a/tls/openssl/meson.build b/tls/openssl/meson.build
index 0ac25c8..9b799ec 100644
--- a/tls/openssl/meson.build
+++ b/tls/openssl/meson.build
@@ -7,6 +7,7 @@ sources = files(
'gtlsclientconnection-openssl.c',
'gtlsdatabase-openssl.c',
'gtlsfiledatabase-openssl.c',
+ 'gtlsoperationsthread-openssl.c',
'gtlsbio.c',
'openssl-util.c',
)
diff --git a/tls/tests/connection.c b/tls/tests/connection.c
index ed62c49..2d750b9 100644
--- a/tls/tests/connection.c
+++ b/tls/tests/connection.c
@@ -82,7 +82,6 @@ typedef struct {
GSocketConnectable *identity;
GSocketAddress *address;
GTlsAuthenticationMode auth_mode;
- gboolean rehandshake;
GTlsCertificateFlags accept_flags;
GError *read_error;
GError *server_error;
@@ -221,25 +220,6 @@ 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,
@@ -272,15 +252,6 @@ on_output_write_finish (GObject *object,
g_assert_no_error (test->server_error);
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->connection_received_strategy == WRITE_THEN_CLOSE)
close_server_connection (test);
}
@@ -342,7 +313,7 @@ on_incoming_connection (GSocketService *service,
test->connection_received_strategy == WRITE_THEN_WAIT)
{
g_output_stream_write_async (stream, TEST_DATA,
- test->rehandshake ? TEST_DATA_LENGTH / 2 : TEST_DATA_LENGTH,
+ TEST_DATA_LENGTH,
G_PRIORITY_DEFAULT, NULL,
on_output_write_finish, test);
}
@@ -440,13 +411,6 @@ run_echo_server (GThreadedSocketService *service,
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);
@@ -1169,20 +1133,6 @@ test_client_auth_pkcs11_connection (TestConnection *test,
}
#endif
-static void
-test_client_auth_rehandshake (TestConnection *test,
- gconstpointer data)
-{
-#ifdef BACKEND_IS_OPENSSL
- /* FIXME: this doesn't make sense, we should support safe renegotation */
- g_test_skip ("the server avoids rehandshake to avoid the security problem CVE-2009-3555");
- return;
-#endif
-
- test->rehandshake = TRUE;
- test_client_auth_connection (test, data);
-}
-
static void
test_client_auth_failure (TestConnection *test,
gconstpointer data)
@@ -1884,19 +1834,6 @@ test_simultaneous_async (TestConnection *test,
g_assert_cmpstr (test->buf, ==, TEST_DATA);
}
-static void
-test_simultaneous_async_rehandshake (TestConnection *test,
- gconstpointer data)
-{
-#ifdef BACKEND_IS_OPENSSL
- g_test_skip ("this needs more research on openssl");
- return;
-#endif
-
- test->rehandshake = TRUE;
- test_simultaneous_async (test, data);
-}
-
static gpointer
simul_read_thread (gpointer user_data)
{
@@ -1983,19 +1920,6 @@ test_simultaneous_sync (TestConnection *test,
g_assert_no_error (error);
}
-static void
-test_simultaneous_sync_rehandshake (TestConnection *test,
- gconstpointer data)
-{
-#ifdef BACKEND_IS_OPENSSL
- g_test_skip ("this needs more research on openssl");
- return;
-#endif
-
- test->rehandshake = TRUE;
- test_simultaneous_sync (test, data);
-}
-
static void
test_close_immediately (TestConnection *test,
gconstpointer data)
@@ -2609,8 +2533,6 @@ main (int argc,
setup_connection, test_invalid_chain_with_alternative_ca_cert, teardown_connection);
g_test_add ("/tls/" BACKEND "/connection/client-auth", TestConnection, NULL,
setup_connection, test_client_auth_connection, teardown_connection);
- g_test_add ("/tls/" BACKEND "/connection/client-auth-rehandshake", TestConnection, NULL,
- setup_connection, test_client_auth_rehandshake, teardown_connection);
g_test_add ("/tls/" BACKEND "/connection/client-auth-failure", TestConnection, NULL,
setup_connection, test_client_auth_failure, teardown_connection);
g_test_add ("/tls/" BACKEND "/connection/client-auth-fail-missing-client-private-key", TestConnection,
NULL,
@@ -2640,10 +2562,6 @@ main (int argc,
setup_connection, test_simultaneous_async, teardown_connection);
g_test_add ("/tls/" BACKEND "/connection/simultaneous-sync", TestConnection, NULL,
setup_connection, test_simultaneous_sync, teardown_connection);
- g_test_add ("/tls/" BACKEND "/connection/simultaneous-async-rehandshake", TestConnection, NULL,
- setup_connection, test_simultaneous_async_rehandshake, teardown_connection);
- g_test_add ("/tls/" BACKEND "/connection/simultaneous-sync-rehandshake", TestConnection, NULL,
- setup_connection, test_simultaneous_sync_rehandshake, teardown_connection);
g_test_add ("/tls/" BACKEND "/connection/close-immediately", TestConnection, NULL,
setup_connection, test_close_immediately, teardown_connection);
g_test_add ("/tls/" BACKEND "/connection/unclean-close-by-server", TestConnection, NULL,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]