[glib-networking] openssl: add support for DTLS



commit 852e5c2140f4f14275f24ff09adfd1d10014fc04
Author: Ole André Vadla Ravnås <oleavr gmail com>
Date:   Wed May 26 09:39:16 2021 +0200

    openssl: add support for DTLS

 tls/openssl/gtlsbackend-openssl.c          |   2 +
 tls/openssl/gtlsbio.c                      | 225 ++++++++++++-----
 tls/openssl/gtlsbio.h                      |  13 +-
 tls/openssl/gtlsclientconnection-openssl.c |  13 +-
 tls/openssl/gtlsconnection-openssl.c       | 381 ++++++++++++++++++++---------
 tls/openssl/gtlsserverconnection-openssl.c |  13 +-
 tls/openssl/openssl-include.h              |  11 +-
 tls/tests/dtls-connection.c                |   2 +
 tls/tests/meson.build                      |   5 -
 9 files changed, 466 insertions(+), 199 deletions(-)
---
diff --git a/tls/openssl/gtlsbackend-openssl.c b/tls/openssl/gtlsbackend-openssl.c
index 87248cc..23cd8de 100644
--- a/tls/openssl/gtlsbackend-openssl.c
+++ b/tls/openssl/gtlsbackend-openssl.c
@@ -239,6 +239,8 @@ g_tls_backend_openssl_interface_init (GTlsBackendInterface *iface)
   iface->get_server_connection_type = g_tls_server_connection_openssl_get_type;
   iface->get_file_database_type = g_tls_file_database_openssl_get_type;
   iface->get_default_database = g_tls_backend_openssl_get_default_database;
+  iface->get_dtls_client_connection_type = g_tls_client_connection_openssl_get_type;
+  iface->get_dtls_server_connection_type = g_tls_server_connection_openssl_get_type;
 }
 
 void
diff --git a/tls/openssl/gtlsbio.c b/tls/openssl/gtlsbio.c
index ad1efb5..4e138e7 100644
--- a/tls/openssl/gtlsbio.c
+++ b/tls/openssl/gtlsbio.c
@@ -29,24 +29,27 @@
 
 typedef struct {
   GIOStream *io_stream;
+  GDatagramBased *socket;
   GCancellable *read_cancellable;
   GCancellable *write_cancellable;
-  gboolean read_blocking;
-  gboolean write_blocking;
   GError **read_error;
   GError **write_error;
-  GMainContext *context;
-  GMainLoop *loop;
 } GTlsBio;
 
+typedef struct {
+  gboolean done;
+  gboolean timed_out;
+} WaitData;
+
 static void
 free_gbio (gpointer user_data)
 {
   GTlsBio *bio = (GTlsBio *)user_data;
 
-  g_object_unref (bio->io_stream);
-  g_main_context_unref (bio->context);
-  g_main_loop_unref (bio->loop);
+  if (bio->io_stream)
+    g_object_unref (bio->io_stream);
+  else
+    g_object_unref (bio->socket);
   g_free (bio);
 }
 
@@ -131,6 +134,9 @@ gtls_bio_ctrl (BIO  *b,
     case BIO_CTRL_POP:
       ret = 0;
       break;
+    case BIO_CTRL_DGRAM_QUERY_MTU:
+      ret = 1400; /* Same as the GnuTLS backend */
+      break;
     default:
       g_debug ("Got unsupported command: %d", cmd);
       ret = 0;
@@ -165,11 +171,28 @@ gtls_bio_write (BIO        *bio,
 #endif
 
   BIO_clear_retry_flags (bio);
-  written = g_pollable_stream_write (g_io_stream_get_output_stream (gbio->io_stream),
-                                     in, inl,
-                                     gbio->write_blocking,
-                                     gbio->write_cancellable,
-                                     &error);
+  if (gbio->io_stream)
+    {
+      written = g_pollable_stream_write (g_io_stream_get_output_stream (gbio->io_stream),
+                                         in, inl,
+                                         FALSE,
+                                         gbio->write_cancellable,
+                                         &error);
+    }
+  else
+    {
+      GOutputVector vector = { in, inl };
+      GOutputMessage message = { NULL, &vector, 1, 0, NULL, 0 };
+
+      written = g_datagram_based_send_messages (gbio->socket,
+                                                &message, 1, 0,
+                                                0,
+                                                gbio->write_cancellable,
+                                                &error);
+
+      if (written > 0)
+        written = message.bytes_sent;
+    }
 
   if (written == -1)
     {
@@ -208,11 +231,28 @@ gtls_bio_read (BIO  *bio,
 #endif
 
   BIO_clear_retry_flags (bio);
-  read = g_pollable_stream_read (g_io_stream_get_input_stream (gbio->io_stream),
-                                 out, outl,
-                                 gbio->read_blocking,
-                                 gbio->read_cancellable,
-                                 &error);
+  if (gbio->io_stream)
+    {
+      read = g_pollable_stream_read (g_io_stream_get_input_stream (gbio->io_stream),
+                                     out, outl,
+                                     FALSE,
+                                     gbio->read_cancellable,
+                                     &error);
+    }
+  else
+    {
+      GInputVector vector = { out, outl };
+      GInputMessage message = { NULL, &vector, 1, 0, 0, NULL, NULL };
+
+      read = g_datagram_based_receive_messages (gbio->socket,
+                                                &message, 1, 0,
+                                                0,
+                                                gbio->read_cancellable,
+                                                &error);
+
+      if (read > 0)
+        read = message.bytes_received;
+    }
 
   if (read == -1)
     {
@@ -284,8 +324,8 @@ BIO_s_gtls (void)
 }
 #endif
 
-BIO *
-g_tls_bio_new (GIOStream *io_stream)
+static BIO *
+g_tls_bio_alloc (GTlsBio **out_gbio)
 {
   BIO *ret;
   GTlsBio *gbio;
@@ -295,9 +335,6 @@ g_tls_bio_new (GIOStream *io_stream)
     return NULL;
 
   gbio = g_new0 (GTlsBio, 1);
-  gbio->io_stream = g_object_ref (io_stream);
-  gbio->context = g_main_context_new ();
-  gbio->loop = g_main_loop_new (gbio->context, FALSE);
 
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
   ret->ptr = gbio;
@@ -307,28 +344,37 @@ g_tls_bio_new (GIOStream *io_stream)
   BIO_set_init (ret, 1);
 #endif
 
+  *out_gbio = gbio;
   return ret;
 }
 
-void
-g_tls_bio_set_read_cancellable (BIO          *bio,
-                                GCancellable *cancellable)
+BIO *
+g_tls_bio_new_from_iostream (GIOStream *io_stream)
 {
+  BIO *ret;
   GTlsBio *gbio;
 
-  g_return_if_fail (bio);
+  ret = g_tls_bio_alloc (&gbio);
+  gbio->io_stream = g_object_ref (io_stream);
 
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
-  gbio = (GTlsBio *)bio->ptr;
-#else
-  gbio = BIO_get_data (bio);
-#endif
-  gbio->read_cancellable = cancellable;
+  return ret;
+}
+
+BIO *
+g_tls_bio_new_from_datagram_based (GDatagramBased *socket)
+{
+  BIO *ret;
+  GTlsBio *gbio;
+
+  ret = g_tls_bio_alloc (&gbio);
+  gbio->socket = g_object_ref (socket);
+
+  return ret;
 }
 
 void
-g_tls_bio_set_read_blocking (BIO      *bio,
-                             gboolean  blocking)
+g_tls_bio_set_read_cancellable (BIO          *bio,
+                                GCancellable *cancellable)
 {
   GTlsBio *gbio;
 
@@ -339,7 +385,7 @@ g_tls_bio_set_read_blocking (BIO      *bio,
 #else
   gbio = BIO_get_data (bio);
 #endif
-  gbio->read_blocking = blocking;
+  gbio->read_cancellable = cancellable;
 }
 
 void
@@ -375,8 +421,8 @@ g_tls_bio_set_write_cancellable (BIO          *bio,
 }
 
 void
-g_tls_bio_set_write_blocking (BIO          *bio,
-                              gboolean      blocking)
+g_tls_bio_set_write_error (BIO     *bio,
+                           GError **error)
 {
   GTlsBio *gbio;
 
@@ -387,45 +433,55 @@ g_tls_bio_set_write_blocking (BIO          *bio,
 #else
   gbio = BIO_get_data (bio);
 #endif
-  gbio->write_blocking = blocking;
+  gbio->write_error = error;
 }
 
-void
-g_tls_bio_set_write_error (BIO     *bio,
-                           GError **error)
+static gboolean
+on_pollable_source_ready (GObject *pollable_stream,
+                          gpointer user_data)
 {
-  GTlsBio *gbio;
+  WaitData *wait_data = user_data;
 
-  g_return_if_fail (bio);
+  wait_data->done = TRUE;
 
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
-  gbio = (GTlsBio *)bio->ptr;
-#else
-  gbio = BIO_get_data (bio);
-#endif
-  gbio->write_error = error;
+  return G_SOURCE_REMOVE;
 }
 
 static gboolean
-on_source_ready (GObject *pollable_stream,
-                 gpointer user_data)
+on_datagram_source_ready (GDatagramBased *datagram_based,
+                          GIOCondition condition,
+                          gpointer user_data)
 {
-  GMainLoop *loop = user_data;
+  WaitData *wait_data = user_data;
 
-  g_main_loop_quit (loop);
+  wait_data->done = TRUE;
 
   return G_SOURCE_REMOVE;
 }
 
-void
+static gboolean
+on_timeout_source_ready (gpointer user_data)
+{
+  WaitData *wait_data = user_data;
+
+  wait_data->done = TRUE;
+  wait_data->timed_out = TRUE;
+
+  return G_SOURCE_REMOVE;
+}
+
+gboolean
 g_tls_bio_wait_available (BIO          *bio,
                           GIOCondition  condition,
+                          gint64        timeout,
                           GCancellable *cancellable)
 {
   GTlsBio *gbio;
-  GSource *source;
+  WaitData wait_data;
+  GMainContext *ctx;
+  GSource *io_source, *timeout_source;
 
-  g_return_if_fail (bio);
+  g_return_val_if_fail (bio, FALSE);
 
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
   gbio = (GTlsBio *)bio->ptr;
@@ -433,21 +489,54 @@ g_tls_bio_wait_available (BIO          *bio,
   gbio = BIO_get_data (bio);
 #endif
 
-  g_main_context_push_thread_default (gbio->context);
+  wait_data.done = FALSE;
+  wait_data.timed_out = FALSE;
+
+  ctx = g_main_context_new ();
+  g_main_context_push_thread_default (ctx);
+
+  if (gbio->io_stream)
+    {
+      if (condition & G_IO_IN)
+        io_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM 
(g_io_stream_get_input_stream (gbio->io_stream)),
+                                                           cancellable);
+      else
+        io_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM 
(g_io_stream_get_output_stream (gbio->io_stream)),
+                                                            cancellable);
+      g_source_set_callback (io_source, (GSourceFunc)on_pollable_source_ready, &wait_data, NULL);
+    }
+  else
+    {
+      io_source = g_datagram_based_create_source (gbio->socket, condition, cancellable);
+      g_source_set_callback (io_source, (GSourceFunc)on_datagram_source_ready, &wait_data, NULL);
+    }
+  g_source_attach (io_source, ctx);
 
-  if (condition & G_IO_IN)
-    source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (g_io_stream_get_input_stream 
(gbio->io_stream)),
-                                                    cancellable);
+  if (timeout >= 0)
+    {
+      timeout_source = g_timeout_source_new (timeout / 1000);
+      g_source_set_callback (timeout_source, (GSourceFunc)on_timeout_source_ready, &wait_data, NULL);
+      g_source_attach (timeout_source, ctx);
+    }
   else
-    source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (g_io_stream_get_output_stream 
(gbio->io_stream)),
-                                                     cancellable);
+    {
+      timeout_source = NULL;
+    }
+
+  while (!wait_data.done)
+    g_main_context_iteration (ctx, TRUE);
+
+  if (timeout_source)
+    {
+      g_source_destroy (timeout_source);
+      g_source_unref (timeout_source);
+    }
 
-  g_source_set_callback (source, (GSourceFunc)on_source_ready, gbio->loop, NULL);
-  g_source_attach (source, gbio->context);
+  g_source_destroy (io_source);
+  g_source_unref (io_source);
 
-  g_main_loop_run (gbio->loop);
-  g_main_context_pop_thread_default (gbio->context);
+  g_main_context_pop_thread_default (ctx);
+  g_main_context_unref (ctx);
 
-  g_source_destroy (source);
-  g_source_unref (source);
+  return !wait_data.timed_out;
 }
diff --git a/tls/openssl/gtlsbio.h b/tls/openssl/gtlsbio.h
index 09dccd3..e4135d1 100644
--- a/tls/openssl/gtlsbio.h
+++ b/tls/openssl/gtlsbio.h
@@ -30,28 +30,25 @@
 
 G_BEGIN_DECLS
 
-BIO       *g_tls_bio_new                   (GIOStream    *io_stream);
+BIO       *g_tls_bio_new_from_iostream     (GIOStream *io_stream);
+
+BIO       *g_tls_bio_new_from_datagram_based (GDatagramBased *socket);
 
 void       g_tls_bio_set_read_cancellable  (BIO          *bio,
                                             GCancellable *cancellable);
 
-void       g_tls_bio_set_read_blocking     (BIO          *bio,
-                                            gboolean      blocking);
-
 void       g_tls_bio_set_read_error        (BIO          *bio,
                                             GError      **error);
 
 void       g_tls_bio_set_write_cancellable (BIO          *bio,
                                             GCancellable *cancellable);
 
-void       g_tls_bio_set_write_blocking    (BIO          *bio,
-                                            gboolean      blocking);
-
 void       g_tls_bio_set_write_error       (BIO          *bio,
                                             GError      **error);
 
-void       g_tls_bio_wait_available        (BIO          *bio,
+gboolean   g_tls_bio_wait_available        (BIO          *bio,
                                             GIOCondition  condition,
+                                            gint64        timeout,
                                             GCancellable *cancellable);
 
 G_END_DECLS
diff --git a/tls/openssl/gtlsclientconnection-openssl.c b/tls/openssl/gtlsclientconnection-openssl.c
index 3f641a5..4bbd237 100644
--- a/tls/openssl/gtlsclientconnection-openssl.c
+++ b/tls/openssl/gtlsclientconnection-openssl.c
@@ -70,7 +70,9 @@ G_DEFINE_TYPE_WITH_CODE (GTlsClientConnectionOpenssl, g_tls_client_connection_op
                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                 g_tls_client_connection_openssl_initable_interface_init)
                          G_IMPLEMENT_INTERFACE (G_TYPE_TLS_CLIENT_CONNECTION,
-                                                
g_tls_client_connection_openssl_client_connection_interface_init))
+                                                
g_tls_client_connection_openssl_client_connection_interface_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_CLIENT_CONNECTION,
+                                                NULL));
 
 static void
 g_tls_client_connection_openssl_finalize (GObject *object)
@@ -381,7 +383,14 @@ g_tls_client_connection_openssl_initable_init (GInitable       *initable,
 
   client->session = SSL_SESSION_new ();
 
-  client->ssl_ctx = SSL_CTX_new (SSLv23_client_method ());
+  client->ssl_ctx = SSL_CTX_new (g_tls_connection_base_is_dtls (G_TLS_CONNECTION_BASE (client))
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined (LIBRESSL_VERSION_NUMBER)
+                                 ? DTLS_client_method ()
+                                 : TLS_client_method ());
+#else
+                                 ? DTLSv1_client_method ()
+                                 : SSLv23_client_method ());
+#endif
   if (!client->ssl_ctx)
     {
       g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
diff --git a/tls/openssl/gtlsconnection-openssl.c b/tls/openssl/gtlsconnection-openssl.c
index 98bbb56..29df6b7 100644
--- a/tls/openssl/gtlsconnection-openssl.c
+++ b/tls/openssl/gtlsconnection-openssl.c
@@ -39,14 +39,32 @@
 
 #include <glib/gi18n-lib.h>
 
+#define DTLS_MESSAGE_MAX_SIZE 65536
+
 typedef struct _GTlsConnectionOpensslPrivate
 {
   BIO *bio;
+  guint8 *dtls_rx;
+  guint8 *dtls_tx;
   GMutex ssl_mutex;
 
   gboolean shutting_down;
 } GTlsConnectionOpensslPrivate;
 
+typedef int (*GTlsOpensslIOFunc) (SSL *ssl, gpointer user_data);
+
+typedef struct _ReadRequest
+{
+  void *buffer;
+  gsize count;
+} ReadRequest;
+
+typedef struct _WriteRequest
+{
+  const void *buffer;
+  gsize count;
+} WriteRequest;
+
 static void g_tls_connection_openssl_initable_iface_init (GInitableIface *iface);
 
 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GTlsConnectionOpenssl, g_tls_connection_openssl, 
G_TYPE_TLS_CONNECTION_BASE,
@@ -62,6 +80,8 @@ g_tls_connection_openssl_finalize (GObject *object)
 
   priv = g_tls_connection_openssl_get_instance_private (openssl);
 
+  g_free (priv->dtls_rx);
+  g_free (priv->dtls_tx);
   g_mutex_clear (&priv->ssl_mutex);
 
   G_OBJECT_CLASS (g_tls_connection_openssl_parent_class)->finalize (object);
@@ -241,6 +261,113 @@ end_openssl_io (GTlsConnectionOpenssl  *openssl,
   return G_TLS_CONNECTION_BASE_ERROR;
 }
 
+static GTlsConnectionBaseStatus
+perform_openssl_io (GTlsConnectionOpenssl  *openssl,
+                    GIOCondition            direction,
+                    GTlsOpensslIOFunc       perform_func,
+                    gpointer                perform_data,
+                    gint64                  timeout,
+                    GCancellable           *cancellable,
+                    int                    *out_ret,
+                    GError                **error,
+                    const char             *err_prefix)
+{
+  GTlsConnectionBaseStatus status;
+  GTlsConnectionBase *tls;
+  GTlsConnectionOpensslPrivate *priv;
+  SSL *ssl;
+  gint64 deadline;
+  int ret;
+
+  tls = G_TLS_CONNECTION_BASE (openssl);
+  priv = g_tls_connection_openssl_get_instance_private (openssl);
+  ssl = g_tls_connection_openssl_get_ssl (openssl);
+
+  if (timeout >= 0)
+    deadline = g_get_monotonic_time () + timeout;
+  else
+    deadline = -1;
+
+  while (TRUE)
+    {
+      GIOCondition io_needed;
+      char error_str[256];
+      struct timeval tv;
+      gint64 io_timeout;
+
+      g_tls_connection_base_push_io (tls, direction, 0, cancellable);
+
+      if (g_tls_connection_base_is_dtls (tls))
+        DTLSv1_handle_timeout (ssl);
+
+      ret = perform_func (ssl, perform_data);
+
+      switch (SSL_get_error (ssl, ret))
+        {
+          case SSL_ERROR_WANT_READ:
+            io_needed = G_IO_IN;
+            break;
+          case SSL_ERROR_WANT_WRITE:
+            io_needed = G_IO_OUT;
+            break;
+          default:
+            io_needed = 0;
+            break;
+        }
+
+      ERR_error_string_n (SSL_get_error (ssl, ret), error_str,
+                          sizeof (error_str));
+      status = end_openssl_io (openssl, direction, ret, TRUE, error, err_prefix,
+                               error_str);
+
+      if (status != G_TLS_CONNECTION_BASE_TRY_AGAIN)
+        break;
+
+      if (g_tls_connection_base_is_dtls (tls) && DTLSv1_get_timeout (ssl, &tv))
+        io_timeout = (tv.tv_sec * G_USEC_PER_SEC) + tv.tv_usec;
+      else
+        io_timeout = -1;
+
+      if (deadline != -1)
+        {
+          gint64 remaining = MAX (deadline - g_get_monotonic_time (), 0);
+
+          if (io_timeout != -1)
+            io_timeout = MIN (io_timeout, remaining);
+          else
+            io_timeout = remaining;
+        }
+
+      if (io_timeout == 0)
+        break;
+
+      g_tls_bio_wait_available (priv->bio, io_needed, io_timeout, cancellable);
+    }
+
+  if (status == G_TLS_CONNECTION_BASE_TRY_AGAIN)
+    {
+      if (timeout == 0)
+        {
+          status = G_TLS_CONNECTION_BASE_WOULD_BLOCK;
+          g_clear_error (error);
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
+                               "Operation would block");
+        }
+      else if (timeout > 0)
+        {
+          status = G_TLS_CONNECTION_BASE_TIMED_OUT;
+          g_clear_error (error);
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                               _("Socket I/O timed out"));
+        }
+    }
+
+  if (out_ret)
+    *out_ret = ret;
+
+  return status;
+}
+
 #if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined (LIBRESSL_VERSION_NUMBER)
 static int
 _openssl_alpn_select_cb (SSL                  *ssl,
@@ -386,41 +513,13 @@ g_tls_connection_openssl_complete_handshake (GTlsConnectionBase  *tls,
 }
 #endif
 
-#define BEGIN_OPENSSL_IO(openssl, direction, timeout, cancellable)          \
-  do {                                                                      \
-    char error_str[256];                                                    \
-    g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl),         \
-                                   direction, timeout, cancellable);
-
-#define END_OPENSSL_IO(openssl, direction, ret, timeout, status, errmsg, err) \
-    ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof(error_str)); \
-    status = end_openssl_io (openssl, direction, ret, timeout == -1, err, errmsg, error_str); \
-  } while (status == G_TLS_CONNECTION_BASE_TRY_AGAIN);
-
-static GTlsConnectionBaseStatus
-g_tls_connection_openssl_handshake_thread_request_rehandshake (GTlsConnectionBase  *tls,
-                                                               gint64               timeout,
-                                                               GCancellable        *cancellable,
-                                                               GError             **error)
+static int
+perform_rehandshake (SSL      *ssl,
+                     gpointer  user_data)
 {
-  GTlsConnectionOpenssl *openssl;
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
+  GTlsConnectionBase *tls = user_data;
   int ret = 1; /* always look on the bright side of life */
 
-  /* On a client-side connection, SSL_renegotiate() itself will start
-   * a rehandshake, so we only need to do something special here for
-   * server-side connections.
-   */
-  if (!G_IS_TLS_SERVER_CONNECTION (tls))
-    return G_TLS_CONNECTION_BASE_OK;
-
-  openssl = G_TLS_CONNECTION_OPENSSL (tls);
-
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-
-  BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
-
 #if OPENSSL_VERSION_NUMBER >= 0x10101000L
   if (SSL_version(ssl) >= TLS1_3_VERSION)
     ret = SSL_key_update (ssl, SSL_KEY_UPDATE_REQUESTED);
@@ -433,10 +532,25 @@ g_tls_connection_openssl_handshake_thread_request_rehandshake (GTlsConnectionBas
   ret = SSL_renegotiate (ssl);
 #endif
 
-  END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
-                  _("Error performing TLS handshake"), error);
+  return ret;
+}
 
-  return status;
+static GTlsConnectionBaseStatus
+g_tls_connection_openssl_handshake_thread_request_rehandshake (GTlsConnectionBase  *tls,
+                                                               gint64               timeout,
+                                                               GCancellable        *cancellable,
+                                                               GError             **error)
+{
+  /* On a client-side connection, SSL_renegotiate() itself will start
+   * a rehandshake, so we only need to do something special here for
+   * server-side connections.
+   */
+  if (!G_IS_TLS_SERVER_CONNECTION (tls))
+    return G_TLS_CONNECTION_BASE_OK;
+
+  return perform_openssl_io (G_TLS_CONNECTION_OPENSSL (tls), G_IO_IN | G_IO_OUT,
+                             perform_rehandshake, tls, timeout, cancellable,
+                             NULL, error, _("Error performing TLS handshake"));
 }
 
 static GTlsCertificate *
@@ -638,21 +752,18 @@ g_tls_connection_openssl_handshake_thread_handshake (GTlsConnectionBase  *tls,
                                                      GCancellable        *cancellable,
                                                      GError             **error)
 {
-  GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
   GTlsConnectionBaseStatus status;
-  SSL *ssl;
   int ret;
 
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
-
-  BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
-  ret = SSL_do_handshake (ssl);
-  END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
-                  _("Error performing TLS handshake"), error);
+  status = perform_openssl_io (G_TLS_CONNECTION_OPENSSL (tls),
+                               G_IO_IN | G_IO_OUT,
+                               (GTlsOpensslIOFunc) SSL_do_handshake,
+                               NULL, timeout, cancellable, &ret, error,
+                               _("Error reading data from TLS socket"));
 
   if (ret > 0)
     {
-      if (!g_tls_connection_base_handshake_thread_verify_certificate (G_TLS_CONNECTION_BASE (openssl)))
+      if (!g_tls_connection_base_handshake_thread_verify_certificate (tls))
         {
           g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
                                _("Unacceptable TLS certificate"));
@@ -678,14 +789,10 @@ g_tls_connection_openssl_push_io (GTlsConnectionBase *tls,
   G_TLS_CONNECTION_BASE_CLASS (g_tls_connection_openssl_parent_class)->push_io (tls, direction,
                                                                                 timeout, cancellable);
 
-  /* FIXME: need to support timeout > 0
-   * This will require changes in GTlsBio */
-
   if (direction & G_IO_IN)
     {
       error = g_tls_connection_base_get_read_error (tls);
       g_tls_bio_set_read_cancellable (priv->bio, cancellable);
-      g_tls_bio_set_read_blocking (priv->bio, timeout == -1);
       g_clear_error (error);
       g_tls_bio_set_read_error (priv->bio, error);
     }
@@ -694,7 +801,6 @@ g_tls_connection_openssl_push_io (GTlsConnectionBase *tls,
     {
       error = g_tls_connection_base_get_write_error (tls);
       g_tls_bio_set_write_cancellable (priv->bio, cancellable);
-      g_tls_bio_set_write_blocking (priv->bio, timeout == -1);
       g_clear_error (error);
       g_tls_bio_set_write_error (priv->bio, error);
     }
@@ -725,6 +831,15 @@ g_tls_connection_openssl_pop_io (GTlsConnectionBase  *tls,
                                                                                       success, error);
 }
 
+static int
+perform_read (SSL      *ssl,
+              gpointer  user_data)
+{
+  ReadRequest *req = user_data;
+
+  return SSL_read (ssl, req->buffer, req->count);
+}
+
 static GTlsConnectionBaseStatus
 g_tls_connection_openssl_read (GTlsConnectionBase    *tls,
                                void                  *buffer,
@@ -733,45 +848,77 @@ g_tls_connection_openssl_read (GTlsConnectionBase    *tls,
                                gssize                *nread,
                                GCancellable          *cancellable,
                                GError               **error)
+{
+  GTlsConnectionBaseStatus status;
+  ReadRequest req = { buffer, count };
+  int ret;
+
+  status = perform_openssl_io (G_TLS_CONNECTION_OPENSSL (tls), G_IO_IN,
+                               perform_read, &req, timeout, cancellable, &ret,
+                               error, _("Error reading data from TLS socket"));
+
+  *nread = MAX (ret, 0);
+  return status;
+}
+
+static GTlsConnectionBaseStatus
+g_tls_connection_openssl_read_message (GTlsConnectionBase  *tls,
+                                       GInputVector        *vectors,
+                                       guint                num_vectors,
+                                       gint64               timeout,
+                                       gssize              *nread,
+                                       GCancellable        *cancellable,
+                                       GError             **error)
 {
   GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
   GTlsConnectionOpensslPrivate *priv;
   GTlsConnectionBaseStatus status;
-  SSL *ssl;
-  gssize ret;
+  gssize bytes_read;
+  gsize bytes_copied, bytes_remaining;
+  guint i;
+
+  *nread = 0;
 
   priv = g_tls_connection_openssl_get_instance_private (openssl);
 
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
+  if (!priv->dtls_rx)
+    priv->dtls_rx = g_malloc (DTLS_MESSAGE_MAX_SIZE);
 
-  /* FIXME: revert back to use BEGIN/END_OPENSSL_IO once we move all the ssl
-   * operations into a worker thread
-   */
-  while (TRUE)
-    {
-      char error_str[256];
+  status = g_tls_connection_openssl_read (tls, priv->dtls_rx,
+                                          DTLS_MESSAGE_MAX_SIZE, timeout,
+                                          &bytes_read, cancellable, error);
+  if (status != G_TLS_CONNECTION_BASE_OK)
+    return status;
 
-      /* We want to always be non blocking here to avoid deadlocks */
-      g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl),
-                                     G_IO_IN, 0, cancellable);
+  bytes_copied = 0;
+  bytes_remaining = bytes_read;
+  for (i = 0; i < num_vectors && bytes_remaining > 0; i++)
+    {
+      GInputVector *vector = &vectors[i];
+      gsize n;
 
-      ret = SSL_read (ssl, buffer, count);
+      n = MIN (bytes_remaining, vector->size);
 
-      ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof (error_str));
-      status = end_openssl_io (openssl, G_IO_IN, ret, timeout == -1, error,
-                               _("Error reading data from TLS socket"), error_str);
+      memcpy (vector->buffer, priv->dtls_rx + bytes_copied, n);
 
-      if (status != G_TLS_CONNECTION_BASE_TRY_AGAIN)
-        break;
-
-      /* Wait for the socket to be available again to avoid an infinite loop */
-      g_tls_bio_wait_available (priv->bio, G_IO_IN, cancellable);
+      bytes_copied += n;
+      bytes_remaining -= n;
     }
 
-  *nread = MAX (ret, 0);
+  *nread = bytes_copied;
+
   return status;
 }
 
+static int
+perform_write (SSL      *ssl,
+               gpointer  user_data)
+{
+  WriteRequest *req = user_data;
+
+  return SSL_write (ssl, req->buffer, req->count);
+}
+
 static GTlsConnectionBaseStatus
 g_tls_connection_openssl_write (GTlsConnectionBase    *tls,
                                 const void            *buffer,
@@ -780,40 +927,55 @@ g_tls_connection_openssl_write (GTlsConnectionBase    *tls,
                                 gssize                *nwrote,
                                 GCancellable          *cancellable,
                                 GError               **error)
+{
+  GTlsConnectionBaseStatus status;
+  WriteRequest req = { buffer, count };
+  int ret;
+
+  status = perform_openssl_io (G_TLS_CONNECTION_OPENSSL (tls), G_IO_OUT,
+                               perform_write, &req, timeout, cancellable, &ret,
+                               error, _("Error writing data to TLS socket"));
+
+  *nwrote = MAX (ret, 0);
+  return status;
+}
+
+static GTlsConnectionBaseStatus
+g_tls_connection_openssl_write_message (GTlsConnectionBase  *tls,
+                                        GOutputVector       *vectors,
+                                        guint                num_vectors,
+                                        gint64               timeout,
+                                        gssize              *nwrote,
+                                        GCancellable        *cancellable,
+                                        GError             **error)
 {
   GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
   GTlsConnectionOpensslPrivate *priv;
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
-  gssize ret;
+  gsize bytes_copied, bytes_available;
+  guint i;
 
   priv = g_tls_connection_openssl_get_instance_private (openssl);
 
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
+  if (!priv->dtls_tx)
+    priv->dtls_tx = g_malloc (DTLS_MESSAGE_MAX_SIZE);
 
-  while (TRUE)
+  bytes_copied = 0;
+  bytes_available = DTLS_MESSAGE_MAX_SIZE;
+  for (i = 0; i < num_vectors && bytes_available > 0; i++)
     {
-      char error_str[256];
-
-      /* We want to always be non blocking here to avoid deadlocks */
-      g_tls_connection_base_push_io (G_TLS_CONNECTION_BASE (openssl),
-                                     G_IO_OUT, 0, cancellable);
+      GOutputVector *vector = &vectors[i];
+      gsize n;
 
-      ret = SSL_write (ssl, buffer, count);
+      n = MIN (vector->size, bytes_available);
 
-      ERR_error_string_n (SSL_get_error (ssl, ret), error_str, sizeof (error_str));
-      status = end_openssl_io (openssl, G_IO_OUT, ret, timeout == -1, error,
-                               _("Error writing data to TLS socket"), error_str);
+      memcpy (priv->dtls_tx + bytes_copied, vector->buffer, n);
 
-      if (status != G_TLS_CONNECTION_BASE_TRY_AGAIN)
-        break;
-
-      /* Wait for the socket to be available again to avoid an infinite loop */
-      g_tls_bio_wait_available (priv->bio, G_IO_OUT, cancellable);
+      bytes_copied += n;
+      bytes_available -= n;
     }
 
-  *nwrote = MAX (ret, 0);
-  return status;
+  return g_tls_connection_openssl_write (tls, priv->dtls_tx, bytes_copied,
+                                         timeout, nwrote, cancellable, error);
 }
 
 static GTlsConnectionBaseStatus
@@ -824,25 +986,16 @@ g_tls_connection_openssl_close (GTlsConnectionBase  *tls,
 {
   GTlsConnectionOpenssl *openssl = G_TLS_CONNECTION_OPENSSL (tls);
   GTlsConnectionOpensslPrivate *priv;
-  GTlsConnectionBaseStatus status;
-  SSL *ssl;
-  int ret;
 
-  ssl = g_tls_connection_openssl_get_ssl (openssl);
   priv = g_tls_connection_openssl_get_instance_private (openssl);
 
   priv->shutting_down = TRUE;
 
-  BEGIN_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, timeout, cancellable);
-  ret = SSL_shutdown (ssl);
-  /* Note it is documented that getting 0 is correct when shutting down since
-   * it means it will close the write direction
-   */
-  ret = ret == 0 ? 1 : ret;
-  END_OPENSSL_IO (openssl, G_IO_IN | G_IO_OUT, ret, timeout, status,
-                  _("Error performing TLS close"), error);
-
-  return status;
+  return perform_openssl_io (G_TLS_CONNECTION_OPENSSL (tls),
+                             G_IO_IN | G_IO_OUT,
+                             (GTlsOpensslIOFunc) SSL_shutdown,
+                             NULL, timeout, cancellable, NULL, error,
+                             _("Error performing TLS close"));
 }
 
 static void
@@ -865,7 +1018,9 @@ g_tls_connection_openssl_class_init (GTlsConnectionOpensslClass *klass)
   base_class->push_io                                    = g_tls_connection_openssl_push_io;
   base_class->pop_io                                     = g_tls_connection_openssl_pop_io;
   base_class->read_fn                                    = g_tls_connection_openssl_read;
+  base_class->read_message_fn                            = g_tls_connection_openssl_read_message;
   base_class->write_fn                                   = g_tls_connection_openssl_write;
+  base_class->write_message_fn                           = g_tls_connection_openssl_write_message;
   base_class->close_fn                                   = g_tls_connection_openssl_close;
 }
 
@@ -880,12 +1035,16 @@ g_tls_connection_openssl_initable_init (GInitable     *initable,
   GTlsConnectionOpensslPrivate *priv;
   GTlsConnectionBase *tls = G_TLS_CONNECTION_BASE (initable);
   GIOStream *base_io_stream;
+  GDatagramBased *base_socket;
   SSL *ssl;
 
   g_object_get (tls,
                 "base-io-stream", &base_io_stream,
+                "base-socket", &base_socket,
                 NULL);
-  g_return_val_if_fail (base_io_stream, FALSE);
+
+  /* Ensure we are in TLS mode or DTLS mode. */
+  g_return_val_if_fail (!!base_io_stream != !!base_socket, FALSE);
 
   priv = g_tls_connection_openssl_get_instance_private (openssl);
 
@@ -897,11 +1056,15 @@ g_tls_connection_openssl_initable_init (GInitable     *initable,
   }
   SSL_set_ex_data (ssl, data_index, openssl);
 
-  priv->bio = g_tls_bio_new (base_io_stream);
+  if (base_io_stream)
+    priv->bio = g_tls_bio_new_from_iostream (base_io_stream);
+  else
+    priv->bio = g_tls_bio_new_from_datagram_based (base_socket);
 
   SSL_set_bio (ssl, priv->bio, priv->bio);
 
-  g_object_unref (base_io_stream);
+  g_clear_object (&base_io_stream);
+  g_clear_object (&base_socket);
 
   return TRUE;
 }
diff --git a/tls/openssl/gtlsserverconnection-openssl.c b/tls/openssl/gtlsserverconnection-openssl.c
index 4ebc4c7..69b65f2 100644
--- a/tls/openssl/gtlsserverconnection-openssl.c
+++ b/tls/openssl/gtlsserverconnection-openssl.c
@@ -57,7 +57,9 @@ G_DEFINE_TYPE_WITH_CODE (GTlsServerConnectionOpenssl, g_tls_server_connection_op
                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                 g_tls_server_connection_openssl_initable_interface_init)
                          G_IMPLEMENT_INTERFACE (G_TYPE_TLS_SERVER_CONNECTION,
-                                                
g_tls_server_connection_openssl_server_connection_interface_init))
+                                                
g_tls_server_connection_openssl_server_connection_interface_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_DTLS_SERVER_CONNECTION,
+                                                NULL));
 
 static void
 g_tls_server_connection_openssl_finalize (GObject *object)
@@ -417,7 +419,14 @@ g_tls_server_connection_openssl_initable_init (GInitable       *initable,
 
   server->session = SSL_SESSION_new ();
 
-  server->ssl_ctx = SSL_CTX_new (SSLv23_server_method ());
+  server->ssl_ctx = SSL_CTX_new (g_tls_connection_base_is_dtls (G_TLS_CONNECTION_BASE (server))
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined (LIBRESSL_VERSION_NUMBER)
+                                 ? DTLS_server_method ()
+                                 : TLS_server_method ());
+#else
+                                 ? DTLSv1_server_method ()
+                                 : SSLv23_server_method ());
+#endif
   if (!server->ssl_ctx)
     {
       g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC,
diff --git a/tls/openssl/openssl-include.h b/tls/openssl/openssl-include.h
index 8f57042..7d394d5 100644
--- a/tls/openssl/openssl-include.h
+++ b/tls/openssl/openssl-include.h
@@ -26,21 +26,22 @@
 
 #pragma once
 
-#include "glib.h"
-
 /* Due to name clashes between Windows and openssl headers we have to
  * make sure windows.h is included before openssl and that we undef the
- * clashing macros.
+ * clashing macros. We also need `struct timeval` for DTLSv1_get_timeout(),
+ * and the following header also covers it for Windows.
  */
+#include <gio/gnetworking.h>
 #ifdef G_OS_WIN32
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
 /* These are defined by the Windows headers, but clash with openssl */
 #undef X509_NAME
 #undef X509_CERT_PAIR
 #undef X509_EXTENSIONS
 #undef OCSP_REQUEST
 #undef OCSP_RESPONSE
+#else
+/* Need `struct timeval` for DTLSv1_get_timeout() */
+#include <sys/time.h>
 #endif
 
 #include <openssl/ssl.h>
diff --git a/tls/tests/dtls-connection.c b/tls/tests/dtls-connection.c
index 4f3cd3c..78b2cee 100644
--- a/tls/tests/dtls-connection.c
+++ b/tls/tests/dtls-connection.c
@@ -31,7 +31,9 @@
 #include "mock-interaction.h"
 
 #include <gio/gio.h>
+#ifdef BACKEND_IS_GNUTLS
 #include <gnutls/gnutls.h>
+#endif
 
 #include <sys/types.h>
 #include <string.h>
diff --git a/tls/tests/meson.build b/tls/tests/meson.build
index a256317..e9c7d8c 100644
--- a/tls/tests/meson.build
+++ b/tls/tests/meson.build
@@ -51,11 +51,6 @@ test_programs = [
 
 foreach backend: backends
   foreach program: test_programs
-    # OpenSSL does not support DTLS yet.
-    if program[0] == 'dtls-connection' and backend == 'openssl'
-      continue
-    endif
-
     program_name = program[0] + '-' + backend
 
     test_conf = configuration_data()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]