summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGerd Hoffmann <kraxel@redhat.com>2010-09-15 12:07:18 +0200
committerGerd Hoffmann <kraxel@redhat.com>2010-09-15 12:07:18 +0200
commit830519ef528c6b7fe296be5c03156dfdb60a1f79 (patch)
treec743ad8e3b2d33a14441a6e8cd7d179bde5ce6a1
parentaeff3ce331577a16a0e5f79ca651328fcf19981b (diff)
tls support
-rw-r--r--gtk/snappy.c22
-rw-r--r--gtk/spice-channel-priv.h5
-rw-r--r--gtk/spice-channel.c140
-rw-r--r--gtk/spice-session.c190
-rw-r--r--gtk/spice-session.h5
-rw-r--r--gtk/spicy.c36
6 files changed, 315 insertions, 83 deletions
diff --git a/gtk/snappy.c b/gtk/snappy.c
index 1fb2cd0..79486a8 100644
--- a/gtk/snappy.c
+++ b/gtk/snappy.c
@@ -1,9 +1,12 @@
#include "spice-client.h"
/* config */
-static char *host = "localhost";
-static char *port = "5920";
-static char *outf = "snappy.ppm";
+static char *host = "localhost";
+static char *port = "5920";
+static char *tls_port = "5921";
+static char *password;
+static char *ca_file;
+static char *outf = "snappy.ppm";
/* state */
static SpiceSession *session;
@@ -141,8 +144,17 @@ int main(int argc, char *argv[])
g_signal_connect(session, "spice-session-channel-new",
G_CALLBACK(channel_new), NULL);
- spice_session_set_host(session, host);
- spice_session_set_port(session, port);
+ if (host)
+ g_object_set(session, "host", host, NULL);
+ if (port)
+ g_object_set(session, "port", port, NULL);
+ if (tls_port)
+ g_object_set(session, "tls-port", tls_port, NULL);
+ if (password)
+ g_object_set(session, "password", password, NULL);
+ if (ca_file)
+ g_object_set(session, "ca-file", ca_file, NULL);
+
if (!spice_session_connect(session)) {
fprintf(stderr, "spice_session_connect failed\n");
exit(1);
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index b7b5988..b411ca3 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -1,5 +1,7 @@
#include <pixman.h>
+#include <openssl/ssl.h>
+
#include "decode.h"
/* spice/common */
@@ -32,6 +34,7 @@ struct spice_msg_out {
enum spice_channel_state {
SPICE_CHANNEL_STATE_UNCONNECTED = 0,
+ SPICE_CHANNEL_STATE_TLS,
SPICE_CHANNEL_STATE_LINK_HDR,
SPICE_CHANNEL_STATE_LINK_MSG,
SPICE_CHANNEL_STATE_AUTH,
@@ -118,6 +121,8 @@ struct spice_channel {
spice_parse_channel_func_t parser;
SpiceMessageMarshallers *marshallers;
spice_watch *watch;
+ SSL_CTX *ctx;
+ SSL *ssl;
int protocol;
int tls;
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index b78f744..6c980a2 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -7,10 +7,12 @@
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
+#include <openssl/err.h>
#include <sys/socket.h>
static void spice_channel_send_msg(SpiceChannel *channel, spice_msg_out *out);
+static void spice_channel_send_link(SpiceChannel *channel);
/* ------------------------------------------------------------------ */
/* gobject glue */
@@ -29,8 +31,7 @@ static void spice_channel_init(SpiceChannel *channel)
c->socket = -1;
}
-static void
-spice_channel_finalize(GObject *gobject)
+static void spice_channel_finalize(GObject *gobject)
{
SpiceChannel *channel = SPICE_CHANNEL(gobject);
@@ -250,32 +251,75 @@ static int spice_channel_send(SpiceChannel *channel, void *buf, int len)
{
spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
- return send(c->socket, buf, len, 0);
+ if (c->tls) {
+ return SSL_write(c->ssl, buf, len);
+ } else {
+ return send(c->socket, buf, len, 0);
+ }
}
static int spice_channel_recv(SpiceChannel *channel, void *buf, int len)
{
spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
- int rc;
+ int rc, err;
- rc = recv(c->socket, buf, len, 0);
- switch (rc) {
- case -1:
- if (errno == EAGAIN)
+ if (c->tls) {
+ rc = SSL_read(c->ssl, buf, len);
+ if (rc > 0) {
+ return rc;
+ }
+ if (rc == 0) {
+ fprintf(stderr, "channel/tls eof: %d:%d (%s)\n",
+ c->info->type, c->channel_id, c->info->name);
+ spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
return 0;
- fprintf(stderr, "channel error: %d:%d (%s) %s\n",
+ }
+ err = SSL_get_error(c->ssl, rc);
+ if (err == SSL_ERROR_WANT_READ) {
+ return 0;
+ }
+ fprintf(stderr, "channel/tls error: %d:%d (%s): %s\n",
c->info->type, c->channel_id, c->info->name,
- strerror(errno));
+ ERR_error_string(err, NULL));
spice_channel_disconnect(channel, SPICE_CHANNEL_ERROR_IO);
return 0;
- case 0:
- fprintf(stderr, "channel eof: %d:%d (%s)\n",
- c->info->type, c->channel_id, c->info->name);
- spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
- return 0;
- default:
- return rc;
+ } else {
+ rc = recv(c->socket, buf, len, 0);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return 0;
+ fprintf(stderr, "channel error: %d:%d (%s): %s\n",
+ c->info->type, c->channel_id, c->info->name,
+ strerror(errno));
+ spice_channel_disconnect(channel, SPICE_CHANNEL_ERROR_IO);
+ return 0;
+ case 0:
+ fprintf(stderr, "channel eof: %d:%d (%s)\n",
+ c->info->type, c->channel_id, c->info->name);
+ spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
+ return 0;
+ default:
+ return rc;
+ }
+ }
+}
+
+static void spice_channel_tls_connect(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ int rc, err;
+
+ rc = SSL_connect(c->ssl);
+ if (rc <= 0) {
+ err = SSL_get_error(c->ssl, rc);
+ if (err == SSL_ERROR_WANT_READ) {
+ return;
+ }
+ PANIC("SSL_connect failed: %s", ERR_error_string(err, NULL));
}
+ c->state = SPICE_CHANNEL_STATE_LINK_HDR;
+ spice_channel_send_link(channel);
}
static void spice_channel_send_auth(SpiceChannel *channel)
@@ -285,10 +329,13 @@ static void spice_channel_send_auth(SpiceChannel *channel)
int nRSASize;
BIO *bioKey;
RSA *rsa;
- const uint8_t *password = spice_session_get_password(c->session);
+ const uint8_t *password;
uint8_t *encrypted;
int rc;
+ g_object_get(c->session, "password", &password, NULL);
+ fprintf(stderr, "%s: password \"%s\"\n", __FUNCTION__, password);
+
bioKey = BIO_new(BIO_s_mem());
if (bioKey == NULL)
PANIC("Could not initiate BIO");
@@ -303,7 +350,8 @@ static void spice_channel_send_auth(SpiceChannel *channel)
The use of RSA encryption limit the potential maximum password length.
for RSA_PKCS1_OAEP_PADDING it is RSA_size(rsa) - 41.
*/
- rc = RSA_public_encrypt(strlen(password) + 1, password, encrypted,
+ rc = RSA_public_encrypt(strlen((char*)password) + 1,
+ password, encrypted,
rsa, RSA_PKCS1_OAEP_PADDING);
if (rc <= 0)
PANIC("could not encrypt password");
@@ -564,6 +612,9 @@ static void spice_channel_data(int event, void *opaque)
spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
switch (c->state) {
+ case SPICE_CHANNEL_STATE_TLS:
+ spice_channel_tls_connect(channel);
+ break;
case SPICE_CHANNEL_STATE_LINK_HDR:
spice_channel_recv_link_hdr(channel);
break;
@@ -616,9 +667,25 @@ int spice_channel_id(SpiceChannel *channel)
return c->channel_id;
}
+static int tls_verify(int preverify_ok, X509_STORE_CTX *ctx)
+{
+ spice_channel *c;
+ char *hostname;
+ SSL *ssl;
+
+ ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ c = SSL_get_app_data(ssl);
+
+ g_object_get(c->session, "host", &hostname, NULL);
+ /* TODO: check hostname */
+
+ return preverify_ok;
+}
+
gboolean spice_channel_connect(SpiceChannel *channel)
{
spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ int rc, err;
if (c->state != SPICE_CHANNEL_STATE_UNCONNECTED) {
return true;
@@ -638,7 +705,40 @@ reconnect:
spice_channel_data, channel);
if (c->tls) {
- PANIC("TODO: tls");
+ char *ca_file;
+
+ c->ctx = SSL_CTX_new(TLSv1_method());
+ if (c->ctx == NULL) {
+ PANIC("SSL_CTX_new failed");
+ }
+
+ g_object_get(c->session, "ca-file", &ca_file, NULL);
+ if (ca_file) {
+ rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL);
+ if (rc <= 0) {
+ fprintf(stderr, "loading ca certs from %s failed\n", ca_file);
+ }
+ }
+ SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, tls_verify);
+
+ c->ssl = SSL_new(c->ctx);
+ if (c->ssl == NULL) {
+ PANIC("SSL_new failed");
+ }
+ rc = SSL_set_fd(c->ssl, c->socket);
+ if (rc <= 0) {
+ PANIC("SSL_set_fd failed");
+ }
+ SSL_set_app_data(c->ssl, c);
+ rc = SSL_connect(c->ssl);
+ if (rc <= 0) {
+ err = SSL_get_error(c->ssl, rc);
+ if (err == SSL_ERROR_WANT_READ) {
+ c->state = SPICE_CHANNEL_STATE_TLS;
+ return 0;
+ }
+ PANIC("SSL_connect failed: %s", ERR_error_string(err, NULL));
+ }
}
c->state = SPICE_CHANNEL_STATE_LINK_HDR;
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
index aadd866..41dc292 100644
--- a/gtk/spice-session.c
+++ b/gtk/spice-session.c
@@ -8,8 +8,8 @@ struct spice_session {
char *port;
char *tls_port;
char *password;
+ char *ca_file;
struct addrinfo ai;
-
int connection_id;
SpiceChannel *channels[16];
int nchannels;
@@ -23,6 +23,19 @@ struct spice_session {
G_DEFINE_TYPE (SpiceSession, spice_session, G_TYPE_OBJECT);
+/* Properties */
+enum {
+ PROP_0,
+ PROP_HOST,
+ PROP_PORT,
+ PROP_TLS_PORT,
+ PROP_PASSWORD,
+ PROP_CA_FILE,
+ PROP_IPV4,
+ PROP_IPV6,
+};
+
+/* signals */
enum {
SPICE_SESSION_CHANNEL_NEW,
SPICE_SESSION_LAST_SIGNAL,
@@ -51,16 +64,152 @@ spice_session_finalize(GObject *gobject)
/* release stuff */
free(s->host);
free(s->port);
+ free(s->tls_port);
+ free(s->password);
+ free(s->ca_file);
/* Chain up to the parent class */
G_OBJECT_CLASS(spice_session_parent_class)->finalize(gobject);
}
+static void spice_session_get_property(GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceSession *session = SPICE_SESSION(gobject);
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+
+ switch (prop_id) {
+ case PROP_HOST:
+ g_value_set_string(value, s->host);
+ break;
+ case PROP_PORT:
+ g_value_set_string(value, s->port);
+ break;
+ case PROP_TLS_PORT:
+ g_value_set_string(value, s->tls_port);
+ break;
+ case PROP_PASSWORD:
+ g_value_set_string(value, s->password);
+ break;
+ case PROP_CA_FILE:
+ g_value_set_string(value, s->ca_file);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void spice_session_set_property(GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceSession *session = SPICE_SESSION(gobject);
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+ const char *str;
+
+ switch (prop_id) {
+ case PROP_HOST:
+ free(s->host);
+ str = g_value_get_string(value);
+ s->host = str ? strdup(str) : NULL;
+ break;
+ case PROP_PORT:
+ free(s->port);
+ str = g_value_get_string(value);
+ s->port = str ? strdup(str) : NULL;
+ break;
+ case PROP_TLS_PORT:
+ free(s->tls_port);
+ str = g_value_get_string(value);
+ s->tls_port = str ? strdup(str) : NULL;
+ break;
+ case PROP_PASSWORD:
+ free(s->password);
+ str = g_value_get_string(value);
+ s->password = str ? strdup(str) : NULL;
+ break;
+ case PROP_CA_FILE:
+ free(s->ca_file);
+ str = g_value_get_string(value);
+ s->ca_file = str ? strdup(str) : NULL;
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+ break;
+ }
+}
+
static void spice_session_class_init(SpiceSessionClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
gobject_class->finalize = spice_session_finalize;
+ gobject_class->get_property = spice_session_get_property;
+ gobject_class->set_property = spice_session_set_property;
+
+ g_object_class_install_property
+ (gobject_class, PROP_HOST,
+ g_param_spec_string("host",
+ "Host",
+ "remote host",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_PORT,
+ g_param_spec_string("port",
+ "Port",
+ "remote port (plaintext)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_TLS_PORT,
+ g_param_spec_string("tls-port",
+ "TLS port",
+ "remote port (encrypted)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_PASSWORD,
+ g_param_spec_string("password",
+ "Password",
+ "",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_CA_FILE,
+ g_param_spec_string("ca-file",
+ "CA file",
+ "File holding the CA certificates",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
signals[SPICE_SESSION_CHANNEL_NEW] =
g_signal_new("spice-session-channel-new",
@@ -87,45 +236,6 @@ SpiceSession *spice_session_new()
NULL));
}
-void spice_session_set_host(SpiceSession *session, const char *host)
-{
- spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
-
- free(s->host);
- s->host = strdup(host);
-}
-
-void spice_session_set_port(SpiceSession *session, const char *port)
-{
- spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
-
- free(s->port);
- s->port = strdup(port);
-}
-
-void spice_session_set_tls_port(SpiceSession *session, const char *tls_port)
-{
- spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
-
- free(s->tls_port);
- s->tls_port = strdup(tls_port);
-}
-
-void spice_session_set_password(SpiceSession *session, const char *password)
-{
- spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
-
- free(s->password);
- s->password = strdup(password);
-}
-
-const char *spice_session_get_password(SpiceSession *session)
-{
- spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
-
- return s->password ? s->password : "";
-}
-
gboolean spice_session_connect(SpiceSession *session)
{
SpiceChannel *cmain;
diff --git a/gtk/spice-session.h b/gtk/spice-session.h
index 212f3a1..2c0a459 100644
--- a/gtk/spice-session.h
+++ b/gtk/spice-session.h
@@ -34,11 +34,6 @@ struct _SpiceSessionClass
GType spice_session_get_type(void) G_GNUC_CONST;
SpiceSession *spice_session_new(void);
-void spice_session_set_host(SpiceSession *session, const char *host);
-void spice_session_set_port(SpiceSession *session, const char *port);
-void spice_session_set_tls_port(SpiceSession *session, const char *tls_port);
-void spice_session_set_password(SpiceSession *session, const char *password);
-const char *spice_session_get_password(SpiceSession *session);
gboolean spice_session_connect(SpiceSession *session);
void spice_session_disconnect(SpiceSession *session);
int spice_session_get_channels(SpiceSession *session, SpiceChannel **channels, int max);
diff --git a/gtk/spicy.c b/gtk/spicy.c
index 95a3774..40e7325 100644
--- a/gtk/spicy.c
+++ b/gtk/spicy.c
@@ -13,10 +13,11 @@ struct spice_window {
};
/* config */
-static char *host = "localhost";
-static char *port = "5920";
-static char *tls_port = "5921";
+static char *host = "localhost";
+static char *port = "5920";
+static char *tls_port = "5921";
static char *password;
+static char *ca_file;
static bool fullscreen = false;
/* state */
@@ -313,14 +314,14 @@ static void usage(FILE *fp)
{
fprintf(fp,
"usage:\n"
- " spice-gtk [ options ]\n"
+ " spicy [ options ]\n"
"\n"
"options:\n"
- " -h host [ %s ]\n"
- " -p port [ %s ]\n"
- " -s tls port [ %s ]\n"
- " -w password\n"
- " -f fullscreen\n"
+ " -h <host> remote host [ %s ]\n"
+ " -p <port> remote port [ %s ]\n"
+ " -s <port> remote tls port [ %s ]\n"
+ " -w <secret> password\n"
+ " -f fullscreen\n"
"\n",
host, port, tls_port);
}
@@ -361,18 +362,27 @@ int main(int argc, char *argv[])
}
}
+ if (ca_file == NULL) {
+ char *home = getenv("HOME");
+ size_t size = strlen(home) + 32;
+ ca_file = malloc(size);
+ snprintf(ca_file, size, "%s/.spicec/spice_truststore.pem", home);
+ }
session = spice_session_new();
g_signal_connect(session, "spice-session-channel-new",
G_CALLBACK(channel_new), NULL);
- spice_session_set_host(session, host);
+ if (host)
+ g_object_set(session, "host", host, NULL);
if (port)
- spice_session_set_port(session, port);
+ g_object_set(session, "port", port, NULL);
if (tls_port)
- spice_session_set_tls_port(session, tls_port);
+ g_object_set(session, "tls-port", tls_port, NULL);
if (password)
- spice_session_set_password(session, password);
+ g_object_set(session, "password", password, NULL);
+ if (ca_file)
+ g_object_set(session, "ca-file", ca_file, NULL);
if (!spice_session_connect(session)) {
fprintf(stderr, "spice_session_connect failed\n");