diff options
author | Christophe Fergeau <cfergeau@redhat.com> | 2014-05-14 21:49:27 +0200 |
---|---|---|
committer | Christophe Fergeau <cfergeau@redhat.com> | 2014-05-15 12:37:15 +0200 |
commit | b26c5dbc389de696a91a2ccbd40e81773fe7e63b (patch) | |
tree | fae8f42816b8dfe209db833add4b4411f653e10b | |
parent | e9a8ecf6667beca9557526bbff5265ed3eb92c95 (diff) |
gnutls support
-rw-r--r-- | configure.ac | 5 | ||||
-rw-r--r-- | gtk/Makefile.am | 2 | ||||
-rw-r--r-- | gtk/spice-channel-priv.h | 2 | ||||
-rw-r--r-- | gtk/spice-channel.c | 220 |
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; |