X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=tls%2Fgnutls%2Fgtlsconnection-gnutls.c;h=1163fb5ebfb2538d8e8129a1780ae9d2b6d7c5d8;hb=refs%2Ftags%2Fsubmit%2Ftizen%2F20191016.084905;hp=ef53bb2a69fb0ebd4348607f5d565d8fcc396b3e;hpb=c03f4e22bef871bdd6ff1c48d40263e942ee0114;p=platform%2Fupstream%2Fglib-networking.git diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c old mode 100644 new mode 100755 index ef53bb2..1163fb5 --- a/tls/gnutls/gtlsconnection-gnutls.c +++ b/tls/gnutls/gtlsconnection-gnutls.c @@ -1,11 +1,14 @@ -/* GIO - GLib Input, Output and Streaming Library +/* -*- 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. * * 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 of the License, or (at your option) any later version. + * 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 @@ -15,70 +18,120 @@ * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see * . + * + * In addition, when the library is used with OpenSSL, a special + * exception applies. Refer to the LICENSE_EXCEPTION file for details. */ #include "config.h" #include "glib.h" #include +#include +#include #include #include #include "gtlsconnection-gnutls.h" #include "gtlsbackend-gnutls.h" #include "gtlscertificate-gnutls.h" +#include "gtlsclientconnection-gnutls.h" #include "gtlsinputstream-gnutls.h" #include "gtlsoutputstream-gnutls.h" #include "gtlsserverconnection-gnutls.h" -#ifdef HAVE_PKCS11 -#include -#include "pkcs11/gpkcs11pin.h" +#ifdef G_OS_WIN32 +#include +#include + +/* It isn’t clear whether MinGW always defines EMSGSIZE. */ +#ifndef EMSGSIZE +#define EMSGSIZE WSAEMSGSIZE +#endif #endif #include +#include + +/* + * GTlsConnectionGnutls is the base abstract implementation of TLS and DTLS + * support, for both the client and server side of a connection. The choice + * between TLS and DTLS is made by setting the base-io-stream or + * base-socket properties — exactly one of them must be set at + * construction time. + * + * Client and server specific code is in the GTlsClientConnectionGnutls and + * GTlsServerConnectionGnutls concrete subclasses, although the line about where + * code is put is a little blurry, and there are various places in + * GTlsConnectionGnutls which check G_IS_TLS_CLIENT_CONNECTION(self) to switch + * to a client-only code path. + * + * This abstract class implements a lot of interfaces: + * • Derived from GTlsConnection (itself from GIOStream), for TLS and streaming + * communications. + * • Implements GDtlsConnection and GDatagramBased, for DTLS and datagram + * communications. + * • Implements GInitable for failable GnuTLS initialisation. + * + * The GTlsClientConnectionGnutls and GTlsServerConnectionGnutls subclasses are + * both derived from GTlsConnectionGnutls (and hence GIOStream), and both + * implement the relevant TLS and DTLS interfaces: + * • GTlsClientConnection + * • GDtlsClientConnection + * • GTlsServerConnection + * • GDtlsServerConnection + */ + +#include "TIZEN.h" + +#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG) +#include +#ifndef PR_TASK_PERF_USER_TRACE +#define PR_TASK_PERF_USER_TRACE 666 +#endif +#define HWCLOCK_LOG(s) {const char *str=s; prctl(PR_TASK_PERF_USER_TRACE, str, strlen(str));} +#endif static ssize_t g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t transport_data, - const void *buf, - size_t buflen); + 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); + void *buf, + size_t buflen); + +static int g_tls_connection_gnutls_pull_timeout_func (gnutls_transport_ptr_t transport_data, + unsigned int ms); + static void g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface); static gboolean g_tls_connection_gnutls_initable_init (GInitable *initable, - GCancellable *cancellable, - GError **error); - -#ifdef HAVE_PKCS11 -static P11KitPin* on_pin_prompt_callback (const char *pinfile, - P11KitUri *pin_uri, - const char *pin_description, - P11KitPinFlags pin_flags, - void *callback_data); -#endif + GCancellable *cancellable, + GError **error); +static void g_tls_connection_gnutls_dtls_connection_iface_init (GDtlsConnectionInterface *iface); +static void g_tls_connection_gnutls_datagram_based_iface_init (GDatagramBasedInterface *iface); static void g_tls_connection_gnutls_init_priorities (void); +static int verify_certificate_cb (gnutls_session_t session); + static gboolean do_implicit_handshake (GTlsConnectionGnutls *gnutls, - gboolean blocking, - GCancellable *cancellable, - GError **error); + gint64 timeout, + GCancellable *cancellable, + GError **error); static gboolean finish_handshake (GTlsConnectionGnutls *gnutls, - GTask *thread_task, - GError **error); - -G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls, G_TYPE_TLS_CONNECTION, - G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, - g_tls_connection_gnutls_initable_iface_init); - g_tls_connection_gnutls_init_priorities (); - ); - + GTask *task, + GError **error); enum { PROP_0, + /* For this class: */ PROP_BASE_IO_STREAM, + PROP_BASE_SOCKET, + /* For GTlsConnection and GDtlsConnection: */ PROP_REQUIRE_CLOSE_NOTIFY, PROP_REHANDSHAKE_MODE, PROP_USE_SYSTEM_CERTDB, @@ -86,20 +139,53 @@ enum PROP_CERTIFICATE, PROP_INTERACTION, PROP_PEER_CERTIFICATE, - PROP_PEER_CERTIFICATE_ERRORS + PROP_PEER_CERTIFICATE_ERRORS, +#if GLIB_CHECK_VERSION(2, 60, 0) + PROP_ADVERTISED_PROTOCOLS, + PROP_NEGOTIATED_PROTOCOL, +#endif }; -struct _GTlsConnectionGnutlsPrivate +typedef struct { + /* When operating in stream mode, as a GTlsConnection. These are + * mutually-exclusive with base_socket. There are two different + * GIOStreams here: (a) base_io_stream and (b) the GTlsConnectionGnutls + * itself. base_io_stream is the GIOStream used to create the GTlsConnection, + * and corresponds to the GTlsConnection::base-io-stream property. + * base_istream and base_ostream are the GInputStream and GOutputStream, + * respectively, of base_io_stream. These are for the underlying sockets that + * don't know about TLS. + * + * Then the GTlsConnectionGnutls also has tls_istream and tls_ostream which + * wrap the aforementioned base streams with a TLS session. + * + * When operating in datagram mode, none of these are used. + */ GIOStream *base_io_stream; GPollableInputStream *base_istream; GPollableOutputStream *base_ostream; + GInputStream *tls_istream; + GOutputStream *tls_ostream; - gnutls_certificate_credentials creds; - gnutls_session session; + /* When operating in datagram mode, as a GDtlsConnection, the + * GTlsConnectionGnutls is itself the DTLS GDatagramBased. It uses base_socket + * for the underlying I/O. It is mutually-exclusive with base_io_stream and + * the other streams. + */ + GDatagramBased *base_socket; + + gnutls_certificate_credentials_t creds; + gnutls_session_t session; GTlsCertificate *certificate, *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; gboolean is_system_certdb; @@ -107,19 +193,19 @@ struct _GTlsConnectionGnutlsPrivate gboolean database_is_unset; /* need_handshake means the next claim_op() will get diverted into - * an implicit handshake (unless it's an OP_HANDSHAKE itself). - * need_finish_handshake means the next claim_op() will get - * diverted into finish_handshake(). + * an implicit handshake (unless it's an OP_HANDSHAKE or OP_CLOSE*). + * need_finish_handshake means the next claim_op() will get diverted + * into finish_handshake() (unless it's an OP_CLOSE*). * - * handshaking is TRUE as soon as a handshake thread is queued. - * Normally it becomes FALSE after finish_handshake() completes. For - * an implicit handshake, but in the case of an async implicit - * handshake, it becomes FALSE at the end of handshake_thread(), - * (and then the next read/write op will call finish_handshake()). - * This is because we don't want to call finish_handshake() (and - * possibly emit signals) if the caller is not actually in a TLS op - * at the time. (Eg, if they're waiting to try a nonblocking call - * again, we don't want to emit the signal until they do.) + * handshaking is TRUE as soon as a handshake thread is queued. For + * a sync handshake it becomes FALSE after finish_handshake() + * completes in the calling thread, but for an async implicit + * handshake, it becomes FALSE (and need_finish_handshake becomes + * TRUE) at the end of the handshaking thread (and then the next + * non-close op will call finish_handshake()). We can't just wait + * for handshake_thread_completed() to run, because it's possible + * that its main loop is being blocked by a synchronous op which is + * waiting for handshaking to become FALSE... * * started_handshake indicates that the current handshake attempt * got at least as far as calling gnutls_handshake() (and so any @@ -127,148 +213,239 @@ struct _GTlsConnectionGnutlsPrivate * future operations). ever_handshaked indicates that TLS has * been successfully negotiated at some point. */ - gboolean need_handshake, need_finish_handshake; + gboolean need_handshake, need_finish_handshake, sync_handshake_completed; gboolean started_handshake, handshaking, ever_handshaked; + GMainContext *handshake_context; GTask *implicit_handshake; GError *handshake_error; + GByteArray *app_data_buf; - gboolean closing, closed; - - GInputStream *tls_istream; - GOutputStream *tls_ostream; + /* read_closed means the read direction has closed; write_closed similarly. + * If (and only if) both are set, the entire GTlsConnection is closed. */ + gboolean read_closing, read_closed; + gboolean write_closing, write_closed; GTlsInteraction *interaction; gchar *interaction_id; +#if GLIB_CHECK_VERSION(2, 60, 0) + gchar **advertised_protocols; + gchar *negotiated_protocol; +#endif + GMutex op_mutex; GCancellable *waiting_for_op; gboolean reading; - gboolean read_blocking; + gint64 read_timeout; GError *read_error; GCancellable *read_cancellable; gboolean writing; - gboolean write_blocking; + gint64 write_timeout; GError *write_error; GCancellable *write_cancellable; +} GTlsConnectionGnutlsPrivate; -#ifndef GNUTLS_E_PREMATURE_TERMINATION - gboolean eof; -#endif -}; +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionGnutls, g_tls_connection_gnutls, G_TYPE_TLS_CONNECTION, + G_ADD_PRIVATE (GTlsConnectionGnutls); + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + g_tls_connection_gnutls_initable_iface_init); + G_IMPLEMENT_INTERFACE (G_TYPE_DATAGRAM_BASED, + g_tls_connection_gnutls_datagram_based_iface_init); + G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_CONNECTION, + g_tls_connection_gnutls_dtls_connection_iface_init); + g_tls_connection_gnutls_init_priorities (); + ); 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); gint unique_id; - gnutls->priv = G_TYPE_INSTANCE_GET_PRIVATE (gnutls, G_TYPE_TLS_CONNECTION_GNUTLS, GTlsConnectionGnutlsPrivate); + gnutls_certificate_allocate_credentials (&priv->creds); - gnutls_certificate_allocate_credentials (&gnutls->priv->creds); - gnutls_certificate_set_verify_flags (gnutls->priv->creds, - GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + g_mutex_init (&priv->verify_certificate_mutex); + g_cond_init (&priv->verify_certificate_condition); - gnutls->priv->need_handshake = TRUE; + priv->need_handshake = TRUE; - gnutls->priv->database_is_unset = TRUE; - gnutls->priv->is_system_certdb = TRUE; + priv->database_is_unset = TRUE; + priv->is_system_certdb = TRUE; unique_id = g_atomic_int_add (&unique_interaction_id, 1); - gnutls->priv->interaction_id = g_strdup_printf ("gtls:%d", unique_id); + priv->interaction_id = g_strdup_printf ("gtls:%d", unique_id); -#ifdef HAVE_PKCS11 - p11_kit_pin_register_callback (gnutls->priv->interaction_id, - on_pin_prompt_callback, gnutls, NULL); -#endif - - gnutls->priv->waiting_for_op = g_cancellable_new (); - g_cancellable_cancel (gnutls->priv->waiting_for_op); + priv->waiting_for_op = g_cancellable_new (); + g_cancellable_cancel (priv->waiting_for_op); + g_mutex_init (&priv->op_mutex); } -/* First field is "ssl3 only", second is "allow unsafe rehandshaking" */ +/* First field is "fallback", second is "allow unsafe rehandshaking" */ static gnutls_priority_t priorities[2][2]; +#if ENABLE(TIZEN_TV_UPDATE_DEFAULT_PRIORITY) +#define DEFAULT_BASE_PRIORITY "NORMAL:%COMPAT:!VERS-SSL3.0:%LATEST_RECORD_VERSION" +#else +/* TODO: Get rid of this in favor of gnutls_set_default_priority_append() + * when upgrading to GnuTLS 3.6.3. + */ +#define DEFAULT_BASE_PRIORITY "NORMAL:%COMPAT" +#endif + static void g_tls_connection_gnutls_init_priorities (void) { const gchar *base_priority; - gchar *ssl3_priority, *unsafe_rehandshake_priority, *ssl3_unsafe_rehandshake_priority; + gchar *fallback_priority, *unsafe_rehandshake_priority, *fallback_unsafe_rehandshake_priority; + const guint *protos; + int ret, i, nprotos, fallback_proto; base_priority = g_getenv ("G_TLS_GNUTLS_PRIORITY"); if (!base_priority) - base_priority = "NORMAL:%COMPAT"; + base_priority = DEFAULT_BASE_PRIORITY; + ret = gnutls_priority_init (&priorities[FALSE][FALSE], base_priority, NULL); + if (ret == GNUTLS_E_INVALID_REQUEST) + { + g_warning ("G_TLS_GNUTLS_PRIORITY is invalid; ignoring!"); + base_priority = DEFAULT_BASE_PRIORITY; + ret = gnutls_priority_init (&priorities[FALSE][FALSE], base_priority, NULL); + g_warn_if_fail (ret == 0); + } - ssl3_priority = g_strdup_printf ("%s:!VERS-TLS1.2:!VERS-TLS1.1:!VERS-TLS1.0", base_priority); unsafe_rehandshake_priority = g_strdup_printf ("%s:%%UNSAFE_RENEGOTIATION", base_priority); - ssl3_unsafe_rehandshake_priority = g_strdup_printf ("%s:!VERS-TLS1.2:!VERS-TLS1.1:!VERS-TLS1.0:%%UNSAFE_RENEGOTIATION", base_priority); - - gnutls_priority_init (&priorities[FALSE][FALSE], base_priority, NULL); - gnutls_priority_init (&priorities[TRUE][FALSE], ssl3_priority, NULL); - gnutls_priority_init (&priorities[FALSE][TRUE], unsafe_rehandshake_priority, NULL); - gnutls_priority_init (&priorities[TRUE][TRUE], ssl3_unsafe_rehandshake_priority, NULL); - - g_free (ssl3_priority); + ret = gnutls_priority_init (&priorities[FALSE][TRUE], unsafe_rehandshake_priority, NULL); + g_warn_if_fail (ret == 0); g_free (unsafe_rehandshake_priority); - g_free (ssl3_unsafe_rehandshake_priority); + + /* Figure out the lowest SSl/TLS version supported by base_priority */ + nprotos = gnutls_priority_protocol_list (priorities[FALSE][FALSE], &protos); + fallback_proto = G_MAXUINT; + for (i = 0; i < nprotos; i++) + { + if (protos[i] < fallback_proto) + fallback_proto = protos[i]; + } + if (fallback_proto == G_MAXUINT) + { + g_warning ("All GNUTLS protocol versions disabled?"); + fallback_priority = g_strdup (base_priority); + } + else + { + /* %COMPAT is intentionally duplicated here, to ensure it gets added for + * the fallback even if the default priority has been changed. */ + fallback_priority = g_strdup_printf ("%s:%%COMPAT:!VERS-TLS-ALL:+VERS-%s:%%FALLBACK_SCSV", + DEFAULT_BASE_PRIORITY, + gnutls_protocol_get_name (fallback_proto)); + } + fallback_unsafe_rehandshake_priority = g_strdup_printf ("%s:%%UNSAFE_RENEGOTIATION", + fallback_priority); + + ret = gnutls_priority_init (&priorities[TRUE][FALSE], fallback_priority, NULL); + g_warn_if_fail (ret == 0); + ret = gnutls_priority_init (&priorities[TRUE][TRUE], fallback_unsafe_rehandshake_priority, NULL); + g_warn_if_fail (ret == 0); + g_free (fallback_priority); + g_free (fallback_unsafe_rehandshake_priority); } static void g_tls_connection_gnutls_set_handshake_priority (GTlsConnectionGnutls *gnutls) { - gboolean use_ssl3, unsafe_rehandshake; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + gboolean fallback, unsafe_rehandshake; if (G_IS_TLS_CLIENT_CONNECTION (gnutls)) - use_ssl3 = g_tls_client_connection_get_use_ssl3 (G_TLS_CLIENT_CONNECTION (gnutls)); + { +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + fallback = g_tls_client_connection_get_use_ssl3 (G_TLS_CLIENT_CONNECTION (gnutls)); +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else - use_ssl3 = FALSE; - unsafe_rehandshake = (gnutls->priv->rehandshake_mode == G_TLS_REHANDSHAKE_UNSAFELY); - gnutls_priority_set (gnutls->priv->session, - priorities[use_ssl3][unsafe_rehandshake]); + fallback = FALSE; + unsafe_rehandshake = (priv->rehandshake_mode == G_TLS_REHANDSHAKE_UNSAFELY); + gnutls_priority_set (priv->session, + priorities[fallback][unsafe_rehandshake]); +} + +static gboolean +g_tls_connection_gnutls_is_dtls (GTlsConnectionGnutls *gnutls) +{ + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + return (priv->base_socket != NULL); } static gboolean g_tls_connection_gnutls_initable_init (GInitable *initable, - GCancellable *cancellable, - GError **error) + GCancellable *cancellable, + GError **error) { GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (initable); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + gboolean client = G_IS_TLS_CLIENT_CONNECTION (gnutls); + guint flags = client ? GNUTLS_CLIENT : GNUTLS_SERVER; int status; - g_return_val_if_fail (gnutls->priv->base_istream != NULL && - gnutls->priv->base_ostream != NULL, FALSE); + g_return_val_if_fail ((priv->base_istream == NULL) == + (priv->base_ostream == NULL), FALSE); + g_return_val_if_fail ((priv->base_socket == NULL) != + (priv->base_istream == NULL), FALSE); - /* Make sure gnutls->priv->session has been initialized (it may have - * already been initialized by a construct-time property setter). - */ - g_tls_connection_gnutls_get_session (gnutls); + /* Check whether to use DTLS or TLS. */ + if (g_tls_connection_gnutls_is_dtls (gnutls)) + flags |= GNUTLS_DATAGRAM; - status = gnutls_credentials_set (gnutls->priv->session, - GNUTLS_CRD_CERTIFICATE, - gnutls->priv->creds); + 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)); + _("Could not create TLS connection: %s"), + gnutls_strerror (status)); return FALSE; } - /* Some servers (especially on embedded devices) use tiny keys that - * gnutls will reject by default. We want it to accept them. - */ - gnutls_dh_set_prime_bits (gnutls->priv->session, 256); + 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); + + /* GDatagramBased supports vectored I/O; GPollableOutputStream does not. */ + if (priv->base_socket != NULL) + { + gnutls_transport_set_vec_push_function (priv->session, + g_tls_connection_gnutls_vec_push_func); + } - gnutls_transport_set_push_function (gnutls->priv->session, - g_tls_connection_gnutls_push_func); - gnutls_transport_set_pull_function (gnutls->priv->session, - g_tls_connection_gnutls_pull_func); - gnutls_transport_set_ptr (gnutls->priv->session, gnutls); + /* Set reasonable MTU */ + if (flags & GNUTLS_DATAGRAM) + gnutls_dtls_set_mtu (priv->session, 1400); - gnutls->priv->tls_istream = g_tls_input_stream_gnutls_new (gnutls); - gnutls->priv->tls_ostream = g_tls_output_stream_gnutls_new (gnutls); + /* Create output streams if operating in streaming mode. */ + if (!(flags & GNUTLS_DATAGRAM)) + { + priv->tls_istream = g_tls_input_stream_gnutls_new (gnutls); + priv->tls_ostream = g_tls_output_stream_gnutls_new (gnutls); + } return TRUE; } @@ -277,89 +454,124 @@ 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); - g_clear_object (&gnutls->priv->base_io_stream); + g_clear_object (&priv->base_io_stream); + g_clear_object (&priv->base_socket); - g_clear_object (&gnutls->priv->tls_istream); - g_clear_object (&gnutls->priv->tls_ostream); + g_clear_object (&priv->tls_istream); + g_clear_object (&priv->tls_ostream); - if (gnutls->priv->session) - gnutls_deinit (gnutls->priv->session); - if (gnutls->priv->creds) - gnutls_certificate_free_credentials (gnutls->priv->creds); + if (priv->session) + gnutls_deinit (priv->session); + if (priv->creds) + gnutls_certificate_free_credentials (priv->creds); - g_clear_object (&gnutls->priv->database); - g_clear_object (&gnutls->priv->certificate); - g_clear_object (&gnutls->priv->peer_certificate); + g_clear_object (&priv->database); + g_clear_object (&priv->certificate); + g_clear_object (&priv->peer_certificate); -#ifdef HAVE_PKCS11 - p11_kit_pin_unregister_callback (gnutls->priv->interaction_id, - on_pin_prompt_callback, gnutls); + g_mutex_clear (&priv->verify_certificate_mutex); + g_cond_clear (&priv->verify_certificate_condition); + + + g_clear_pointer (&priv->app_data_buf, g_byte_array_unref); + + g_free (priv->interaction_id); + g_clear_object (&priv->interaction); + +#if GLIB_CHECK_VERSION(2, 60, 0) + g_clear_pointer (&priv->advertised_protocols, g_strfreev); + g_clear_pointer (&priv->negotiated_protocol, g_free); #endif - g_free (gnutls->priv->interaction_id); - g_clear_object (&gnutls->priv->interaction); - g_clear_error (&gnutls->priv->handshake_error); - g_clear_error (&gnutls->priv->read_error); - g_clear_error (&gnutls->priv->write_error); + g_clear_error (&priv->handshake_error); + g_clear_error (&priv->read_error); + g_clear_error (&priv->write_error); - g_clear_object (&gnutls->priv->waiting_for_op); + g_clear_pointer (&priv->handshake_context, g_main_context_unref); + + /* This must always be NULL here, as it holds a reference to @gnutls as + * its source object. However, we clear it anyway just in case this changes + * in future. */ + g_clear_object (&priv->implicit_handshake); + + 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_OBJECT_CLASS (g_tls_connection_gnutls_parent_class)->finalize (object); } static void g_tls_connection_gnutls_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) + guint prop_id, + GValue *value, + GParamSpec *pspec) { GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); GTlsBackend *backend; switch (prop_id) { case PROP_BASE_IO_STREAM: - g_value_set_object (value, gnutls->priv->base_io_stream); + g_value_set_object (value, priv->base_io_stream); + break; + + case PROP_BASE_SOCKET: + g_value_set_object (value, priv->base_socket); break; case PROP_REQUIRE_CLOSE_NOTIFY: - g_value_set_boolean (value, gnutls->priv->require_close_notify); + g_value_set_boolean (value, priv->require_close_notify); break; case PROP_REHANDSHAKE_MODE: - g_value_set_enum (value, gnutls->priv->rehandshake_mode); + g_value_set_enum (value, priv->rehandshake_mode); break; case PROP_USE_SYSTEM_CERTDB: - g_value_set_boolean (value, gnutls->priv->is_system_certdb); + g_value_set_boolean (value, priv->is_system_certdb); break; case PROP_DATABASE: - if (gnutls->priv->database_is_unset) + if (priv->database_is_unset) { backend = g_tls_backend_get_default (); - gnutls->priv->database = g_tls_backend_get_default_database (backend); - gnutls->priv->database_is_unset = FALSE; + priv->database = g_tls_backend_get_default_database (backend); + priv->database_is_unset = FALSE; } - g_value_set_object (value, gnutls->priv->database); + g_value_set_object (value, priv->database); break; case PROP_CERTIFICATE: - g_value_set_object (value, gnutls->priv->certificate); + g_value_set_object (value, priv->certificate); break; case PROP_INTERACTION: - g_value_set_object (value, gnutls->priv->interaction); + g_value_set_object (value, priv->interaction); break; case PROP_PEER_CERTIFICATE: - g_value_set_object (value, gnutls->priv->peer_certificate); + g_value_set_object (value, priv->peer_certificate); break; case PROP_PEER_CERTIFICATE_ERRORS: - g_value_set_flags (value, gnutls->priv->peer_certificate_errors); + g_value_set_flags (value, priv->peer_certificate_errors); + break; + +#if GLIB_CHECK_VERSION(2, 60, 0) + case PROP_ADVERTISED_PROTOCOLS: + g_value_set_boxed (value, priv->advertised_protocols); + break; + + case PROP_NEGOTIATED_PROTOCOL: + g_value_set_string (value, priv->negotiated_protocol); break; +#endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -368,11 +580,12 @@ g_tls_connection_gnutls_get_property (GObject *object, static void g_tls_connection_gnutls_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) + guint prop_id, + const GValue *value, + GParamSpec *pspec) { GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); GInputStream *istream; GOutputStream *ostream; gboolean system_certdb; @@ -381,378 +594,488 @@ g_tls_connection_gnutls_set_property (GObject *object, switch (prop_id) { case PROP_BASE_IO_STREAM: - if (gnutls->priv->base_io_stream) - { - g_object_unref (gnutls->priv->base_io_stream); - gnutls->priv->base_istream = NULL; - gnutls->priv->base_ostream = NULL; - } - gnutls->priv->base_io_stream = g_value_dup_object (value); - if (!gnutls->priv->base_io_stream) - return; - - istream = g_io_stream_get_input_stream (gnutls->priv->base_io_stream); - ostream = g_io_stream_get_output_stream (gnutls->priv->base_io_stream); + g_assert (g_value_get_object (value) == NULL || + priv->base_socket == NULL); + + if (priv->base_io_stream) + { + g_object_unref (priv->base_io_stream); + priv->base_istream = NULL; + priv->base_ostream = NULL; + } + priv->base_io_stream = g_value_dup_object (value); + if (!priv->base_io_stream) + return; + + istream = g_io_stream_get_input_stream (priv->base_io_stream); + ostream = g_io_stream_get_output_stream (priv->base_io_stream); if (G_IS_POLLABLE_INPUT_STREAM (istream) && - g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (istream))) - gnutls->priv->base_istream = G_POLLABLE_INPUT_STREAM (istream); + g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (istream))) + priv->base_istream = G_POLLABLE_INPUT_STREAM (istream); if (G_IS_POLLABLE_OUTPUT_STREAM (ostream) && - g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (ostream))) - gnutls->priv->base_ostream = G_POLLABLE_OUTPUT_STREAM (ostream); + g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (ostream))) + priv->base_ostream = G_POLLABLE_OUTPUT_STREAM (ostream); + break; + + case PROP_BASE_SOCKET: + g_assert (g_value_get_object (value) == NULL || + priv->base_io_stream == NULL); + + g_clear_object (&priv->base_socket); + priv->base_socket = g_value_dup_object (value); break; case PROP_REQUIRE_CLOSE_NOTIFY: - gnutls->priv->require_close_notify = g_value_get_boolean (value); + priv->require_close_notify = g_value_get_boolean (value); break; case PROP_REHANDSHAKE_MODE: - gnutls->priv->rehandshake_mode = g_value_get_enum (value); + priv->rehandshake_mode = g_value_get_enum (value); break; case PROP_USE_SYSTEM_CERTDB: system_certdb = g_value_get_boolean (value); - if (system_certdb != gnutls->priv->is_system_certdb) + if (system_certdb != priv->is_system_certdb) { - g_clear_object (&gnutls->priv->database); + g_clear_object (&priv->database); if (system_certdb) { backend = g_tls_backend_get_default (); - gnutls->priv->database = g_tls_backend_get_default_database (backend); + priv->database = g_tls_backend_get_default_database (backend); } - gnutls->priv->is_system_certdb = system_certdb; - gnutls->priv->database_is_unset = FALSE; + priv->is_system_certdb = system_certdb; + priv->database_is_unset = FALSE; } break; case PROP_DATABASE: - g_clear_object (&gnutls->priv->database); - gnutls->priv->database = g_value_dup_object (value); - gnutls->priv->is_system_certdb = FALSE; - gnutls->priv->database_is_unset = FALSE; + g_clear_object (&priv->database); + priv->database = g_value_dup_object (value); + priv->is_system_certdb = FALSE; + priv->database_is_unset = FALSE; break; case PROP_CERTIFICATE: - if (gnutls->priv->certificate) - g_object_unref (gnutls->priv->certificate); - gnutls->priv->certificate = g_value_dup_object (value); + if (priv->certificate) + g_object_unref (priv->certificate); + priv->certificate = g_value_dup_object (value); break; case PROP_INTERACTION: - g_clear_object (&gnutls->priv->interaction); - gnutls->priv->interaction = g_value_dup_object (value); + g_clear_object (&priv->interaction); + priv->interaction = g_value_dup_object (value); + break; + +#if GLIB_CHECK_VERSION(2, 60, 0) + case PROP_ADVERTISED_PROTOCOLS: + g_clear_pointer (&priv->advertised_protocols, g_strfreev); + priv->advertised_protocols = g_value_dup_boxed (value); break; +#endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } -gnutls_certificate_credentials +gnutls_certificate_credentials_t g_tls_connection_gnutls_get_credentials (GTlsConnectionGnutls *gnutls) { - return gnutls->priv->creds; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + return priv->creds; } -gnutls_session +gnutls_session_t g_tls_connection_gnutls_get_session (GTlsConnectionGnutls *gnutls) { - /* Ideally we would initialize gnutls->priv->session from - * g_tls_connection_gnutls_init(), but we can't tell if it's a - * client or server connection at that point... And - * g_tls_connection_gnutls_initiable_init() is too late, because - * construct-time property setters may need to modify it. - */ - if (!gnutls->priv->session) - { - gboolean client = G_IS_TLS_CLIENT_CONNECTION (gnutls); - gnutls_init (&gnutls->priv->session, client ? GNUTLS_CLIENT : GNUTLS_SERVER); - } + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); - return gnutls->priv->session; + return priv->session; } void -g_tls_connection_gnutls_get_certificate (GTlsConnectionGnutls *gnutls, - gnutls_retr2_st *st) +g_tls_connection_gnutls_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)); - st->cert_type = GNUTLS_CRT_X509; - st->ncerts = 0; - if (cert) + { g_tls_certificate_gnutls_copy (G_TLS_CERTIFICATE_GNUTLS (cert), - gnutls->priv->interaction_id, st); + priv->interaction_id, + pcert, pcert_length, pkey); + } + else + { + *pcert = NULL; + *pcert_length = 0; + *pkey = NULL; + } } typedef enum { G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE, G_TLS_CONNECTION_GNUTLS_OP_READ, G_TLS_CONNECTION_GNUTLS_OP_WRITE, - G_TLS_CONNECTION_GNUTLS_OP_CLOSE, + G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ, + G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE, + G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH, } GTlsConnectionGnutlsOp; static gboolean claim_op (GTlsConnectionGnutls *gnutls, - GTlsConnectionGnutlsOp op, - gboolean blocking, - GCancellable *cancellable, - GError **error) + GTlsConnectionGnutlsOp op, + gint64 timeout, + GCancellable *cancellable, + GError **error) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + try_again: if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; - g_mutex_lock (&gnutls->priv->op_mutex); + g_mutex_lock (&priv->op_mutex); - if (gnutls->priv->closing || gnutls->priv->closed) + if (((op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE || + op == G_TLS_CONNECTION_GNUTLS_OP_READ) && + (priv->read_closing || priv->read_closed)) || + ((op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE || + op == G_TLS_CONNECTION_GNUTLS_OP_WRITE) && + (priv->write_closing || priv->write_closed))) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED, - _("Connection is closed")); - g_mutex_unlock (&gnutls->priv->op_mutex); + _("Connection is closed")); + g_mutex_unlock (&priv->op_mutex); return FALSE; } - if (gnutls->priv->handshake_error && op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE) + if (priv->handshake_error && + op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH && + op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ && + op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE) { if (error) - *error = g_error_copy (gnutls->priv->handshake_error); - g_mutex_unlock (&gnutls->priv->op_mutex); + *error = g_error_copy (priv->handshake_error); + g_mutex_unlock (&priv->op_mutex); return FALSE; } - if (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE && - op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE) + if (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE) { - if (gnutls->priv->need_handshake) - { - gnutls->priv->need_handshake = FALSE; - gnutls->priv->handshaking = TRUE; - if (!do_implicit_handshake (gnutls, blocking, cancellable, error)) - { - g_mutex_unlock (&gnutls->priv->op_mutex); - return FALSE; - } - } + if (op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH && + op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ && + op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE && + priv->need_handshake) + { + priv->need_handshake = FALSE; + priv->handshaking = TRUE; + if (!do_implicit_handshake (gnutls, timeout, cancellable, error)) + { + g_mutex_unlock (&priv->op_mutex); + return FALSE; + } + } + + if (priv->need_finish_handshake && + priv->implicit_handshake) + { + GError *my_error = NULL; + gboolean success; + + priv->need_finish_handshake = FALSE; - if (gnutls->priv->need_finish_handshake) - { - GError *my_error = NULL; - gboolean success; + g_mutex_unlock (&priv->op_mutex); + success = finish_handshake (gnutls, priv->implicit_handshake, &my_error); + g_clear_object (&priv->implicit_handshake); + g_clear_pointer (&priv->handshake_context, g_main_context_unref); + g_mutex_lock (&priv->op_mutex); - gnutls->priv->need_finish_handshake = FALSE; + if (op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH && + op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ && + op != G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE && + (!success || g_cancellable_set_error_if_cancelled (cancellable, &my_error))) + { + g_propagate_error (error, my_error); + g_mutex_unlock (&priv->op_mutex); + return FALSE; + } - g_mutex_unlock (&gnutls->priv->op_mutex); - success = finish_handshake (gnutls, gnutls->priv->implicit_handshake, &my_error); - g_clear_object (&gnutls->priv->implicit_handshake); - g_mutex_lock (&gnutls->priv->op_mutex); + g_clear_error (&my_error); + } + } - gnutls->priv->handshaking = FALSE; - if (!success || g_cancellable_set_error_if_cancelled (cancellable, &my_error)) - { - g_propagate_error (error, my_error); - g_mutex_unlock (&gnutls->priv->op_mutex); - return FALSE; - } - } + if (priv->handshaking && + timeout != 0 && + g_main_context_is_owner (priv->handshake_context)) + { + /* Cannot perform a blocking operation during a handshake on the + * same thread that triggered the handshake. The only way this can + * occur is if the application is doing something weird in its + * accept-certificate callback. Allowing a blocking op would stall + * 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_mutex_unlock (&priv->op_mutex); + return FALSE; } - if ((op != G_TLS_CONNECTION_GNUTLS_OP_WRITE && gnutls->priv->reading) || - (op != G_TLS_CONNECTION_GNUTLS_OP_READ && gnutls->priv->writing) || - (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE && gnutls->priv->handshaking)) + if ((op != G_TLS_CONNECTION_GNUTLS_OP_WRITE && priv->reading) || + (op != G_TLS_CONNECTION_GNUTLS_OP_READ && priv->writing) || + (op != G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE && priv->handshaking)) { GPollFD fds[2]; int nfds; + gint64 start_time; + gint result = 1; /* if the loop is never entered, it’s as if we cancelled early */ - g_cancellable_reset (gnutls->priv->waiting_for_op); + g_cancellable_reset (priv->waiting_for_op); - g_mutex_unlock (&gnutls->priv->op_mutex); + g_mutex_unlock (&priv->op_mutex); - if (!blocking) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, - _("Operation would block")); - return FALSE; - } + 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. */ + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block"); + return FALSE; + } - g_cancellable_make_pollfd (gnutls->priv->waiting_for_op, &fds[0]); - if (g_cancellable_make_pollfd (cancellable, &fds[0])) - nfds = 2; + g_cancellable_make_pollfd (priv->waiting_for_op, &fds[0]); + if (g_cancellable_make_pollfd (cancellable, &fds[1])) + nfds = 2; else - nfds = 1; - g_poll (fds, nfds, -1); - g_cancellable_release_fd (cancellable); + nfds = 1; + + /* Convert from microseconds to milliseconds. */ + if (timeout != -1) + timeout = timeout / 1000; + + /* Poll until cancellation or the timeout is reached. */ + start_time = g_get_monotonic_time (); + + while (!g_cancellable_is_cancelled (priv->waiting_for_op) && + !g_cancellable_is_cancelled (cancellable)) + { + result = g_poll (fds, nfds, timeout); + + if (result == 0) + break; + if (result != -1 || errno != EINTR) + continue; + + if (timeout != -1) + { + timeout -= (g_get_monotonic_time () - start_time) / 1000; + if (timeout < 0) + timeout = 0; + } + } + + if (nfds > 1) + g_cancellable_release_fd (cancellable); + + if (result == 0) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + _("Socket I/O timed out")); + return FALSE; + } goto try_again; } if (op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE) { - gnutls->priv->handshaking = TRUE; - gnutls->priv->need_handshake = FALSE; + priv->handshaking = TRUE; + priv->need_handshake = FALSE; } - if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE) - gnutls->priv->closing = TRUE; + if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH || + op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ) + priv->read_closing = TRUE; + if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH || + op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE) + priv->write_closing = TRUE; if (op != G_TLS_CONNECTION_GNUTLS_OP_WRITE) - gnutls->priv->reading = TRUE; + priv->reading = TRUE; if (op != G_TLS_CONNECTION_GNUTLS_OP_READ) - gnutls->priv->writing = TRUE; + priv->writing = TRUE; - g_mutex_unlock (&gnutls->priv->op_mutex); + g_mutex_unlock (&priv->op_mutex); return TRUE; } static void yield_op (GTlsConnectionGnutls *gnutls, - GTlsConnectionGnutlsOp op) + GTlsConnectionGnutlsOp op) { - g_mutex_lock (&gnutls->priv->op_mutex); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + g_mutex_lock (&priv->op_mutex); if (op == G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE) - gnutls->priv->handshaking = FALSE; - if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE) - gnutls->priv->closing = FALSE; + priv->handshaking = FALSE; + if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH || + op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ) + priv->read_closing = FALSE; + if (op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH || + op == G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE) + priv->write_closing = FALSE; if (op != G_TLS_CONNECTION_GNUTLS_OP_WRITE) - gnutls->priv->reading = FALSE; + priv->reading = FALSE; if (op != G_TLS_CONNECTION_GNUTLS_OP_READ) - gnutls->priv->writing = FALSE; + priv->writing = FALSE; - g_cancellable_cancel (gnutls->priv->waiting_for_op); - g_mutex_unlock (&gnutls->priv->op_mutex); + g_cancellable_cancel (priv->waiting_for_op); + g_mutex_unlock (&priv->op_mutex); } static void begin_gnutls_io (GTlsConnectionGnutls *gnutls, - GIOCondition direction, - gboolean blocking, - GCancellable *cancellable) + GIOCondition direction, + gint64 timeout, + GCancellable *cancellable) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + g_assert (direction & (G_IO_IN | G_IO_OUT)); if (direction & G_IO_IN) { - gnutls->priv->read_blocking = blocking; - gnutls->priv->read_cancellable = cancellable; - g_clear_error (&gnutls->priv->read_error); + priv->read_timeout = timeout; + priv->read_cancellable = cancellable; + g_clear_error (&priv->read_error); } if (direction & G_IO_OUT) { - gnutls->priv->write_blocking = blocking; - gnutls->priv->write_cancellable = cancellable; - g_clear_error (&gnutls->priv->write_error); + priv->write_timeout = timeout; + priv->write_cancellable = cancellable; + g_clear_error (&priv->write_error); } } static int end_gnutls_io (GTlsConnectionGnutls *gnutls, - GIOCondition direction, - int status, - const char *errmsg, - GError **error) + GIOCondition direction, + int status, + GError **error, + const char *err_prefix); + +static int +end_gnutls_io (GTlsConnectionGnutls *gnutls, + GIOCondition direction, + int status, + GError **error, + const char *err_prefix) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); GError *my_error = NULL; g_assert (direction & (G_IO_IN | G_IO_OUT)); g_assert (!error || !*error); + /* 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 (status == GNUTLS_E_AGAIN || status == GNUTLS_E_WARNING_ALERT_RECEIVED) return GNUTLS_E_AGAIN; if (direction & G_IO_IN) { - gnutls->priv->read_cancellable = NULL; + priv->read_cancellable = NULL; if (status < 0) - { - my_error = gnutls->priv->read_error; - gnutls->priv->read_error = NULL; - } + { + my_error = priv->read_error; + priv->read_error = NULL; + } else - g_clear_error (&gnutls->priv->read_error); + g_clear_error (&priv->read_error); } if (direction & G_IO_OUT) { - gnutls->priv->write_cancellable = NULL; + priv->write_cancellable = NULL; if (status < 0 && !my_error) - { - my_error = gnutls->priv->write_error; - gnutls->priv->write_error = NULL; - } + { + my_error = priv->write_error; + priv->write_error = NULL; + } else - g_clear_error (&gnutls->priv->write_error); + g_clear_error (&priv->write_error); } if (status >= 0) return status; - if (gnutls->priv->handshaking && !gnutls->priv->ever_handshaked) + if (priv->handshaking && !priv->ever_handshaked) { if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_FAILED) || -#if GLIB_CHECK_VERSION (2, 35, 3) - g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) || -#endif - status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH || - status == GNUTLS_E_FATAL_ALERT_RECEIVED || - status == GNUTLS_E_DECRYPTION_FAILED || - status == GNUTLS_E_UNSUPPORTED_VERSION_PACKET) - { - g_clear_error (&my_error); - g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS, - _("Peer failed to perform TLS handshake")); - return GNUTLS_E_PULL_ERROR; - } + g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) || + 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_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS, + _("Peer failed to perform TLS handshake")); + return GNUTLS_E_PULL_ERROR; + } } if (my_error) { - if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) - G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls); + if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) && + !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) + G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls); g_propagate_error (error, my_error); return status; } else if (status == GNUTLS_E_REHANDSHAKE) { - if (gnutls->priv->rehandshake_mode == G_TLS_REHANDSHAKE_NEVER) - { - g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC, - _("Peer requested illegal TLS rehandshake")); - return GNUTLS_E_PULL_ERROR; - } - - g_mutex_lock (&gnutls->priv->op_mutex); - if (!gnutls->priv->handshaking) - gnutls->priv->need_handshake = TRUE; - g_mutex_unlock (&gnutls->priv->op_mutex); + if (priv->rehandshake_mode == G_TLS_REHANDSHAKE_NEVER) + { + g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC, + _("Peer requested illegal TLS rehandshake")); + return GNUTLS_E_PULL_ERROR; + } + + g_mutex_lock (&priv->op_mutex); + if (!priv->handshaking) + priv->need_handshake = TRUE; + g_mutex_unlock (&priv->op_mutex); return status; } - else if (status == GNUTLS_E_GOT_APPLICATION_DATA) + else if (status == GNUTLS_E_PREMATURE_TERMINATION) { - if (gnutls->priv->handshaking && G_IS_TLS_SERVER_CONNECTION (gnutls)) - return GNUTLS_E_AGAIN; - } - else if ( -#ifdef GNUTLS_E_PREMATURE_TERMINATION - status == GNUTLS_E_PREMATURE_TERMINATION -#else - status == GNUTLS_E_UNEXPECTED_PACKET_LENGTH && gnutls->priv->eof -#endif - ) - { - if (gnutls->priv->require_close_notify) - { - g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF, - _("TLS connection closed unexpectedly")); - G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls); - return status; - } + if (priv->handshaking && !priv->ever_handshaked) + { + g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS, + _("Peer failed to perform TLS handshake")); + return GNUTLS_E_PULL_ERROR; + } + else if (priv->require_close_notify) + { + g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_EOF, + _("TLS connection closed unexpectedly")); + G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->failed (gnutls); + return status; + } else - return 0; + return 0; } else if (status == GNUTLS_E_NO_CERTIFICATE_FOUND) { @@ -760,47 +1083,113 @@ end_gnutls_io (GTlsConnectionGnutls *gnutls, _("TLS connection peer did not send a certificate")); return status; } + else if (status == GNUTLS_E_CERTIFICATE_ERROR) + { + g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE, + _("Unacceptable TLS certificate")); + return status; + } + else if (status == GNUTLS_E_FATAL_ALERT_RECEIVED) + { + g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC, + _("Peer sent fatal TLS alert: %s"), + gnutls_alert_get_name (gnutls_alert_get (priv->session))); + return status; + } + else if (status == GNUTLS_E_INAPPROPRIATE_FALLBACK) + { + g_set_error_literal (error, G_TLS_ERROR, +#if GLIB_CHECK_VERSION(2, 60, 0) + G_TLS_ERROR_INAPPROPRIATE_FALLBACK, +#else + G_TLS_ERROR_MISC, +#endif + _("Protocol version downgrade attack detected")); + return status; + } + else if (status == GNUTLS_E_LARGE_PACKET) + { + guint mtu = gnutls_dtls_get_data_mtu (priv->session); + 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 status; + } + else if (status == GNUTLS_E_TIMEDOUT) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + _("The operation timed out")); + return status; + } if (error) { - g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC, - errmsg, gnutls_strerror (status)); + *error = g_error_new (G_TLS_ERROR, G_TLS_ERROR_MISC, "%s: %s", + err_prefix, gnutls_strerror (status)); } return status; } -#define BEGIN_GNUTLS_IO(gnutls, direction, blocking, cancellable) \ - begin_gnutls_io (gnutls, direction, blocking, cancellable); \ +#define BEGIN_GNUTLS_IO(gnutls, direction, timeout, cancellable) \ + begin_gnutls_io (gnutls, direction, timeout, cancellable); \ do { -#define END_GNUTLS_IO(gnutls, direction, ret, errmsg, err) \ - } while ((ret = end_gnutls_io (gnutls, direction, ret, errmsg, err)) == GNUTLS_E_AGAIN); +#define END_GNUTLS_IO(gnutls, direction, ret, errmsg, err) \ + } while ((ret = end_gnutls_io (gnutls, direction, ret, err, errmsg)) == GNUTLS_E_AGAIN); + +/* Checks whether the underlying base stream or GDatagramBased meets + * @condition. */ +static gboolean +g_tls_connection_gnutls_base_check (GTlsConnectionGnutls *gnutls, + GIOCondition condition) +{ + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + if (g_tls_connection_gnutls_is_dtls (gnutls)) + return g_datagram_based_condition_check (priv->base_socket, + condition); + else if (condition & G_IO_IN) + return g_pollable_input_stream_is_readable (priv->base_istream); + else if (condition & G_IO_OUT) + return g_pollable_output_stream_is_writable (priv->base_ostream); + else + g_assert_not_reached (); +} +/* Checks whether the (D)TLS stream meets @condition; not the underlying base + * stream or GDatagramBased. */ gboolean g_tls_connection_gnutls_check (GTlsConnectionGnutls *gnutls, - GIOCondition condition) + GIOCondition condition) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + /* Racy, but worst case is that we just get WOULD_BLOCK back */ - if (gnutls->priv->need_finish_handshake) + if (priv->need_finish_handshake) return TRUE; /* If a handshake or close is in progress, then tls_istream and * tls_ostream are blocked, regardless of the base stream status. */ - if (gnutls->priv->handshaking || gnutls->priv->closing) + if (priv->handshaking) return FALSE; - if (condition & G_IO_IN) - return g_pollable_input_stream_is_readable (gnutls->priv->base_istream); - else - return g_pollable_output_stream_is_writable (gnutls->priv->base_ostream); + if (((condition & G_IO_IN) && priv->read_closing) || + ((condition & G_IO_OUT) && priv->write_closing)) + return FALSE; + + /* Defer to the base stream or GDatagramBased. */ + return g_tls_connection_gnutls_base_check (gnutls, condition); } typedef struct { GSource source; GTlsConnectionGnutls *gnutls; - GObject *stream; + /* Either a GDatagramBased (datagram mode), or a GPollableInputStream or + * GPollableOutputStream (streaming mode): + */ + GObject *base; GSource *child_source; GIOCondition condition; @@ -811,7 +1200,7 @@ typedef struct { static gboolean gnutls_source_prepare (GSource *source, - gint *timeout) + gint *timeout) { *timeout = -1; return FALSE; @@ -823,26 +1212,40 @@ gnutls_source_check (GSource *source) return FALSE; } +/* 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 gnutls_source_sync (GTlsConnectionGnutlsSource *gnutls_source) { GTlsConnectionGnutls *gnutls = gnutls_source->gnutls; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); gboolean io_waiting, op_waiting; - g_mutex_lock (&gnutls->priv->op_mutex); - if (((gnutls_source->condition & G_IO_IN) && gnutls->priv->reading) || - ((gnutls_source->condition & G_IO_OUT) && gnutls->priv->writing) || - (gnutls->priv->handshaking && !gnutls->priv->need_finish_handshake)) + /* Was the source destroyed earlier in this main context iteration? */ + if (g_source_is_destroyed ((GSource *)gnutls_source)) + return; + + g_mutex_lock (&priv->op_mutex); + if (((gnutls_source->condition & G_IO_IN) && priv->reading) || + ((gnutls_source->condition & G_IO_OUT) && priv->writing) || + (priv->handshaking && !priv->need_finish_handshake)) op_waiting = TRUE; else op_waiting = FALSE; - if (!op_waiting && !gnutls->priv->need_handshake && - !gnutls->priv->need_finish_handshake) + if (!op_waiting && !priv->need_handshake && + !priv->need_finish_handshake) io_waiting = TRUE; else io_waiting = FALSE; - g_mutex_unlock (&gnutls->priv->op_mutex); + g_mutex_unlock (&priv->op_mutex); if (op_waiting == gnutls_source->op_waiting && io_waiting == gnutls_source->io_waiting) @@ -853,33 +1256,41 @@ gnutls_source_sync (GTlsConnectionGnutlsSource *gnutls_source) if (gnutls_source->child_source) { g_source_remove_child_source ((GSource *)gnutls_source, - gnutls_source->child_source); + gnutls_source->child_source); g_source_unref (gnutls_source->child_source); } if (op_waiting) - gnutls_source->child_source = g_cancellable_source_new (gnutls->priv->waiting_for_op); - else if (io_waiting && G_IS_POLLABLE_INPUT_STREAM (gnutls_source->stream)) - gnutls_source->child_source = g_pollable_input_stream_create_source (gnutls->priv->base_istream, NULL); - else if (io_waiting && G_IS_POLLABLE_OUTPUT_STREAM (gnutls_source->stream)) - gnutls_source->child_source = g_pollable_output_stream_create_source (gnutls->priv->base_ostream, NULL); + gnutls_source->child_source = g_cancellable_source_new (priv->waiting_for_op); + else if (io_waiting && G_IS_DATAGRAM_BASED (gnutls_source->base)) + gnutls_source->child_source = g_datagram_based_create_source (priv->base_socket, gnutls_source->condition, NULL); + else if (io_waiting && G_IS_POLLABLE_INPUT_STREAM (gnutls_source->base)) + gnutls_source->child_source = g_pollable_input_stream_create_source (priv->base_istream, NULL); + else if (io_waiting && G_IS_POLLABLE_OUTPUT_STREAM (gnutls_source->base)) + gnutls_source->child_source = g_pollable_output_stream_create_source (priv->base_ostream, NULL); else gnutls_source->child_source = g_timeout_source_new (0); - g_source_set_dummy_callback (gnutls_source->child_source); + g_source_set_callback (gnutls_source->child_source, dummy_callback, NULL, NULL); g_source_add_child_source ((GSource *)gnutls_source, gnutls_source->child_source); } static gboolean gnutls_source_dispatch (GSource *source, - GSourceFunc callback, - gpointer user_data) + GSourceFunc callback, + gpointer user_data) { - GPollableSourceFunc func = (GPollableSourceFunc)callback; + GDatagramBasedSourceFunc datagram_based_func = (GDatagramBasedSourceFunc)callback; + GPollableSourceFunc pollable_func = (GPollableSourceFunc)callback; GTlsConnectionGnutlsSource *gnutls_source = (GTlsConnectionGnutlsSource *)source; gboolean ret; - ret = (*func) (gnutls_source->stream, user_data); + if (G_IS_DATAGRAM_BASED (gnutls_source->base)) + ret = (*datagram_based_func) (G_DATAGRAM_BASED (gnutls_source->base), + gnutls_source->condition, user_data); + else + ret = (*pollable_func) (gnutls_source->base, user_data); + if (ret) gnutls_source_sync (gnutls_source); @@ -897,7 +1308,7 @@ gnutls_source_finalize (GSource *source) static gboolean g_tls_connection_gnutls_source_closure_callback (GObject *stream, - gpointer data) + gpointer data) { GClosure *closure = data; @@ -919,7 +1330,35 @@ g_tls_connection_gnutls_source_closure_callback (GObject *stream, return result; } -static GSourceFuncs gnutls_source_funcs = +static gboolean +g_tls_connection_gnutls_source_dtls_closure_callback (GObject *stream, + GIOCondition condition, + gpointer data) +{ + GClosure *closure = data; + + GValue param[2] = { 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_DATAGRAM_BASED); + g_value_set_object (¶m[0], stream); + g_value_init (¶m[1], G_TYPE_IO_CONDITION); + g_value_set_flags (¶m[1], condition); + + g_closure_invoke (closure, &result_value, 2, param, NULL); + + result = g_value_get_boolean (&result_value); + g_value_unset (&result_value); + g_value_unset (¶m[0]); + g_value_unset (¶m[1]); + + return result; +} + +static GSourceFuncs gnutls_tls_source_funcs = { gnutls_source_prepare, gnutls_source_check, @@ -929,23 +1368,47 @@ static GSourceFuncs gnutls_source_funcs = (GSourceDummyMarshal)g_cclosure_marshal_generic }; +static GSourceFuncs gnutls_dtls_source_funcs = +{ + gnutls_source_prepare, + gnutls_source_check, + gnutls_source_dispatch, + gnutls_source_finalize, + (GSourceFunc)g_tls_connection_gnutls_source_dtls_closure_callback, + (GSourceDummyMarshal)g_cclosure_marshal_generic +}; + GSource * g_tls_connection_gnutls_create_source (GTlsConnectionGnutls *gnutls, - GIOCondition condition, - GCancellable *cancellable) + GIOCondition condition, + GCancellable *cancellable) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); GSource *source, *cancellable_source; GTlsConnectionGnutlsSource *gnutls_source; - source = g_source_new (&gnutls_source_funcs, sizeof (GTlsConnectionGnutlsSource)); + if (g_tls_connection_gnutls_is_dtls (gnutls)) + { + source = g_source_new (&gnutls_dtls_source_funcs, + sizeof (GTlsConnectionGnutlsSource)); + } + else + { + source = g_source_new (&gnutls_tls_source_funcs, + sizeof (GTlsConnectionGnutlsSource)); + } g_source_set_name (source, "GTlsConnectionGnutlsSource"); gnutls_source = (GTlsConnectionGnutlsSource *)source; gnutls_source->gnutls = g_object_ref (gnutls); gnutls_source->condition = condition; - if (condition & G_IO_IN) - gnutls_source->stream = G_OBJECT (gnutls->priv->tls_istream); - else if (condition & G_IO_OUT) - gnutls_source->stream = G_OBJECT (gnutls->priv->tls_ostream); + if (g_tls_connection_gnutls_is_dtls (gnutls)) + gnutls_source->base = G_OBJECT (gnutls); + else if (priv->tls_istream != NULL && condition & G_IO_IN) + gnutls_source->base = G_OBJECT (priv->tls_istream); + else if (priv->tls_ostream != NULL && condition & G_IO_OUT) + gnutls_source->base = G_OBJECT (priv->tls_ostream); + else + g_assert_not_reached (); gnutls_source->op_waiting = (gboolean) -1; gnutls_source->io_waiting = (gboolean) -1; @@ -962,168 +1425,382 @@ g_tls_connection_gnutls_create_source (GTlsConnectionGnutls *gnutls, return source; } -static void -set_gnutls_error (GTlsConnectionGnutls *gnutls, - GError *error) +static GSource * +g_tls_connection_gnutls_dtls_create_source (GDatagramBased *datagram_based, + GIOCondition condition, + GCancellable *cancellable) { - /* 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). - */ + GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based); - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - gnutls_transport_set_errno (gnutls->priv->session, EINTR); - else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) - gnutls_transport_set_errno (gnutls->priv->session, EINTR); - else - gnutls_transport_set_errno (gnutls->priv->session, EIO); + return g_tls_connection_gnutls_create_source (gnutls, condition, cancellable); } -static ssize_t -g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t transport_data, - void *buf, - size_t buflen) +static GIOCondition +g_tls_connection_gnutls_condition_check (GDatagramBased *datagram_based, + GIOCondition condition) { - GTlsConnectionGnutls *gnutls = transport_data; - ssize_t ret; + GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based); - /* If gnutls->priv->read_error is non-%NULL 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 (&gnutls->priv->read_error); + return (g_tls_connection_gnutls_check (gnutls, condition)) ? condition : 0; +} - ret = g_pollable_stream_read (G_INPUT_STREAM (gnutls->priv->base_istream), - buf, buflen, - gnutls->priv->read_blocking, - gnutls->priv->read_cancellable, - &gnutls->priv->read_error); +static gboolean +g_tls_connection_gnutls_condition_wait (GDatagramBased *datagram_based, + GIOCondition condition, + gint64 timeout, + GCancellable *cancellable, + GError **error) +{ + GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + GPollFD fds[2]; + guint n_fds; + gint result = 1; /* if the loop is never entered, it’s as if we cancelled early */ + gint64 start_time; - if (ret < 0) - set_gnutls_error (gnutls, gnutls->priv->read_error); -#ifndef GNUTLS_E_PREMATURE_TERMINATION - else if (ret == 0) - gnutls->priv->eof = TRUE; -#endif + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; - return ret; + /* Convert from microseconds to milliseconds. */ + if (timeout != -1) + timeout = timeout / 1000; + + start_time = g_get_monotonic_time (); + + g_cancellable_make_pollfd (priv->waiting_for_op, &fds[0]); + n_fds = 1; + + if (g_cancellable_make_pollfd (cancellable, &fds[1])) + n_fds++; + + while (!g_tls_connection_gnutls_condition_check (datagram_based, condition) && + !g_cancellable_is_cancelled (cancellable)) + { + result = g_poll (fds, n_fds, timeout); + if (result == 0) + break; + if (result != -1 || errno != EINTR) + continue; + + if (timeout != -1) + { + timeout -= (g_get_monotonic_time () - start_time) / 1000; + if (timeout < 0) + timeout = 0; + } + } + + if (n_fds > 1) + g_cancellable_release_fd (cancellable); + + if (result == 0) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + _("Socket I/O timed out")); + return FALSE; + } + + return !g_cancellable_set_error_if_cancelled (cancellable, error); +} + +static void +set_gnutls_error (GTlsConnectionGnutls *gnutls, + GError *error) +{ + 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 (priv->base_socket && priv->handshaking) + 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_push_func (gnutls_transport_ptr_t transport_data, - const void *buf, - size_t buflen) +g_tls_connection_gnutls_pull_func (gnutls_transport_ptr_t transport_data, + void *buf, + size_t buflen) { GTlsConnectionGnutls *gnutls = transport_data; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); ssize_t ret; - /* See comment in pull_func. */ - g_clear_error (&gnutls->priv->write_error); + /* If priv->read_error is non-%NULL 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 (&priv->read_error); + + if (g_tls_connection_gnutls_is_dtls (gnutls)) + { + GInputVector vector = { buf, buflen }; + GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL }; + + ret = g_datagram_based_receive_messages (priv->base_socket, + &message, 1, 0, + priv->handshaking ? 0 : priv->read_timeout, + priv->read_cancellable, + &priv->read_error); + + if (ret > 0) + ret = message.bytes_received; + } + else + { + ret = g_pollable_stream_read (G_INPUT_STREAM (priv->base_istream), + buf, buflen, + (priv->read_timeout != 0), + priv->read_cancellable, + &priv->read_error); + } - ret = g_pollable_stream_write (G_OUTPUT_STREAM (gnutls->priv->base_ostream), - buf, buflen, - gnutls->priv->write_blocking, - gnutls->priv->write_cancellable, - &gnutls->priv->write_error); if (ret < 0) - set_gnutls_error (gnutls, gnutls->priv->write_error); + set_gnutls_error (gnutls, priv->read_error); return ret; } -static void -handshake_thread (GTask *task, - gpointer object, - gpointer task_data, - GCancellable *cancellable) +static ssize_t +g_tls_connection_gnutls_push_func (gnutls_transport_ptr_t transport_data, + const void *buf, + size_t buflen) { - GTlsConnectionGnutls *gnutls = object; - gboolean is_client; - GError *error = NULL; - int ret; + GTlsConnectionGnutls *gnutls = transport_data; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + ssize_t ret; - gnutls->priv->started_handshake = FALSE; + /* See comment in pull_func. */ + g_clear_error (&priv->write_error); - if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE, - TRUE, cancellable, &error)) + if (g_tls_connection_gnutls_is_dtls (gnutls)) { - g_task_return_error (task, error); - return; - } + GOutputVector vector = { buf, buflen }; + GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 }; - g_clear_error (&gnutls->priv->handshake_error); + ret = g_datagram_based_send_messages (priv->base_socket, + &message, 1, 0, + priv->write_timeout, + priv->write_cancellable, + &priv->write_error); - is_client = G_IS_TLS_CLIENT_CONNECTION (gnutls); - - if (!is_client && gnutls->priv->ever_handshaked && - !gnutls->priv->implicit_handshake) + if (ret > 0) + ret = message.bytes_sent; + } + else { - BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable); - ret = gnutls_rehandshake (gnutls->priv->session); - END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, - _("Error performing TLS handshake: %s"), &error); - - if (error) - { - g_task_return_error (task, error); - return; - } + ret = g_pollable_stream_write (G_OUTPUT_STREAM (priv->base_ostream), + buf, buflen, + (priv->write_timeout != 0), + priv->write_cancellable, + &priv->write_error); } - gnutls->priv->started_handshake = TRUE; + if (ret < 0) + set_gnutls_error (gnutls, priv->write_error); - g_clear_object (&gnutls->priv->peer_certificate); - gnutls->priv->peer_certificate_errors = 0; + return ret; +} - g_tls_connection_gnutls_set_handshake_priority (gnutls); +static ssize_t +g_tls_connection_gnutls_vec_push_func (gnutls_transport_ptr_t transport_data, + const giovec_t *iov, + int iovcnt) +{ + GTlsConnectionGnutls *gnutls = transport_data; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + ssize_t ret; + GOutputMessage message = { NULL, }; + GOutputVector *vectors; - BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable); - ret = gnutls_handshake (gnutls->priv->session); - END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, - _("Error performing TLS handshake: %s"), &error); + /* This function should only be set if we’re using base_socket. */ + g_assert (priv->base_socket != NULL); - if (error) + /* See comment in pull_func. */ + g_clear_error (&priv->write_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 */ { - g_task_return_error (task, error); + message.vectors = (GOutputVector *)iov; + message.num_vectors = iovcnt; } else + /* ABI is incompatible */ { - gnutls->priv->ever_handshaked = TRUE; - g_task_return_boolean (task, TRUE); + 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 (priv->base_socket, + &message, 1, 0, + priv->write_timeout, + priv->write_cancellable, + &priv->write_error); + + if (ret > 0) + ret = message.bytes_sent; + else if (ret < 0) + set_gnutls_error (gnutls, priv->write_error); + + 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) +{ + GTlsConnectionGnutls *gnutls = transport_data; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + /* Fast path. */ + if (g_tls_connection_gnutls_base_check (gnutls, G_IO_IN) || + g_cancellable_is_cancelled (priv->read_cancellable)) + 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_gnutls_is_dtls (gnutls)) + { + read_source = g_datagram_based_create_source (priv->base_socket, 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 (priv->base_istream, 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_gnutls_base_check (gnutls, G_IO_IN) || + g_cancellable_is_cancelled (priv->read_cancellable)) + return 1; } + + return 0; } static GTlsCertificate * get_peer_certificate_from_session (GTlsConnectionGnutls *gnutls) { - GTlsCertificate *chain, *cert; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); const gnutls_datum_t *certs; + GTlsCertificateGnutls *chain; unsigned int num_certs; - int i; - certs = gnutls_certificate_get_peers (gnutls->priv->session, &num_certs); + certs = gnutls_certificate_get_peers (priv->session, &num_certs); if (!certs || !num_certs) return NULL; - chain = NULL; - for (i = num_certs - 1; i >= 0; i--) - { - cert = g_tls_certificate_gnutls_new (&certs[i], chain); - if (chain) - g_object_unref (chain); - chain = cert; - } + chain = g_tls_certificate_gnutls_build_chain (certs, num_certs, GNUTLS_X509_FMT_DER); + if (!chain) + return NULL; - return chain; + return G_TLS_CERTIFICATE (chain); } static GTlsCertificateFlags verify_peer_certificate (GTlsConnectionGnutls *gnutls, - GTlsCertificate *peer_certificate) + GTlsCertificate *peer_certificate) { GTlsConnection *conn = G_TLS_CONNECTION (gnutls); GSocketConnectable *peer_identity; @@ -1132,10 +1809,13 @@ verify_peer_certificate (GTlsConnectionGnutls *gnutls, gboolean is_client; is_client = G_IS_TLS_CLIENT_CONNECTION (gnutls); - if (is_client) + + if (!is_client) + peer_identity = NULL; + else if (!g_tls_connection_gnutls_is_dtls (gnutls)) peer_identity = g_tls_client_connection_get_server_identity (G_TLS_CLIENT_CONNECTION (gnutls)); else - peer_identity = NULL; + peer_identity = g_dtls_client_connection_get_server_identity (G_DTLS_CLIENT_CONNECTION (gnutls)); errors = 0; @@ -1150,121 +1830,447 @@ verify_peer_certificate (GTlsConnectionGnutls *gnutls, GError *error = NULL; errors |= g_tls_database_verify_chain (database, peer_certificate, - is_client ? - G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER : - G_TLS_DATABASE_PURPOSE_AUTHENTICATE_CLIENT, - peer_identity, - g_tls_connection_get_interaction (conn), - G_TLS_DATABASE_VERIFY_NONE, - NULL, &error); + is_client ? + G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER : + G_TLS_DATABASE_PURPOSE_AUTHENTICATE_CLIENT, + peer_identity, + g_tls_connection_get_interaction (conn), + G_TLS_DATABASE_VERIFY_NONE, + NULL, &error); if (error) - { - g_warning ("failure verifying certificate chain: %s", - error->message); - g_assert (errors != 0); - g_clear_error (&error); - } + { + g_warning ("failure verifying certificate chain: %s", + error->message); + g_assert (errors != 0); + g_clear_error (&error); + } } return errors; } +static void +update_peer_certificate_and_compute_errors (GTlsConnectionGnutls *gnutls) +{ + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + /* 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. + */ + g_assert (priv->handshake_context); + g_assert (g_main_context_is_owner (priv->handshake_context)); + + g_clear_object (&priv->peer_certificate); + priv->peer_certificate_errors = 0; + + if (gnutls_certificate_type_get (priv->session) == GNUTLS_CRT_X509) + { + priv->peer_certificate = get_peer_certificate_from_session (gnutls); + if (priv->peer_certificate) + priv->peer_certificate_errors = verify_peer_certificate (gnutls, priv->peer_certificate); + } + + g_object_notify (G_OBJECT (gnutls), "peer-certificate"); + g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors"); +} + static gboolean -accept_peer_certificate (GTlsConnectionGnutls *gnutls, - GTlsCertificate *peer_certificate, - GTlsCertificateFlags peer_certificate_errors) +accept_or_reject_peer_certificate (gpointer user_data) { - gboolean accepted; + GTlsConnectionGnutls *gnutls = user_data; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + gboolean accepted = FALSE; - if (G_IS_TLS_CLIENT_CONNECTION (gnutls)) + g_assert (g_main_context_is_owner (priv->handshake_context)); + + g_mutex_lock (&priv->verify_certificate_mutex); + + update_peer_certificate_and_compute_errors (gnutls); + + if (G_IS_TLS_CLIENT_CONNECTION (gnutls) && priv->peer_certificate != NULL) { - GTlsCertificateFlags validation_flags = - g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (gnutls)); + GTlsCertificateFlags validation_flags; - if ((peer_certificate_errors & validation_flags) == 0) - accepted = TRUE; + if (!g_tls_connection_gnutls_is_dtls (gnutls)) + validation_flags = + g_tls_client_connection_get_validation_flags (G_TLS_CLIENT_CONNECTION (gnutls)); else - { - accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls), - peer_certificate, - peer_certificate_errors); - } + validation_flags = + g_dtls_client_connection_get_validation_flags (G_DTLS_CLIENT_CONNECTION (gnutls)); + + if ((priv->peer_certificate_errors & validation_flags) == 0) + accepted = TRUE; } - else + + if (!accepted) { + g_main_context_pop_thread_default (priv->handshake_context); accepted = g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls), - peer_certificate, - peer_certificate_errors); + priv->peer_certificate, + priv->peer_certificate_errors); + g_main_context_push_thread_default (priv->handshake_context); } - return accepted; + 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); + + g_object_notify (G_OBJECT (gnutls), "peer-certificate"); + g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors"); + + return G_SOURCE_REMOVE; +} + +static int +verify_certificate_cb (gnutls_session_t session) +{ + GTlsConnectionGnutls *gnutls = gnutls_session_get_ptr (session); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + gboolean accepted; + + g_mutex_lock (&priv->verify_certificate_mutex); + priv->peer_certificate_examined = FALSE; + priv->peer_certificate_accepted = FALSE; + g_mutex_unlock (&priv->verify_certificate_mutex); + + /* Invoke the callback on the handshake context's thread. This is + * necessary because we need to ensure the accept-certificate signal + * is emitted on the original thread. + */ + g_assert (priv->handshake_context); + g_main_context_invoke (priv->handshake_context, accept_or_reject_peer_certificate, gnutls); + + /* We'll block the handshake thread until the original thread has + * decided whether to accept the certificate. + */ + g_mutex_lock (&priv->verify_certificate_mutex); + while (!priv->peer_certificate_examined) + g_cond_wait (&priv->verify_certificate_condition, &priv->verify_certificate_mutex); + accepted = priv->peer_certificate_accepted; + g_mutex_unlock (&priv->verify_certificate_mutex); + + /* Return 0 for the handshake to continue, non-zero to terminate. */ + return !accepted; +} + +static void +handshake_thread (GTask *task, + gpointer object, + gpointer task_data, + GCancellable *cancellable) +{ + GTlsConnectionGnutls *gnutls = object; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + GError *error = NULL; + int ret; + gint64 start_time; + gint64 timeout; + + /* A timeout, in microseconds, must be provided as a gint64* task_data. */ + g_assert (task_data != NULL); + + timeout = *((gint64 *)task_data); + start_time = g_get_monotonic_time (); + priv->started_handshake = FALSE; + + if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE, + timeout, cancellable, &error)) + { + g_task_return_error (task, error); + return; + } + + g_clear_error (&priv->handshake_error); + + if (priv->ever_handshaked && !priv->implicit_handshake) + { + if (priv->rehandshake_mode != G_TLS_REHANDSHAKE_UNSAFELY && + !gnutls_safe_renegotiation_status (priv->session)) + { + g_task_return_new_error (task, G_TLS_ERROR, G_TLS_ERROR_MISC, + _("Peer does not support safe renegotiation")); + return; + } + + if (!G_IS_TLS_CLIENT_CONNECTION (gnutls)) + { + /* Adjust the timeout for the next operation in the sequence. */ + if (timeout > 0) + { + unsigned int timeout_ms; + + timeout -= (g_get_monotonic_time () - start_time); + if (timeout <= 0) + timeout = 1; + + /* 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_rehandshake (priv->session); + END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, + _("Error performing TLS handshake"), &error); + + if (error) + { + g_task_return_error (task, error); + return; + } + } + } + + priv->started_handshake = TRUE; + + if (!priv->ever_handshaked) + g_tls_connection_gnutls_set_handshake_priority (gnutls); + + /* Adjust the timeout for the next operation in the sequence. */ + if (timeout > 0) + { + unsigned int timeout_ms; + + timeout -= (g_get_monotonic_time () - start_time); + if (timeout <= 0) + timeout = 1; + + /* 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) + { + if (!priv->app_data_buf) + priv->app_data_buf = g_byte_array_new (); + g_byte_array_append (priv->app_data_buf, buf, ret); + ret = GNUTLS_E_AGAIN; + } + } + END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, + _("Error performing TLS handshake"), &error); + + /* This calls the finish_handshake code of GTlsClientConnectionGnutls + * or GTlsServerConnectionGnutls. It has nothing to do with + * GTlsConnectionGnutls's own finish_handshake function, which still + * needs to be called at this point. + */ + G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->finish_handshake (gnutls, &error); + + if (error) + { + g_task_return_error (task, error); + } + else + { + priv->ever_handshaked = TRUE; + g_task_return_boolean (task, TRUE); + } } static void begin_handshake (GTlsConnectionGnutls *gnutls) { +#if GLIB_CHECK_VERSION(2, 60, 0) + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + +#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG) + HWCLOCK_LOG("[BGN] gnutls_verify_peer"); +#endif + + if (priv->advertised_protocols) + { + gnutls_datum_t *protocols; + int n_protos, i; + + n_protos = g_strv_length (priv->advertised_protocols); + protocols = g_new (gnutls_datum_t, n_protos); + for (i = 0; priv->advertised_protocols[i]; i++) + { + protocols[i].size = strlen (priv->advertised_protocols[i]); + protocols[i].data = g_memdup (priv->advertised_protocols[i], protocols[i].size); + } + gnutls_alpn_set_protocols (priv->session, protocols, n_protos, 0); + g_free (protocols); + } +#endif + +#if ENABLE(TIZEN_PERFORMANCE_TEST_LOG) + HWCLOCK_LOG("[END] gnutls_verify_peer"); +#endif + G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->begin_handshake (gnutls); } +#if GLIB_CHECK_VERSION(2, 60, 0) +static void +update_negotiated_protocol (GTlsConnectionGnutls *gnutls) +{ + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + gchar *orig_negotiated_protocol; + gnutls_datum_t protocol; + + /* + * Preserve the prior negotiated protocol before clearing it + */ + orig_negotiated_protocol = g_steal_pointer (&priv->negotiated_protocol); + + + if (gnutls_alpn_get_selected_protocol (priv->session, &protocol) == 0 && protocol.size > 0) + priv->negotiated_protocol = g_strndup ((gchar *)protocol.data, protocol.size); + + /* + * Notify only if the negotiated protocol changed + */ + if (g_strcmp0 (orig_negotiated_protocol, priv->negotiated_protocol) != 0) + g_object_notify (G_OBJECT (gnutls), "negotiated-protocol"); + + g_free (orig_negotiated_protocol); +} +#endif + static gboolean finish_handshake (GTlsConnectionGnutls *gnutls, - GTask *task, - GError **error) + GTask *task, + GError **error) { - GTlsCertificate *peer_certificate; - GTlsCertificateFlags peer_certificate_errors; - + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); g_assert (error != NULL); - if (g_task_propagate_boolean (task, error) && - gnutls_certificate_type_get (gnutls->priv->session) == GNUTLS_CRT_X509) - peer_certificate = get_peer_certificate_from_session (gnutls); - else - peer_certificate = NULL; - - if (peer_certificate) + if (gnutls_session_is_resumed (priv->session)) { - peer_certificate_errors = verify_peer_certificate (gnutls, peer_certificate); - if (!accept_peer_certificate (gnutls, peer_certificate, - peer_certificate_errors)) - { - g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE, - _("Unacceptable TLS certificate")); - } - - gnutls->priv->peer_certificate = peer_certificate; - gnutls->priv->peer_certificate_errors = peer_certificate_errors; - g_object_notify (G_OBJECT (gnutls), "peer-certificate"); - g_object_notify (G_OBJECT (gnutls), "peer-certificate-errors"); + /* 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 (gnutls); + priv->peer_certificate_examined = TRUE; + priv->peer_certificate_accepted = TRUE; + g_mutex_unlock (&priv->verify_certificate_mutex); } - else if (error && !*error && G_IS_TLS_CLIENT_CONNECTION (gnutls)) + + if (g_task_propagate_boolean (task, error) && + priv->peer_certificate && !priv->peer_certificate_accepted) { g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE, - _("Server did not return a valid TLS certificate")); + _("Unacceptable TLS certificate")); } - G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->finish_handshake (gnutls, error); +#if GLIB_CHECK_VERSION(2, 60, 0) + if (!*error && priv->advertised_protocols) + update_negotiated_protocol (gnutls); +#endif - if (*error && gnutls->priv->started_handshake) - gnutls->priv->handshake_error = g_error_copy (*error); + if (*error && priv->started_handshake) + priv->handshake_error = g_error_copy (*error); return (*error == NULL); } +static void +sync_handshake_thread_completed (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + g_assert (g_main_context_is_owner (priv->handshake_context)); + + g_mutex_lock (&priv->op_mutex); + priv->sync_handshake_completed = TRUE; + g_mutex_unlock (&priv->op_mutex); + + g_main_context_wakeup (priv->handshake_context); +} + +static void +crank_sync_handshake_context (GTlsConnectionGnutls *gnutls, + GCancellable *cancellable) +{ + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + /* need_finish_handshake will be set inside sync_handshake_thread_completed(), + * which should only ever be invoked while iterating the handshake context + * here. So need_finish_handshake should only change on this thread. + */ + g_mutex_lock (&priv->op_mutex); + priv->sync_handshake_completed = FALSE; + while (!priv->sync_handshake_completed && !g_cancellable_is_cancelled (cancellable)) + { + g_mutex_unlock (&priv->op_mutex); + g_main_context_iteration (priv->handshake_context, TRUE); + g_mutex_lock (&priv->op_mutex); + } + g_mutex_unlock (&priv->op_mutex); +} + static gboolean g_tls_connection_gnutls_handshake (GTlsConnection *conn, - GCancellable *cancellable, - GError **error) + GCancellable *cancellable, + GError **error) { GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); GTask *task; gboolean success; + gint64 *timeout = NULL; GError *my_error = NULL; - task = g_task_new (conn, cancellable, NULL, NULL); + g_assert (priv->handshake_context == NULL); + priv->handshake_context = g_main_context_new (); + + g_main_context_push_thread_default (priv->handshake_context); + begin_handshake (gnutls); - g_task_run_in_thread_sync (task, handshake_thread); + + task = g_task_new (conn, cancellable, sync_handshake_thread_completed, NULL); + g_task_set_source_tag (task, g_tls_connection_gnutls_handshake); + g_task_set_return_on_cancel (task, TRUE); + + timeout = g_new0 (gint64, 1); + *timeout = -1; /* blocking */ + g_task_set_task_data (task, timeout, g_free); + + g_task_run_in_thread (task, handshake_thread); + crank_sync_handshake_context (gnutls, cancellable); + success = finish_handshake (gnutls, task, &my_error); + + g_main_context_pop_thread_default (priv->handshake_context); + g_clear_pointer (&priv->handshake_context, g_main_context_unref); g_object_unref (task); yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE); @@ -1274,6 +2280,15 @@ g_tls_connection_gnutls_handshake (GTlsConnection *conn, return success; } +static gboolean +g_tls_connection_gnutls_dtls_handshake (GDtlsConnection *conn, + GCancellable *cancellable, + GError **error) +{ + return g_tls_connection_gnutls_handshake (G_TLS_CONNECTION (conn), + cancellable, error); +} + /* In the async version we use two GTasks; one to run handshake_thread() and * then call handshake_thread_completed(), and a second to call the caller's * original callback after we call finish_handshake(). @@ -1281,49 +2296,102 @@ g_tls_connection_gnutls_handshake (GTlsConnection *conn, static void handshake_thread_completed (GObject *object, - GAsyncResult *result, - gpointer user_data) + GAsyncResult *result, + gpointer user_data) { GTask *caller_task = user_data; GTlsConnectionGnutls *gnutls = g_task_get_source_object (caller_task); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); GError *error = NULL; - gboolean success; + gboolean need_finish_handshake, success; + + g_mutex_lock (&priv->op_mutex); + if (priv->need_finish_handshake) + { + need_finish_handshake = TRUE; + priv->need_finish_handshake = FALSE; + } + else + need_finish_handshake = FALSE; + g_mutex_unlock (&priv->op_mutex); + + if (need_finish_handshake) + { + success = finish_handshake (gnutls, G_TASK (result), &error); + if (success) + g_task_return_boolean (caller_task, TRUE); + else + g_task_return_error (caller_task, error); + } + else if (priv->handshake_error) + g_task_return_error (caller_task, g_error_copy (priv->handshake_error)); + else + g_task_return_boolean (caller_task, TRUE); + + g_clear_pointer (&priv->handshake_context, g_main_context_unref); + g_object_unref (caller_task); +} + +static void +async_handshake_thread (GTask *task, + gpointer object, + gpointer task_data, + GCancellable *cancellable) +{ + GTlsConnectionGnutls *gnutls = object; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + handshake_thread (task, object, task_data, cancellable); - success = finish_handshake (gnutls, G_TASK (result), &error); - yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE); + g_mutex_lock (&priv->op_mutex); + priv->need_finish_handshake = TRUE; + /* yield_op will clear handshaking too, but we don't want the + * connection to be briefly "handshaking && need_finish_handshake" + * after we unlock the mutex. + */ + priv->handshaking = FALSE; + g_mutex_unlock (&priv->op_mutex); - if (success) - g_task_return_boolean (caller_task, TRUE); - else - g_task_return_error (caller_task, error); - g_object_unref (caller_task); + yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE); } static void g_tls_connection_gnutls_handshake_async (GTlsConnection *conn, - int io_priority, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (G_TLS_CONNECTION_GNUTLS (conn)); GTask *thread_task, *caller_task; + gint64 *timeout = NULL; + + g_assert (!priv->handshake_context); + priv->handshake_context = g_main_context_ref_thread_default (); caller_task = g_task_new (conn, cancellable, callback, user_data); + g_task_set_source_tag (caller_task, g_tls_connection_gnutls_handshake_async); g_task_set_priority (caller_task, io_priority); begin_handshake (G_TLS_CONNECTION_GNUTLS (conn)); thread_task = g_task_new (conn, cancellable, - handshake_thread_completed, caller_task); + handshake_thread_completed, caller_task); + g_task_set_source_tag (thread_task, g_tls_connection_gnutls_handshake_async); g_task_set_priority (thread_task, io_priority); - g_task_run_in_thread (thread_task, handshake_thread); + + 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); } static gboolean g_tls_connection_gnutls_handshake_finish (GTlsConnection *conn, - GAsyncResult *result, - GError **error) + GAsyncResult *result, + GError **error) { g_return_val_if_fail (g_task_is_valid (result, conn), FALSE); @@ -1331,82 +2399,235 @@ g_tls_connection_gnutls_handshake_finish (GTlsConnection *conn, } static void -implicit_handshake_completed (GObject *object, - GAsyncResult *result, - gpointer user_data) +g_tls_connection_gnutls_dtls_handshake_async (GDtlsConnection *conn, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object); - - g_mutex_lock (&gnutls->priv->op_mutex); - gnutls->priv->need_finish_handshake = TRUE; - g_mutex_unlock (&gnutls->priv->op_mutex); + g_tls_connection_gnutls_handshake_async (G_TLS_CONNECTION (conn), io_priority, + cancellable, callback, user_data); +} - yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE); +static gboolean +g_tls_connection_gnutls_dtls_handshake_finish (GDtlsConnection *conn, + GAsyncResult *result, + GError **error) +{ + return g_tls_connection_gnutls_handshake_finish (G_TLS_CONNECTION (conn), + result, error); } static gboolean do_implicit_handshake (GTlsConnectionGnutls *gnutls, - gboolean blocking, - GCancellable *cancellable, - GError **error) + gint64 timeout, + GCancellable *cancellable, + GError **error) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + gint64 *thread_timeout = NULL; + /* We have op_mutex */ - gnutls->priv->implicit_handshake = g_task_new (gnutls, cancellable, - implicit_handshake_completed, - NULL); + g_assert (priv->handshake_context == NULL); + if (timeout != 0) + { + priv->handshake_context = g_main_context_new (); + g_main_context_push_thread_default (priv->handshake_context); + } + else + { + priv->handshake_context = g_main_context_ref_thread_default (); + } + + g_assert (priv->implicit_handshake == NULL); + priv->implicit_handshake = g_task_new (gnutls, cancellable, + timeout ? sync_handshake_thread_completed : NULL, + NULL); + g_task_set_source_tag (priv->implicit_handshake, + do_implicit_handshake); + + thread_timeout = g_new0 (gint64, 1); + g_task_set_task_data (priv->implicit_handshake, + thread_timeout, g_free); begin_handshake (gnutls); - if (blocking) + if (timeout != 0) { GError *my_error = NULL; gboolean success; - g_mutex_unlock (&gnutls->priv->op_mutex); - g_task_run_in_thread_sync (gnutls->priv->implicit_handshake, - handshake_thread); + /* 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_mutex_unlock (&priv->op_mutex); + + g_task_set_return_on_cancel (priv->implicit_handshake, TRUE); + g_task_run_in_thread (priv->implicit_handshake, handshake_thread); + + crank_sync_handshake_context (gnutls, cancellable); + success = finish_handshake (gnutls, - gnutls->priv->implicit_handshake, - &my_error); - g_clear_object (&gnutls->priv->implicit_handshake); + priv->implicit_handshake, + &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 (gnutls, G_TLS_CONNECTION_GNUTLS_OP_HANDSHAKE); - g_mutex_lock (&gnutls->priv->op_mutex); + + g_mutex_lock (&priv->op_mutex); if (my_error) - g_propagate_error (error, my_error); + g_propagate_error (error, my_error); return success; } else { - g_task_run_in_thread (gnutls->priv->implicit_handshake, - handshake_thread); - - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, - _("Operation would block")); - + /* 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; } } gssize g_tls_connection_gnutls_read (GTlsConnectionGnutls *gnutls, - void *buffer, - gsize count, - gboolean blocking, - GCancellable *cancellable, - GError **error) + void *buffer, + gsize count, + gint64 timeout, + GCancellable *cancellable, + GError **error) +{ + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + gssize ret; + + if (priv->app_data_buf && !priv->handshaking) + { + ret = MIN (count, priv->app_data_buf->len); + memcpy (buffer, priv->app_data_buf->data, ret); + if (ret == 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, ret); + return ret; + } + + again: + if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ, + timeout, cancellable, error)) + return -1; + + 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, _("Error reading data from TLS socket"), error); + + yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ); + + if (ret >= 0) + return ret; + else if (ret == GNUTLS_E_REHANDSHAKE) + goto again; + else + return -1; +} + +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 gssize +g_tls_connection_gnutls_read_message (GTlsConnectionGnutls *gnutls, + GInputVector *vectors, + guint num_vectors, + gint64 timeout, + GCancellable *cancellable, + GError **error) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + guint i; gssize ret; + gnutls_packet_t packet = { 0, }; + + /* Copy data out of the app data buffer first. */ + if (priv->app_data_buf && !priv->handshaking) + { + ret = 0; + + for (i = 0; i < num_vectors; i++) + { + gsize count; + GInputVector *vec = &vectors[i]; + + count = MIN (vec->size, priv->app_data_buf->len); + ret += 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); + } + + return ret; + } again: if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ, - blocking, cancellable, error)) + timeout, cancellable, error)) return -1; - BEGIN_GNUTLS_IO (gnutls, G_IO_IN, blocking, cancellable); - ret = gnutls_record_recv (gnutls->priv->session, buffer, count); - END_GNUTLS_IO (gnutls, G_IO_IN, ret, _("Error reading data from TLS socket: %s"), error); + 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, _("Error reading data from TLS socket"), error); yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_READ); @@ -1418,25 +2639,176 @@ g_tls_connection_gnutls_read (GTlsConnectionGnutls *gnutls, return -1; } +static gint +g_tls_connection_gnutls_receive_messages (GDatagramBased *datagram_based, + GInputMessage *messages, + guint num_messages, + gint flags, + gint64 timeout, + GCancellable *cancellable, + GError **error) +{ + GTlsConnectionGnutls *gnutls; + guint i; + GError *child_error = NULL; + + gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based); + + if (flags != G_SOCKET_MSG_NONE) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Receive flags are not supported")); + return -1; + } + + for (i = 0; i < num_messages && child_error == NULL; i++) + { + GInputMessage *message = &messages[i]; + gssize n_bytes_read; + + n_bytes_read = g_tls_connection_gnutls_read_message (gnutls, + message->vectors, + message->num_vectors, + timeout, + cancellable, + &child_error); + + if (message->address != NULL) + *message->address = NULL; + message->flags = G_SOCKET_MSG_NONE; + if (message->control_messages != NULL) + *message->control_messages = NULL; + message->num_control_messages = 0; + + if (n_bytes_read > 0) + { + message->bytes_received = n_bytes_read; + } + else if (n_bytes_read == 0) + { + /* EOS. */ + break; + } + else if (i > 0 && + (g_error_matches (child_error, + G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) || + g_error_matches (child_error, + G_IO_ERROR, G_IO_ERROR_TIMED_OUT))) + { + /* Blocked or timed out after receiving some messages successfully. */ + g_clear_error (&child_error); + break; + } + else + { + /* Error, including G_IO_ERROR_WOULD_BLOCK or G_IO_ERROR_TIMED_OUT on + * the first message; or G_IO_ERROR_CANCELLED at any time. */ + break; + } + } + + if (child_error != NULL) + { + g_propagate_error (error, child_error); + return -1; + } + + return i; +} + gssize g_tls_connection_gnutls_write (GTlsConnectionGnutls *gnutls, - const void *buffer, - gsize count, - gboolean blocking, - GCancellable *cancellable, - GError **error) + const void *buffer, + gsize count, + gint64 timeout, + GCancellable *cancellable, + GError **error) +{ + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + gssize ret; + + again: + if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE, + timeout, cancellable, error)) + return -1; + + 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, _("Error writing data to TLS socket"), error); + + yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE); + + if (ret >= 0) + return ret; + else if (ret == GNUTLS_E_REHANDSHAKE) + goto again; + else + return -1; +} + +static gssize +g_tls_connection_gnutls_write_message (GTlsConnectionGnutls *gnutls, + GOutputVector *vectors, + guint num_vectors, + gint64 timeout, + GCancellable *cancellable, + GError **error) { + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); gssize ret; + guint i; + gsize total_message_size; again: if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE, - blocking, cancellable, error)) + timeout, cancellable, error)) return -1; - BEGIN_GNUTLS_IO (gnutls, G_IO_OUT, blocking, cancellable); - ret = gnutls_record_send (gnutls->priv->session, buffer, count); - END_GNUTLS_IO (gnutls, G_IO_OUT, ret, _("Error writing data to TLS socket: %s"), error); + /* 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 (priv->base_socket != NULL && + 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); + + goto done; + } + + /* 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, _("Error writing data to TLS socket"), error); + done: yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_WRITE); if (ret >= 0) @@ -1447,63 +2819,208 @@ g_tls_connection_gnutls_write (GTlsConnectionGnutls *gnutls, return -1; } +static gint +g_tls_connection_gnutls_send_messages (GDatagramBased *datagram_based, + GOutputMessage *messages, + guint num_messages, + gint flags, + gint64 timeout, + GCancellable *cancellable, + GError **error) +{ + GTlsConnectionGnutls *gnutls; + guint i; + GError *child_error = NULL; + + gnutls = G_TLS_CONNECTION_GNUTLS (datagram_based); + + if (flags != G_SOCKET_MSG_NONE) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Send flags are not supported")); + return -1; + } + + for (i = 0; i < num_messages && child_error == NULL; i++) + { + GOutputMessage *message = &messages[i]; + gssize n_bytes_sent; + + n_bytes_sent = g_tls_connection_gnutls_write_message (gnutls, + message->vectors, + message->num_vectors, + timeout, + cancellable, + &child_error); + + if (n_bytes_sent >= 0) + { + message->bytes_sent = n_bytes_sent; + } + else if (i > 0 && + (g_error_matches (child_error, + G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) || + g_error_matches (child_error, + G_IO_ERROR, G_IO_ERROR_TIMED_OUT))) + { + /* Blocked or timed out after sending some messages successfully. */ + g_clear_error (&child_error); + break; + } + else + { + /* Error, including G_IO_ERROR_WOULD_BLOCK or G_IO_ERROR_TIMED_OUT + * on the first message; or G_IO_ERROR_CANCELLED at any time. */ + break; + } + } + + if (child_error != NULL) + { + g_propagate_error (error, child_error); + return -1; + } + + return i; +} + static GInputStream * g_tls_connection_gnutls_get_input_stream (GIOStream *stream) { GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); - return gnutls->priv->tls_istream; + return priv->tls_istream; } static GOutputStream * g_tls_connection_gnutls_get_output_stream (GIOStream *stream) { GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); - return gnutls->priv->tls_ostream; + return priv->tls_ostream; } -static gboolean -g_tls_connection_gnutls_close (GIOStream *stream, - GCancellable *cancellable, - GError **error) +gboolean +g_tls_connection_gnutls_close_internal (GIOStream *stream, + GTlsDirection direction, + gint64 timeout, + GCancellable *cancellable, + GError **error) { GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (stream); - gboolean success; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + GTlsConnectionGnutlsOp op; + gboolean success = TRUE; int ret = 0; + GError *gnutls_error = NULL, *stream_error = NULL; - if (!claim_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_CLOSE, - TRUE, cancellable, error)) + /* This can be called from g_io_stream_close(), g_input_stream_close(), + * g_output_stream_close() or g_tls_connection_close(). In all cases, we only + * do the gnutls_bye() for writing. The difference is how we set the flags on + * this class and how the underlying stream is closed. + */ + + g_return_val_if_fail (direction != G_TLS_DIRECTION_NONE, FALSE); + + if (direction == G_TLS_DIRECTION_BOTH) + op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_BOTH; + else if (direction == G_TLS_DIRECTION_READ) + op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_READ; + else + op = G_TLS_CONNECTION_GNUTLS_OP_CLOSE_WRITE; + + if (!claim_op (gnutls, op, timeout, cancellable, error)) return FALSE; - if (gnutls->priv->closed) + if (priv->ever_handshaked && !priv->write_closed && + direction & G_TLS_DIRECTION_WRITE) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED, - _("Connection is already closed")); - yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_CLOSE); - return FALSE; + 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, + _("Error performing TLS close"), &gnutls_error); + + priv->write_closed = TRUE; } - if (gnutls->priv->ever_handshaked) + if (!priv->read_closed && direction & G_TLS_DIRECTION_READ) + priv->read_closed = TRUE; + + /* Close the underlying streams. Do this even if the gnutls_bye() call failed, + * as the parent GIOStream will have set its internal closed flag and hence + * this implementation will never be called again. */ + if (priv->base_io_stream != NULL) { - BEGIN_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, TRUE, cancellable); - ret = gnutls_bye (gnutls->priv->session, GNUTLS_SHUT_WR); - END_GNUTLS_IO (gnutls, G_IO_IN | G_IO_OUT, ret, - _("Error performing TLS close: %s"), error); + if (direction == G_TLS_DIRECTION_BOTH) + success = g_io_stream_close (priv->base_io_stream, + cancellable, &stream_error); + else if (direction & G_TLS_DIRECTION_READ) + success = g_input_stream_close (g_io_stream_get_input_stream (priv->base_io_stream), + cancellable, &stream_error); + else if (direction & G_TLS_DIRECTION_WRITE) + success = g_output_stream_close (g_io_stream_get_output_stream (priv->base_io_stream), + cancellable, &stream_error); + } + else if (g_tls_connection_gnutls_is_dtls (gnutls)) + { + /* We do not close underlying #GDatagramBaseds. There is no + * g_datagram_based_close() method since different datagram-based + * protocols vary wildly in how they close. */ + success = TRUE; + } + else + { + g_assert_not_reached (); } - gnutls->priv->closed = TRUE; + yield_op (gnutls, op); + /* Propagate errors. */ if (ret != 0) { - yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_CLOSE); - return FALSE; + g_propagate_error (error, gnutls_error); + g_clear_error (&stream_error); + } + else if (!success) + { + g_propagate_error (error, stream_error); + g_clear_error (&gnutls_error); } - success = g_io_stream_close (gnutls->priv->base_io_stream, - cancellable, error); - yield_op (gnutls, G_TLS_CONNECTION_GNUTLS_OP_CLOSE); - return success; + return success && (ret == 0); +} + +static gboolean +g_tls_connection_gnutls_close (GIOStream *stream, + GCancellable *cancellable, + GError **error) +{ + return g_tls_connection_gnutls_close_internal (stream, + G_TLS_DIRECTION_BOTH, + -1, /* blocking */ + cancellable, error); +} + +static gboolean +g_tls_connection_gnutls_dtls_shutdown (GDtlsConnection *conn, + gboolean shutdown_read, + gboolean shutdown_write, + GCancellable *cancellable, + GError **error) +{ + GTlsDirection direction = G_TLS_DIRECTION_NONE; + + if (shutdown_read) + direction |= G_TLS_DIRECTION_READ; + if (shutdown_write) + direction |= G_TLS_DIRECTION_WRITE; + + return g_tls_connection_gnutls_close_internal (G_IO_STREAM (conn), + direction, + -1, /* blocking */ + cancellable, error); } /* We do async close as synchronous-in-a-thread so we don't need to @@ -1512,95 +3029,112 @@ g_tls_connection_gnutls_close (GIOStream *stream, */ static void close_thread (GTask *task, - gpointer object, - gpointer task_data, - GCancellable *cancellable) + gpointer object, + gpointer task_data, + GCancellable *cancellable) { GIOStream *stream = object; + GTlsDirection direction; GError *error = NULL; - if (!g_tls_connection_gnutls_close (stream, cancellable, &error)) + direction = GPOINTER_TO_INT (g_task_get_task_data (task)); + + if (!g_tls_connection_gnutls_close_internal (stream, direction, + -1, /* blocking */ + cancellable, &error)) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); } static void -g_tls_connection_gnutls_close_async (GIOStream *stream, - int io_priority, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +g_tls_connection_gnutls_close_internal_async (GIOStream *stream, + GTlsDirection direction, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GTask *task; task = g_task_new (stream, cancellable, callback, user_data); + g_task_set_source_tag (task, g_tls_connection_gnutls_close_internal_async); g_task_set_priority (task, io_priority); + g_task_set_task_data (task, GINT_TO_POINTER (direction), NULL); g_task_run_in_thread (task, close_thread); g_object_unref (task); } +static void +g_tls_connection_gnutls_close_async (GIOStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_tls_connection_gnutls_close_internal_async (stream, G_TLS_DIRECTION_BOTH, + io_priority, cancellable, + callback, user_data); +} + static gboolean g_tls_connection_gnutls_close_finish (GIOStream *stream, - GAsyncResult *result, - GError **error) + GAsyncResult *result, + GError **error) { g_return_val_if_fail (g_task_is_valid (result, stream), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } -#ifdef HAVE_PKCS11 - -static P11KitPin* -on_pin_prompt_callback (const char *pinfile, - P11KitUri *pin_uri, - const char *pin_description, - P11KitPinFlags pin_flags, - void *callback_data) +static void +g_tls_connection_gnutls_dtls_shutdown_async (GDtlsConnection *conn, + gboolean shutdown_read, + gboolean shutdown_write, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (callback_data); - GTlsInteractionResult result; - GTlsPasswordFlags flags = 0; - GTlsPassword *password; - P11KitPin *pin = NULL; - GError *error = NULL; - - if (!gnutls->priv->interaction) - return NULL; + GTlsDirection direction = G_TLS_DIRECTION_NONE; - if (pin_flags & P11_KIT_PIN_FLAGS_RETRY) - flags |= G_TLS_PASSWORD_RETRY; - if (pin_flags & P11_KIT_PIN_FLAGS_MANY_TRIES) - flags |= G_TLS_PASSWORD_MANY_TRIES; - if (pin_flags & P11_KIT_PIN_FLAGS_FINAL_TRY) - flags |= G_TLS_PASSWORD_FINAL_TRY; + if (shutdown_read) + direction |= G_TLS_DIRECTION_READ; + if (shutdown_write) + direction |= G_TLS_DIRECTION_WRITE; - password = g_pkcs11_pin_new (flags, pin_description); + g_tls_connection_gnutls_close_internal_async (G_IO_STREAM (conn), direction, + io_priority, cancellable, + callback, user_data); +} - result = g_tls_interaction_ask_password (gnutls->priv->interaction, password, - g_cancellable_get_current (), &error); +static gboolean +g_tls_connection_gnutls_dtls_shutdown_finish (GDtlsConnection *conn, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, conn), FALSE); - switch (result) - { - case G_TLS_INTERACTION_FAILED: - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("couldn't ask for password: %s", error->message); - pin = NULL; - break; - case G_TLS_INTERACTION_UNHANDLED: - pin = NULL; - break; - case G_TLS_INTERACTION_HANDLED: - pin = g_pkcs11_pin_steal_internal (G_PKCS11_PIN (password)); - break; - } + return g_task_propagate_boolean (G_TASK (result), error); +} - g_object_unref (password); - return pin; +#if GLIB_CHECK_VERSION(2, 60, 0) +static void +g_tls_connection_gnutls_dtls_set_advertised_protocols (GDtlsConnection *conn, + const gchar * const *protocols) +{ + g_object_set (conn, "advertised-protocols", protocols, NULL); } -#endif /* HAVE_PKCS11 */ +const gchar * +g_tls_connection_gnutls_dtls_get_negotiated_protocol (GDtlsConnection *conn) +{ + GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (conn); + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + + return priv->negotiated_protocol; +} +#endif static void g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass) @@ -1609,8 +3143,6 @@ g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass) GTlsConnectionClass *connection_class = G_TLS_CONNECTION_CLASS (klass); GIOStreamClass *iostream_class = G_IO_STREAM_CLASS (klass); - g_type_class_add_private (klass, sizeof (GTlsConnectionGnutlsPrivate)); - gobject_class->get_property = g_tls_connection_gnutls_get_property; gobject_class->set_property = g_tls_connection_gnutls_set_property; gobject_class->finalize = g_tls_connection_gnutls_finalize; @@ -1625,7 +3157,9 @@ g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass) iostream_class->close_async = g_tls_connection_gnutls_close_async; iostream_class->close_finish = g_tls_connection_gnutls_close_finish; + /* 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"); g_object_class_override_property (gobject_class, PROP_REQUIRE_CLOSE_NOTIFY, "require-close-notify"); g_object_class_override_property (gobject_class, PROP_REHANDSHAKE_MODE, "rehandshake-mode"); g_object_class_override_property (gobject_class, PROP_USE_SYSTEM_CERTDB, "use-system-certdb"); @@ -1634,6 +3168,10 @@ g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass) g_object_class_override_property (gobject_class, PROP_INTERACTION, "interaction"); g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE, "peer-certificate"); g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE_ERRORS, "peer-certificate-errors"); +#if GLIB_CHECK_VERSION(2, 60, 0) + g_object_class_override_property (gobject_class, PROP_ADVERTISED_PROTOCOLS, "advertised-protocols"); + g_object_class_override_property (gobject_class, PROP_NEGOTIATED_PROTOCOL, "negotiated-protocol"); +#endif } static void @@ -1641,3 +3179,81 @@ g_tls_connection_gnutls_initable_iface_init (GInitableIface *iface) { iface->init = g_tls_connection_gnutls_initable_init; } + +static void +g_tls_connection_gnutls_dtls_connection_iface_init (GDtlsConnectionInterface *iface) +{ + iface->handshake = g_tls_connection_gnutls_dtls_handshake; + iface->handshake_async = g_tls_connection_gnutls_dtls_handshake_async; + iface->handshake_finish = g_tls_connection_gnutls_dtls_handshake_finish; + iface->shutdown = g_tls_connection_gnutls_dtls_shutdown; + iface->shutdown_async = g_tls_connection_gnutls_dtls_shutdown_async; + iface->shutdown_finish = g_tls_connection_gnutls_dtls_shutdown_finish; +#if GLIB_CHECK_VERSION(2, 60, 0) + iface->set_advertised_protocols = g_tls_connection_gnutls_dtls_set_advertised_protocols; + iface->get_negotiated_protocol = g_tls_connection_gnutls_dtls_get_negotiated_protocol; +#endif +} + +static void +g_tls_connection_gnutls_datagram_based_iface_init (GDatagramBasedInterface *iface) +{ + iface->receive_messages = g_tls_connection_gnutls_receive_messages; + iface->send_messages = g_tls_connection_gnutls_send_messages; + iface->create_source = g_tls_connection_gnutls_dtls_create_source; + iface->condition_check = g_tls_connection_gnutls_condition_check; + iface->condition_wait = g_tls_connection_gnutls_condition_wait; +} + +gboolean +g_tls_connection_gnutls_request_certificate (GTlsConnectionGnutls *gnutls, + GError **error) +{ + GTlsInteractionResult res = G_TLS_INTERACTION_UNHANDLED; + GTlsConnectionGnutlsPrivate *priv = g_tls_connection_gnutls_get_instance_private (gnutls); + GTlsInteraction *interaction; + GTlsConnection *conn; + + g_return_val_if_fail (G_IS_TLS_CONNECTION_GNUTLS (gnutls), FALSE); + + conn = G_TLS_CONNECTION (gnutls); + + interaction = g_tls_connection_get_interaction (conn); + if (!interaction) + return FALSE; + + res = g_tls_interaction_invoke_request_certificate (interaction, conn, 0, + priv->read_cancellable, error); + return res != G_TLS_INTERACTION_FAILED; +} + +void +GTLS_DEBUG (gpointer gnutls, + const char *message, + ...) +{ + char *result = NULL; + int ret; + + g_assert (G_IS_TLS_CONNECTION (gnutls)); + + va_list args; + va_start (args, message); + + ret = g_vasprintf (&result, message, args); + g_assert (ret > 0); + + if (G_IS_TLS_CLIENT_CONNECTION (gnutls)) + g_printf ("CLIENT %p: ", gnutls); + else if (G_IS_TLS_SERVER_CONNECTION (gnutls)) + g_printf ("SERVER %p: ", gnutls); + else + g_assert_not_reached (); + + g_printf ("%s\n", result); + + fflush (stdout); + + g_free (result); + va_end (args); +}