summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Fergeau <cfergeau@redhat.com>2014-05-14 21:49:27 +0200
committerChristophe Fergeau <cfergeau@redhat.com>2014-05-15 12:37:15 +0200
commitb26c5dbc389de696a91a2ccbd40e81773fe7e63b (patch)
treefae8f42816b8dfe209db833add4b4411f653e10b
parente9a8ecf6667beca9557526bbff5265ed3eb92c95 (diff)
gnutls support
-rw-r--r--configure.ac5
-rw-r--r--gtk/Makefile.am2
-rw-r--r--gtk/spice-channel-priv.h2
-rw-r--r--gtk/spice-channel.c220
4 files changed, 229 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index d89bd6f..79738c9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -111,6 +111,11 @@ AC_SUBST(SSL_CFLAGS)
AC_SUBST(SSL_LIBS)
SPICE_GLIB_REQUIRES="${SPICE_GLIB_REQUIRES} openssl"
+PKG_CHECK_MODULES(TLS, gnutls)
+AC_SUBST(TLS_CFLAGS)
+AC_SUBST(TLS_LIBS)
+SPICE_GLIB_REQUIRES="${SPICE_TLS_REQUIRES} gnutls"
+
dnl Cyrus SASL
AC_ARG_WITH([sasl],
[AS_HELP_STRING([--with-sasl=@<:@yes/no/auto@:>@], [use cyrus SASL for authentication @<:@default=auto@:>@])],
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 8da1a11..4b7de66 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -91,6 +91,7 @@ SPICE_COMMON_CPPFLAGS = \
$(DBUS_GLIB_CFLAGS) \
$(SSL_CFLAGS) \
$(SASL_CFLAGS) \
+ $(TLS_CFLAGS) \
$(GST_CFLAGS) \
$(SMARTCARD_CFLAGS) \
$(USBREDIR_CFLAGS) \
@@ -196,6 +197,7 @@ libspice_client_glib_2_0_la_LIBADD = \
$(Z_LIBS) \
$(PIXMAN_LIBS) \
$(SSL_LIBS) \
+ $(TLS_LIBS) \
$(PULSE_LIBS) \
$(GST_LIBS) \
$(SASL_LIBS) \
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index 607c0d4..59f94af 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -24,6 +24,7 @@
#include <openssl/ssl.h>
#include <gio/gio.h>
+#include <gnutls/gnutls.h>
#if HAVE_SASL
#include <sasl/sasl.h>
@@ -80,6 +81,7 @@ struct _SpiceChannelPrivate {
SSL_CTX *ctx;
SSL *ssl;
SpiceOpenSSLVerify *sslverify;
+ gnutls_session_t tls_session;
GSocket *sock;
GSocketConnection *conn;
GInputStream *in;
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index e460590..8f5c480 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -54,6 +54,9 @@ static void channel_reset(SpiceChannel *channel, gboolean migrating);
static void spice_channel_reset_capabilities(SpiceChannel *channel);
static void spice_channel_send_migration_handshake(SpiceChannel *channel);
+static gboolean spice_channel_gnutls_eagain_set_giocondition(gnutls_session_t session,
+ GIOCondition *cond);
+
/**
* SECTION:spice-channel
* @short_description: the base channel class
@@ -263,6 +266,35 @@ static void spice_channel_set_property(GObject *gobject,
}
}
+static gnutls_certificate_credentials_t cred = NULL;
+static void spice_channel_gnutls_init(void)
+{
+ gnutls_global_init();
+
+ // Load the trusted CA certificates.
+ int ret = gnutls_certificate_allocate_credentials (&cred);
+ if (ret != GNUTLS_E_SUCCESS) {
+ fprintf(stderr, "error: gnutls_certificate_allocate_credentials: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+ // gnutls_certificate_set_x509_system_trust needs GNUTLS version 3.0
+ // or newer, so we hard-code the path to the certificate store
+ // instead.
+ static const char ca_bundle[] = "/etc/ssl/certs/ca-bundle.crt";
+ ret = gnutls_certificate_set_x509_trust_file (cred, ca_bundle, GNUTLS_X509_FMT_PEM);
+ if (ret == 0) {
+ fprintf(stderr, "error: no certificates found in: %s\n", ca_bundle);
+ exit(1);
+ }
+ if (ret < 0) {
+ fprintf(stderr, "error: gnutls_certificate_set_x509_trust_files(%s): %s\n",
+ ca_bundle, gnutls_strerror(ret));
+ exit(1);
+ }
+ /* FIXME: need to call gnutls_certificate_free_credentials(cred); */
+}
+
static void spice_channel_class_init(SpiceChannelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
@@ -363,6 +395,7 @@ static void spice_channel_class_init(SpiceChannelClass *klass)
SSL_library_init();
SSL_load_error_strings();
+ spice_channel_gnutls_init();
}
/* ---------------------------------------------------------------- */
@@ -788,6 +821,13 @@ static void spice_channel_flush_wire(SpiceChannel *channel,
cond |= G_IO_OUT;
ret = -1;
}
+ ret = gnutls_record_send(c->tls_session, ptr+offset, datalen-offset);
+ if (ret < 0) {
+ if ((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) {
+ spice_channel_gnutls_eagain_set_giocondition(c->tls_session, &cond);
+ }
+ ret = -1;
+ }
} else {
#if GLIB_CHECK_VERSION(2, 28, 0)
ret = g_pollable_output_stream_write_nonblocking(G_POLLABLE_OUTPUT_STREAM(c->out),
@@ -911,6 +951,7 @@ reread:
cond = 0;
if (c->tls) {
+#if 0
ret = SSL_read(c->ssl, data, len);
if (ret < 0) {
ret = SSL_get_error(c->ssl, ret);
@@ -920,6 +961,14 @@ reread:
cond |= G_IO_OUT;
ret = -1;
}
+#endif
+ ret = gnutls_record_recv(c->tls_session, data, len);
+ if (ret < 0) {
+ if ((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) {
+ spice_channel_gnutls_eagain_set_giocondition(c->tls_session, &cond);
+ }
+ ret = -1;
+ }
} else {
GError *error = NULL;
#if GLIB_CHECK_VERSION(2, 28, 0)
@@ -2188,6 +2237,9 @@ static int spice_channel_load_ca(SpiceChannel *channel)
g_return_val_if_fail(c->ctx != NULL, 0);
+/* FIXME: reimplement with GNUTLS,
+ * cf gnutls_certificate_set_x509_trust_file
+ * gnutls_certificate_set_x509_trust_mem */
lookup = X509_STORE_add_lookup(c->ctx->cert_store, &spice_x509_mem_lookup);
ca_file = spice_session_get_ca_file(c->session);
spice_session_get_ca(c->session, &ca, &size);
@@ -2254,6 +2306,99 @@ const GError* spice_channel_get_error(SpiceChannel *self)
return c->error;
}
+/* Function from glib-networking gtlsconnection-gnutls.c implementation */
+/* Copyright 2009 Red Hat, Inc, released as LGPLv2+ */
+static void
+spice_channel_set_gnutls_error(SpiceChannel *channel,
+ GError *error)
+{
+ /* We set EINTR rather than EAGAIN for G_IO_ERROR_WOULD_BLOCK so
+ * that GNUTLS_E_AGAIN only gets returned for gnutls-internal
+ * reasons, not for actual socket EAGAINs (and we have access
+ * to @error at the higher levels, so we can distinguish them
+ * that way later).
+ */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ gnutls_transport_set_errno (channel->priv->tls_session, EINTR);
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ gnutls_transport_set_errno (channel->priv->tls_session, EINTR);
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT))
+ gnutls_transport_set_errno (channel->priv->tls_session, EINTR);
+ else
+ gnutls_transport_set_errno (channel->priv->tls_session, EIO);
+}
+
+static ssize_t
+spice_channel_gnutls_write(gnutls_transport_ptr_t transport,
+ const void *in, size_t inl)
+{
+ gssize ret;
+ GError *error = NULL;
+ SpiceChannel *channel = SPICE_CHANNEL(transport);
+
+#if GLIB_CHECK_VERSION(2, 28, 0)
+ GOutputStream *stream = g_io_stream_get_output_stream(G_IO_STREAM(channel->priv->conn));
+
+ ret = g_pollable_output_stream_write_nonblocking(G_POLLABLE_OUTPUT_STREAM(stream),
+ in, inl, NULL, &error);
+#else
+ ret = g_socket_send(channel->priv->sock,
+ in, inl, NULL, &error);
+#endif
+
+ if (error != NULL) {
+ spice_channel_set_gnutls_error(channel, error);
+ g_error_free(error);
+ }
+
+ return ret;
+}
+
+static ssize_t
+spice_channel_gnutls_read(gnutls_transport_ptr_t transport,
+ void *out, size_t outl)
+{
+ gssize ret;
+ GError *error = NULL;
+ SpiceChannel *channel = SPICE_CHANNEL(transport);
+
+#if GLIB_CHECK_VERSION(2, 28, 0)
+ GInputStream *stream = g_io_stream_get_input_stream(G_IO_STREAM(channel->priv->conn));
+
+ ret = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(stream),
+ out, outl, NULL, &error);
+#else
+ ret = g_socket_receive(channel->priv->sock,
+ out, outl, NULL, &error);
+#endif
+
+ if (error != NULL) {
+ spice_channel_set_gnutls_error(channel, error);
+ g_error_free(error);
+ }
+
+ return ret;
+}
+
+static gboolean
+spice_channel_gnutls_eagain_set_giocondition(gnutls_session_t session,
+ GIOCondition *cond)
+{
+ switch (gnutls_record_get_direction(session)) {
+ case 0:
+ *cond |= G_IO_IN;
+ return TRUE;
+ case 1:
+ *cond |= G_IO_OUT;
+ return TRUE;
+ }
+ /* Unexpected/unknown/undocumented value returned by
+ * gnutls_record_get_direction */
+ g_warn_if_reached();
+ return FALSE;
+}
+
+
/* coroutine context */
static void *spice_channel_coroutine(void *data)
{
@@ -2314,9 +2459,17 @@ reconnect:
emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_TLS);
goto cleanup;
}
+ rc = gnutls_init(&c->tls_session, GNUTLS_CLIENT);
+ if (rc != GNUTLS_E_SUCCESS) {
+ g_critical("gnutls_init failed: %s", gnutls_strerror(rc));
+ emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_TLS);
+ goto cleanup;
+ }
SSL_CTX_set_options(c->ctx, ssl_options);
+ /* FIXME: could be done as soon as the options are set on SpiceSession
+ * since with GNUTLS the truststore is global */
verify = spice_session_get_verify(c->session);
if (verify &
(SPICE_SESSION_VERIFY_SUBJECT | SPICE_SESSION_VERIFY_HOSTNAME)) {
@@ -2334,12 +2487,35 @@ reconnect:
}
{
+ /* FIXME: gnutls_priority_set_direct, check format of string
+ * returned by spice_session_set_cipher */
const gchar *ciphers = spice_session_get_ciphers(c->session);
if (ciphers != NULL) {
rc = SSL_CTX_set_cipher_list(c->ctx, ciphers);
if (rc != 1)
g_warning("loading cipher list %s failed", ciphers);
}
+ const char *errptr = NULL;
+ if (ciphers == NULL) {
+ ciphers = "NORMAL";
+ }
+ rc = gnutls_priority_set_direct(c->tls_session, "NORMAL", &errptr);
+ if (rc != GNUTLS_E_SUCCESS) {
+ g_warning("failed to set cipher list to '%s': %s",
+ ciphers, errptr);
+ emit_main_context(channel, SPICE_CHANNEL_EVENT,
+ SPICE_CHANNEL_ERROR_TLS);
+ goto cleanup;
+ }
+ }
+
+ rc = gnutls_credentials_set(c->tls_session, GNUTLS_CRD_CERTIFICATE,
+ cred);
+ if (rc != GNUTLS_E_SUCCESS) {
+ g_critical("gnutls_credentials_set failed: %s",
+ gnutls_strerror(rc));
+ emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_TLS);
+ goto cleanup;
}
c->ssl = SSL_new(c->ctx);
@@ -2357,6 +2533,24 @@ reconnect:
#endif
SSL_set_bio(c->ssl, bio, bio);
+ gnutls_transport_set_ptr(c->tls_session,
+ (gnutls_transport_ptr_t)channel);
+ gnutls_transport_set_push_function(c->tls_session,
+ spice_channel_gnutls_write);
+ gnutls_transport_set_pull_function(c->tls_session,
+ spice_channel_gnutls_read);
+
+ /* FIXME: correct name to pass there??
+ * especially wrt host subject overrides? */
+ rc = gnutls_server_name_set(c->tls_session, GNUTLS_NAME_DNS,
+ spice_session_get_host(c->session),
+ strlen(spice_session_get_host(c->session)));
+ if (rc != GNUTLS_E_SUCCESS) {
+ g_critical("gnutls_server_name_set: %s", gnutls_strerror(rc));
+ emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_TLS);
+ goto cleanup;
+ }
+
{
guint8 *pubkey;
guint pubkey_len;
@@ -2368,6 +2562,25 @@ reconnect:
spice_session_get_cert_subject(c->session));
}
+handshake_resume:
+ rc = gnutls_handshake(c->tls_session);
+ if ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
+ GIOCondition cond = G_IO_ERR|G_IO_HUP;
+
+ if (!spice_channel_gnutls_eagain_set_giocondition(c->tls_session, &cond)) {
+ emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_TLS);
+ goto cleanup;
+ }
+ g_coroutine_socket_wait(&c->coroutine, c->sock, cond);
+ goto handshake_resume;
+ } else if (rc != GNUTLS_E_SUCCESS) {
+ g_warning("%s: gnutls_handshake: %s",
+ c->name, gnutls_strerror(rc));
+ emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_TLS);
+ goto cleanup;
+ }
+ /* FIXME: certificate verification */
+#if 0
ssl_reconnect:
rc = SSL_connect(c->ssl);
if (rc <= 0) {
@@ -2382,8 +2595,10 @@ ssl_reconnect:
goto cleanup;
}
}
+#endif
}
+
connected:
c->in = g_io_stream_get_input_stream(G_IO_STREAM(c->conn));
c->out = g_io_stream_get_output_stream(G_IO_STREAM(c->conn));
@@ -2552,6 +2767,11 @@ static void channel_reset(SpiceChannel *channel, gboolean migrating)
c->ssl = NULL;
}
+ if (c->tls_session) {
+ gnutls_deinit(c->tls_session);
+ c->tls_session = NULL;
+ }
+
if (c->ctx) {
SSL_CTX_free(c->ctx);
c->ctx = NULL;