diff options
author | Gerd Hoffmann <kraxel@redhat.com> | 2010-09-15 12:07:18 +0200 |
---|---|---|
committer | Gerd Hoffmann <kraxel@redhat.com> | 2010-09-15 12:07:18 +0200 |
commit | 830519ef528c6b7fe296be5c03156dfdb60a1f79 (patch) | |
tree | c743ad8e3b2d33a14441a6e8cd7d179bde5ce6a1 | |
parent | aeff3ce331577a16a0e5f79ca651328fcf19981b (diff) |
tls support
-rw-r--r-- | gtk/snappy.c | 22 | ||||
-rw-r--r-- | gtk/spice-channel-priv.h | 5 | ||||
-rw-r--r-- | gtk/spice-channel.c | 140 | ||||
-rw-r--r-- | gtk/spice-session.c | 190 | ||||
-rw-r--r-- | gtk/spice-session.h | 5 | ||||
-rw-r--r-- | gtk/spicy.c | 36 |
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"); |