From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1LXd5P-0003W6-TU for qemu-devel@nongnu.org; Thu, 12 Feb 2009 10:02:44 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1LXd5O-0003Tz-3R for qemu-devel@nongnu.org; Thu, 12 Feb 2009 10:02:43 -0500 Received: from [199.232.76.173] (port=37763 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1LXd5N-0003Th-Ro for qemu-devel@nongnu.org; Thu, 12 Feb 2009 10:02:41 -0500 Received: from mx1.redhat.com ([66.187.233.31]:33314) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1LXd5M-00079c-Qn for qemu-devel@nongnu.org; Thu, 12 Feb 2009 10:02:41 -0500 Received: from int-mx1.corp.redhat.com (int-mx1.corp.redhat.com [172.16.52.254]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id n1CF2ejJ024936 for ; Thu, 12 Feb 2009 10:02:40 -0500 Received: from file.fab.redhat.com (file.fab.redhat.com [10.33.63.6]) by int-mx1.corp.redhat.com (8.13.1/8.13.1) with ESMTP id n1CF2fRf023711 for ; Thu, 12 Feb 2009 10:02:41 -0500 Received: from file.fab.redhat.com (localhost.localdomain [127.0.0.1]) by file.fab.redhat.com (8.13.1/8.13.1) with ESMTP id n1CF2c8O018524 for ; Thu, 12 Feb 2009 15:02:38 GMT Received: (from berrange@localhost) by file.fab.redhat.com (8.13.1/8.13.1/Submit) id n1CF2cb3018520 for qemu-devel@nongnu.org; Thu, 12 Feb 2009 15:02:38 GMT Date: Thu, 12 Feb 2009 15:02:38 +0000 From: "Daniel P. Berrange" Subject: Re: [Qemu-devel] PATCH: 3/7: Split out VNC TLS auth code to separate file Message-ID: <20090212150238.GS9894@redhat.com> References: <20090212145302.GO9894@redhat.com> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20090212145302.GO9894@redhat.com> Reply-To: "Daniel P. Berrange" , qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org This patch refactors the existing TLS code to make the main VNC code more managable. The code moves to two new files - vnc-tls.c: generic helpers for TLS handshake & credential setup - vnc-auth-vencrypt.c: the actual VNC TLS authentication mechanism. The reason for this split is that there are other TLS based auth mechanisms which we may like to use in the future. These can all share the same vnc-tls.c routines. In addition this will facilitate anyone who may want to port the vnc-tls.c file to allow for choice of GNUTLS & NSS for impl. The TLS state is moved out of the VncState struct, and into a separate VncStateTLS struct, defined in vnc-tls.h. This is then referenced from the main VncState. End size of the struct is the same, but it keeps things a little more managable. The vnc.h file gains a bunch more function prototypes, for functions in vnc.c that were previously static, but now need to be accessed from the separate auth code files. Signed-off-by: Daniel P. Berrange Makefile | 11 b/vnc-auth-vencrypt.c | 167 ++++++++++++++ b/vnc-auth-vencrypt.h | 33 ++ b/vnc-tls.c | 420 +++++++++++++++++++++++++++++++++++ b/vnc-tls.h | 70 +++++ vnc.c | 588 +++----------------------------------------------- vnc.h | 78 ++++-- 7 files changed, 786 insertions(+), 581 deletions(-) Daniel diff -r 95baa502a2b8 Makefile --- a/Makefile Thu Feb 12 12:28:21 2009 +0000 +++ b/Makefile Thu Feb 12 12:28:24 2009 +0000 @@ -145,6 +145,9 @@ ifdef CONFIG_CURSES OBJS+=curses.o endif OBJS+=vnc.o d3des.o +ifdef CONFIG_VNC_TLS +OBJS+=vnc-tls.o vnc-auth-vencrypt.o +endif ifdef CONFIG_COCOA OBJS+=cocoa.o @@ -168,10 +171,16 @@ sdl.o: sdl.c keymaps.h sdl_keysym.h sdl.o audio/sdlaudio.o: CFLAGS += $(SDL_CFLAGS) -vnc.o: vnc.c keymaps.h sdl_keysym.h vnchextile.h d3des.c d3des.h +vnc.h: vnc-tls.h vnc-auth-vencrypt.h keymaps.h + +vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h vnc.o: CFLAGS += $(CONFIG_VNC_TLS_CFLAGS) +vnc-tls.o: vnc-tls.c vnc.h + +vnc-auth-vencrypt.o: vnc-auth-vencrypt.c vnc.h + curses.o: curses.c keymaps.h curses_keys.h bt-host.o: CFLAGS += $(CONFIG_BLUEZ_CFLAGS) diff -r 95baa502a2b8 vnc-auth-vencrypt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vnc-auth-vencrypt.c Thu Feb 12 12:28:24 2009 +0000 @@ -0,0 +1,167 @@ +/* + * QEMU VNC display driver: VeNCrypt authentication setup + * + * Copyright (C) 2006 Anthony Liguori + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc.h" + + +static void start_auth_vencrypt_subauth(VncState *vs) +{ + switch (vs->subauth) { + case VNC_AUTH_VENCRYPT_TLSNONE: + case VNC_AUTH_VENCRYPT_X509NONE: + VNC_DEBUG("Accept TLS auth none\n"); + vnc_write_u32(vs, 0); /* Accept auth completion */ + start_client_init(vs); + break; + + case VNC_AUTH_VENCRYPT_TLSVNC: + case VNC_AUTH_VENCRYPT_X509VNC: + VNC_DEBUG("Start TLS auth VNC\n"); + start_auth_vnc(vs); + break; + + default: /* Should not be possible, but just in case */ + VNC_DEBUG("Reject auth %d\n", vs->auth); + vnc_write_u8(vs, 1); + if (vs->minor >= 8) { + static const char err[] = "Unsupported authentication type"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_client_error(vs); + } +} + +static void vnc_tls_handshake_io(void *opaque); + +static int vnc_start_vencrypt_handshake(struct VncState *vs) { + int ret; + + if ((ret = gnutls_handshake(vs->tls.session)) < 0) { + if (!gnutls_error_is_fatal(ret)) { + VNC_DEBUG("Handshake interrupted (blocking)\n"); + if (!gnutls_record_get_direction(vs->tls.session)) + qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs); + else + qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs); + return 0; + } + VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret)); + vnc_client_error(vs); + return -1; + } + + if (vs->tls.x509verify) { + if (vnc_tls_validate_certificate(vs) < 0) { + VNC_DEBUG("Client verification failed\n"); + vnc_client_error(vs); + return -1; + } else { + VNC_DEBUG("Client verification passed\n"); + } + } + + VNC_DEBUG("Handshake done, switching to TLS data mode\n"); + vs->tls.wiremode = VNC_WIREMODE_TLS; + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); + + start_auth_vencrypt_subauth(vs); + + return 0; +} + +static void vnc_tls_handshake_io(void *opaque) { + struct VncState *vs = (struct VncState *)opaque; + + VNC_DEBUG("Handshake IO continue\n"); + vnc_start_vencrypt_handshake(vs); +} + + + +#define NEED_X509_AUTH(vs) \ + ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ + (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ + (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN) + + +static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len) +{ + int auth = read_u32(data, 0); + + if (auth != vs->subauth) { + VNC_DEBUG("Rejecting auth %d\n", auth); + vnc_write_u8(vs, 0); /* Reject auth */ + vnc_flush(vs); + vnc_client_error(vs); + } else { + VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth); + vnc_write_u8(vs, 1); /* Accept auth */ + vnc_flush(vs); + + if (vnc_tls_client_setup(vs, NEED_X509_AUTH(vs)) < 0) { + VNC_DEBUG("Failed to setup TLS\n"); + return 0; + } + + VNC_DEBUG("Start TLS VeNCrypt handshake process\n"); + if (vnc_start_vencrypt_handshake(vs) < 0) { + VNC_DEBUG("Failed to start TLS handshake\n"); + return 0; + } + } + return 0; +} + +static int protocol_client_vencrypt_init(VncState *vs, uint8_t *data, size_t len) +{ + if (data[0] != 0 || + data[1] != 2) { + VNC_DEBUG("Unsupported VeNCrypt protocol %d.%d\n", (int)data[0], (int)data[1]); + vnc_write_u8(vs, 1); /* Reject version */ + vnc_flush(vs); + vnc_client_error(vs); + } else { + VNC_DEBUG("Sending allowed auth %d\n", vs->subauth); + vnc_write_u8(vs, 0); /* Accept version */ + vnc_write_u8(vs, 1); /* Number of sub-auths */ + vnc_write_u32(vs, vs->subauth); /* The supported auth */ + vnc_flush(vs); + vnc_read_when(vs, protocol_client_vencrypt_auth, 4); + } + return 0; +} + + +void start_auth_vencrypt(VncState *vs) +{ + /* Send VeNCrypt version 0.2 */ + vnc_write_u8(vs, 0); + vnc_write_u8(vs, 2); + + vnc_read_when(vs, protocol_client_vencrypt_init, 2); +} + diff -r 95baa502a2b8 vnc-auth-vencrypt.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vnc-auth-vencrypt.h Thu Feb 12 12:28:24 2009 +0000 @@ -0,0 +1,33 @@ +/* + * QEMU VNC display driver + * + * Copyright (C) 2006 Anthony Liguori + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef __QEMU_VNC_AUTH_VENCRYPT_H__ +#define __QEMU_VNC_AUTH_VENCRYPT_H__ + +void start_auth_vencrypt(VncState *vs); + +#endif /* __QEMU_VNC_AUTH_VENCRYPT_H__ */ diff -r 95baa502a2b8 vnc-tls.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vnc-tls.c Thu Feb 12 12:28:24 2009 +0000 @@ -0,0 +1,420 @@ +/* + * QEMU VNC display driver: TLS helpers + * + * Copyright (C) 2006 Anthony Liguori + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc.h" +#include "qemu_socket.h" + +#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 +/* Very verbose, so only enabled for _VNC_DEBUG >= 2 */ +static void vnc_debug_gnutls_log(int level, const char* str) { + VNC_DEBUG("%d %s", level, str); +} +#endif /* defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 */ + + +#define DH_BITS 1024 +static gnutls_dh_params_t dh_params; + +static int vnc_tls_initialize(void) +{ + static int tlsinitialized = 0; + + if (tlsinitialized) + return 1; + + if (gnutls_global_init () < 0) + return 0; + + /* XXX ought to re-generate diffie-hellmen params periodically */ + if (gnutls_dh_params_init (&dh_params) < 0) + return 0; + if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0) + return 0; + +#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 + gnutls_global_set_log_level(10); + gnutls_global_set_log_function(vnc_debug_gnutls_log); +#endif + + tlsinitialized = 1; + + return 1; +} + +static ssize_t vnc_tls_push(gnutls_transport_ptr_t transport, + const void *data, + size_t len) { + struct VncState *vs = (struct VncState *)transport; + int ret; + + retry: + ret = send(vs->csock, data, len, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry; + return -1; + } + return ret; +} + + +static ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport, + void *data, + size_t len) { + struct VncState *vs = (struct VncState *)transport; + int ret; + + retry: + ret = recv(vs->csock, data, len, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry; + return -1; + } + return ret; +} + + +static gnutls_anon_server_credentials vnc_tls_initialize_anon_cred(void) +{ + gnutls_anon_server_credentials anon_cred; + int ret; + + if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) { + VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); + return NULL; + } + + gnutls_anon_set_server_dh_params(anon_cred, dh_params); + + return anon_cred; +} + + +static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(VncState *vs) +{ + gnutls_certificate_credentials_t x509_cred; + int ret; + + if (!vs->tls.x509cacert) { + VNC_DEBUG("No CA x509 certificate specified\n"); + return NULL; + } + if (!vs->tls.x509cert) { + VNC_DEBUG("No server x509 certificate specified\n"); + return NULL; + } + if (!vs->tls.x509key) { + VNC_DEBUG("No server private key specified\n"); + return NULL; + } + + if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) { + VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); + return NULL; + } + if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, + vs->tls.x509cacert, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + + if ((ret = gnutls_certificate_set_x509_key_file (x509_cred, + vs->tls.x509cert, + vs->tls.x509key, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + + if (vs->tls.x509cacrl) { + if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, + vs->tls.x509cacrl, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + } + + gnutls_certificate_set_dh_params (x509_cred, dh_params); + + return x509_cred; +} + + +int vnc_tls_validate_certificate(struct VncState *vs) +{ + int ret; + unsigned int status; + const gnutls_datum_t *certs; + unsigned int nCerts, i; + time_t now; + + VNC_DEBUG("Validating client certificate\n"); + if ((ret = gnutls_certificate_verify_peers2 (vs->tls.session, &status)) < 0) { + VNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret)); + return -1; + } + + if ((now = time(NULL)) == ((time_t)-1)) { + return -1; + } + + if (status != 0) { + if (status & GNUTLS_CERT_INVALID) + VNC_DEBUG("The certificate is not trusted.\n"); + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + VNC_DEBUG("The certificate hasn't got a known issuer.\n"); + + if (status & GNUTLS_CERT_REVOKED) + VNC_DEBUG("The certificate has been revoked.\n"); + + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + VNC_DEBUG("The certificate uses an insecure algorithm\n"); + + return -1; + } else { + VNC_DEBUG("Certificate is valid!\n"); + } + + /* Only support x509 for now */ + if (gnutls_certificate_type_get(vs->tls.session) != GNUTLS_CRT_X509) + return -1; + + if (!(certs = gnutls_certificate_get_peers(vs->tls.session, &nCerts))) + return -1; + + for (i = 0 ; i < nCerts ; i++) { + gnutls_x509_crt_t cert; + VNC_DEBUG ("Checking certificate chain %d\n", i); + if (gnutls_x509_crt_init (&cert) < 0) + return -1; + + if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) { + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_expiration_time (cert) < now) { + VNC_DEBUG("The certificate has expired\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > now) { + VNC_DEBUG("The certificate is not yet activated\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > now) { + VNC_DEBUG("The certificate is not yet activated\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + gnutls_x509_crt_deinit (cert); + } + + return 0; +} + + +int vnc_tls_client_setup(struct VncState *vs, int needX509Creds) { + static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; + static const int protocol_priority[]= { GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0 }; + static const int kx_anon[] = {GNUTLS_KX_ANON_DH, 0}; + static const int kx_x509[] = {GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0}; + + VNC_DEBUG("Do TLS setup\n"); + if (vnc_tls_initialize() < 0) { + VNC_DEBUG("Failed to init TLS\n"); + vnc_client_error(vs); + return -1; + } + if (vs->tls.session == NULL) { + if (gnutls_init(&vs->tls.session, GNUTLS_SERVER) < 0) { + vnc_client_error(vs); + return -1; + } + + if (gnutls_set_default_priority(vs->tls.session) < 0) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_kx_set_priority(vs->tls.session, needX509Creds ? kx_x509 : kx_anon) < 0) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_certificate_type_set_priority(vs->tls.session, cert_type_priority) < 0) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_protocol_set_priority(vs->tls.session, protocol_priority) < 0) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + vnc_client_error(vs); + return -1; + } + + if (needX509Creds) { + gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(vs); + if (!x509_cred) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + vnc_client_error(vs); + return -1; + } + if (gnutls_credentials_set(vs->tls.session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + gnutls_certificate_free_credentials(x509_cred); + vnc_client_error(vs); + return -1; + } + if (vs->tls.x509verify) { + VNC_DEBUG("Requesting a client certificate\n"); + gnutls_certificate_server_set_request (vs->tls.session, GNUTLS_CERT_REQUEST); + } + + } else { + gnutls_anon_server_credentials anon_cred = vnc_tls_initialize_anon_cred(); + if (!anon_cred) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + vnc_client_error(vs); + return -1; + } + if (gnutls_credentials_set(vs->tls.session, GNUTLS_CRD_ANON, anon_cred) < 0) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + gnutls_anon_free_server_credentials(anon_cred); + vnc_client_error(vs); + return -1; + } + } + + gnutls_transport_set_ptr(vs->tls.session, (gnutls_transport_ptr_t)vs); + gnutls_transport_set_push_function(vs->tls.session, vnc_tls_push); + gnutls_transport_set_pull_function(vs->tls.session, vnc_tls_pull); + } + return 0; +} + + +void vnc_tls_client_cleanup(struct VncState *vs) +{ + if (vs->tls.session) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + } + vs->tls.wiremode = VNC_WIREMODE_CLEAR; +} + + + +static int vnc_set_x509_credential(VncState *vs, + const char *certdir, + const char *filename, + char **cred, + int ignoreMissing) +{ + struct stat sb; + + if (*cred) { + qemu_free(*cred); + *cred = NULL; + } + + *cred = qemu_malloc(strlen(certdir) + strlen(filename) + 2); + + strcpy(*cred, certdir); + strcat(*cred, "/"); + strcat(*cred, filename); + + VNC_DEBUG("Check %s\n", *cred); + if (stat(*cred, &sb) < 0) { + qemu_free(*cred); + *cred = NULL; + if (ignoreMissing && errno == ENOENT) + return 0; + return -1; + } + + return 0; +} + + +#define X509_CA_CERT_FILE "ca-cert.pem" +#define X509_CA_CRL_FILE "ca-crl.pem" +#define X509_SERVER_KEY_FILE "server-key.pem" +#define X509_SERVER_CERT_FILE "server-cert.pem" + + +int vnc_tls_set_x509_creds_dir(VncState *vs, + const char *certdir) +{ + if (vnc_set_x509_credential(vs, certdir, X509_CA_CERT_FILE, &vs->tls.x509cacert, 0) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_CA_CRL_FILE, &vs->tls.x509cacrl, 1) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_SERVER_CERT_FILE, &vs->tls.x509cert, 0) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_SERVER_KEY_FILE, &vs->tls.x509key, 0) < 0) + goto cleanup; + + return 0; + + cleanup: + qemu_free(vs->tls.x509cacert); + qemu_free(vs->tls.x509cacrl); + qemu_free(vs->tls.x509cert); + qemu_free(vs->tls.x509key); + vs->tls.x509cacert = vs->tls.x509cacrl = vs->tls.x509cert = vs->tls.x509key = NULL; + return -1; +} + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * End: + */ diff -r 95baa502a2b8 vnc-tls.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vnc-tls.h Thu Feb 12 12:28:24 2009 +0000 @@ -0,0 +1,70 @@ +/* + * QEMU VNC display driver. TLS helpers + * + * Copyright (C) 2006 Anthony Liguori + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef __QEMU_VNC_TLS_H__ +#define __QEMU_VNC_TLS_H__ + +#include +#include + +enum { + VNC_WIREMODE_CLEAR, + VNC_WIREMODE_TLS, +}; + +struct VncStateTLS { + int x509verify; /* Non-zero if server requests & validates client cert */ + + /* Paths to x509 certs/keys */ + char *x509cacert; + char *x509cacrl; + char *x509cert; + char *x509key; + + /* Whether data is being TLS encrypted yet */ + int wiremode; + gnutls_session_t session; +}; + +int vnc_tls_client_setup(VncState *vs, int x509Creds); +void vnc_tls_client_cleanup(VncState *vs); + +int vnc_tls_validate_certificate(VncState *vs); + +int vnc_tls_set_x509_creds_dir(VncState *vs, + const char *path); + + +#endif /* __QEMU_VNC_TLS_H__ */ + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * End: + */ diff -r 95baa502a2b8 vnc.c --- a/vnc.c Thu Feb 12 12:28:21 2009 +0000 +++ b/vnc.c Thu Feb 12 12:28:24 2009 +0000 @@ -35,21 +35,6 @@ #include "vnc_keysym.h" #include "d3des.h" -// #define _VNC_DEBUG 1 - -#ifdef _VNC_DEBUG -#define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) - -#if defined(CONFIG_VNC_TLS) && _VNC_DEBUG >= 2 -/* Very verbose, so only enabled for _VNC_DEBUG >= 2 */ -static void vnc_debug_gnutls_log(int level, const char* str) { - VNC_DEBUG("%d %s", level, str); -} -#endif /* CONFIG_VNC_TLS && _VNC_DEBUG */ -#else -#define VNC_DEBUG(fmt, ...) do { } while (0) -#endif - #define count_bits(c, v) { \ for (c = 0; v; v >>= 1) \ { \ @@ -198,14 +183,7 @@ static inline uint32_t vnc_has_feature(V 3) resolutions > 1024 */ -static void vnc_write(VncState *vs, const void *data, size_t len); -static void vnc_write_u32(VncState *vs, uint32_t value); -static void vnc_write_s32(VncState *vs, int32_t value); -static void vnc_write_u16(VncState *vs, uint16_t value); -static void vnc_write_u8(VncState *vs, uint8_t value); -static void vnc_flush(VncState *vs); static void vnc_update_client(void *opaque); -static void vnc_client_read(void *opaque); static void vnc_colordepth(DisplayState *ds); @@ -840,11 +818,7 @@ static int vnc_client_io_error(VncState buffer_reset(&vs->output); vs->need_update = 0; #ifdef CONFIG_VNC_TLS - if (vs->tls_session) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - } - vs->wiremode = VNC_WIREMODE_CLEAR; + vnc_tls_client_cleanup(vs); #endif /* CONFIG_VNC_TLS */ audio_del(vs); return 0; @@ -852,19 +826,20 @@ static int vnc_client_io_error(VncState return ret; } -static void vnc_client_error(VncState *vs) + +void vnc_client_error(VncState *vs) { vnc_client_io_error(vs, -1, EINVAL); } -static void vnc_client_write(void *opaque) +void vnc_client_write(void *opaque) { long ret; VncState *vs = opaque; #ifdef CONFIG_VNC_TLS - if (vs->tls_session) { - ret = gnutls_write(vs->tls_session, vs->output.buffer, vs->output.offset); + if (vs->tls.session) { + ret = gnutls_write(vs->tls.session, vs->output.buffer, vs->output.offset); if (ret < 0) { if (ret == GNUTLS_E_AGAIN) errno = EAGAIN; @@ -887,13 +862,13 @@ static void vnc_client_write(void *opaqu } } -static void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) +void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) { vs->read_handler = func; vs->read_handler_expect = expecting; } -static void vnc_client_read(void *opaque) +void vnc_client_read(void *opaque) { VncState *vs = opaque; long ret; @@ -901,8 +876,8 @@ static void vnc_client_read(void *opaque buffer_reserve(&vs->input, 4096); #ifdef CONFIG_VNC_TLS - if (vs->tls_session) { - ret = gnutls_read(vs->tls_session, buffer_end(&vs->input), 4096); + if (vs->tls.session) { + ret = gnutls_read(vs->tls.session, buffer_end(&vs->input), 4096); if (ret < 0) { if (ret == GNUTLS_E_AGAIN) errno = EAGAIN; @@ -936,7 +911,7 @@ static void vnc_client_read(void *opaque } } -static void vnc_write(VncState *vs, const void *data, size_t len) +void vnc_write(VncState *vs, const void *data, size_t len) { buffer_reserve(&vs->output, len); @@ -947,12 +922,12 @@ static void vnc_write(VncState *vs, cons buffer_append(&vs->output, data, len); } -static void vnc_write_s32(VncState *vs, int32_t value) +void vnc_write_s32(VncState *vs, int32_t value) { vnc_write_u32(vs, *(uint32_t *)&value); } -static void vnc_write_u32(VncState *vs, uint32_t value) +void vnc_write_u32(VncState *vs, uint32_t value) { uint8_t buf[4]; @@ -964,7 +939,7 @@ static void vnc_write_u32(VncState *vs, vnc_write(vs, buf, 4); } -static void vnc_write_u16(VncState *vs, uint16_t value) +void vnc_write_u16(VncState *vs, uint16_t value) { uint8_t buf[2]; @@ -974,74 +949,39 @@ static void vnc_write_u16(VncState *vs, vnc_write(vs, buf, 2); } -static void vnc_write_u8(VncState *vs, uint8_t value) +void vnc_write_u8(VncState *vs, uint8_t value) { vnc_write(vs, (char *)&value, 1); } -static void vnc_flush(VncState *vs) +void vnc_flush(VncState *vs) { if (vs->output.offset) vnc_client_write(vs); } -static uint8_t read_u8(uint8_t *data, size_t offset) +uint8_t read_u8(uint8_t *data, size_t offset) { return data[offset]; } -static uint16_t read_u16(uint8_t *data, size_t offset) +uint16_t read_u16(uint8_t *data, size_t offset) { return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); } -static int32_t read_s32(uint8_t *data, size_t offset) +int32_t read_s32(uint8_t *data, size_t offset) { return (int32_t)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); } -static uint32_t read_u32(uint8_t *data, size_t offset) +uint32_t read_u32(uint8_t *data, size_t offset) { return ((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); } -#ifdef CONFIG_VNC_TLS -static ssize_t vnc_tls_push(gnutls_transport_ptr_t transport, - const void *data, - size_t len) { - struct VncState *vs = (struct VncState *)transport; - int ret; - - retry: - ret = send(vs->csock, data, len, 0); - if (ret < 0) { - if (errno == EINTR) - goto retry; - return -1; - } - return ret; -} - - -static ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport, - void *data, - size_t len) { - struct VncState *vs = (struct VncState *)transport; - int ret; - - retry: - ret = recv(vs->csock, data, len, 0); - if (ret < 0) { - if (errno == EINTR) - goto retry; - return -1; - } - return ret; -} -#endif /* CONFIG_VNC_TLS */ - static void client_cut_text(VncState *vs, size_t len, uint8_t *text) { } @@ -1627,6 +1567,11 @@ static int protocol_client_init(VncState return 0; } +void start_client_init(VncState *vs) +{ + vnc_read_when(vs, protocol_client_init, 1); +} + static void make_challenge(VncState *vs) { int i; @@ -1682,12 +1627,12 @@ static int protocol_client_auth_vnc(VncS vnc_write_u32(vs, 0); /* Accept auth */ vnc_flush(vs); - vnc_read_when(vs, protocol_client_init, 1); + start_client_init(vs); } return 0; } -static int start_auth_vnc(VncState *vs) +void start_auth_vnc(VncState *vs) { make_challenge(vs); /* Send client a 'random' challenge */ @@ -1695,411 +1640,9 @@ static int start_auth_vnc(VncState *vs) vnc_flush(vs); vnc_read_when(vs, protocol_client_auth_vnc, sizeof(vs->challenge)); - return 0; } -#ifdef CONFIG_VNC_TLS -#define DH_BITS 1024 -static gnutls_dh_params_t dh_params; - -static int vnc_tls_initialize(void) -{ - static int tlsinitialized = 0; - - if (tlsinitialized) - return 1; - - if (gnutls_global_init () < 0) - return 0; - - /* XXX ought to re-generate diffie-hellmen params periodically */ - if (gnutls_dh_params_init (&dh_params) < 0) - return 0; - if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0) - return 0; - -#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 - gnutls_global_set_log_level(10); - gnutls_global_set_log_function(vnc_debug_gnutls_log); -#endif - - tlsinitialized = 1; - - return 1; -} - -static gnutls_anon_server_credentials vnc_tls_initialize_anon_cred(void) -{ - gnutls_anon_server_credentials anon_cred; - int ret; - - if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) { - VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); - return NULL; - } - - gnutls_anon_set_server_dh_params(anon_cred, dh_params); - - return anon_cred; -} - - -static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(VncState *vs) -{ - gnutls_certificate_credentials_t x509_cred; - int ret; - - if (!vs->x509cacert) { - VNC_DEBUG("No CA x509 certificate specified\n"); - return NULL; - } - if (!vs->x509cert) { - VNC_DEBUG("No server x509 certificate specified\n"); - return NULL; - } - if (!vs->x509key) { - VNC_DEBUG("No server private key specified\n"); - return NULL; - } - - if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) { - VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); - return NULL; - } - if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, - vs->x509cacert, - GNUTLS_X509_FMT_PEM)) < 0) { - VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret)); - gnutls_certificate_free_credentials(x509_cred); - return NULL; - } - - if ((ret = gnutls_certificate_set_x509_key_file (x509_cred, - vs->x509cert, - vs->x509key, - GNUTLS_X509_FMT_PEM)) < 0) { - VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret)); - gnutls_certificate_free_credentials(x509_cred); - return NULL; - } - - if (vs->x509cacrl) { - if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, - vs->x509cacrl, - GNUTLS_X509_FMT_PEM)) < 0) { - VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret)); - gnutls_certificate_free_credentials(x509_cred); - return NULL; - } - } - - gnutls_certificate_set_dh_params (x509_cred, dh_params); - - return x509_cred; -} - -static int vnc_validate_certificate(struct VncState *vs) -{ - int ret; - unsigned int status; - const gnutls_datum_t *certs; - unsigned int nCerts, i; - time_t now; - - VNC_DEBUG("Validating client certificate\n"); - if ((ret = gnutls_certificate_verify_peers2 (vs->tls_session, &status)) < 0) { - VNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret)); - return -1; - } - - if ((now = time(NULL)) == ((time_t)-1)) { - return -1; - } - - if (status != 0) { - if (status & GNUTLS_CERT_INVALID) - VNC_DEBUG("The certificate is not trusted.\n"); - - if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) - VNC_DEBUG("The certificate hasn't got a known issuer.\n"); - - if (status & GNUTLS_CERT_REVOKED) - VNC_DEBUG("The certificate has been revoked.\n"); - - if (status & GNUTLS_CERT_INSECURE_ALGORITHM) - VNC_DEBUG("The certificate uses an insecure algorithm\n"); - - return -1; - } else { - VNC_DEBUG("Certificate is valid!\n"); - } - - /* Only support x509 for now */ - if (gnutls_certificate_type_get(vs->tls_session) != GNUTLS_CRT_X509) - return -1; - - if (!(certs = gnutls_certificate_get_peers(vs->tls_session, &nCerts))) - return -1; - - for (i = 0 ; i < nCerts ; i++) { - gnutls_x509_crt_t cert; - VNC_DEBUG ("Checking certificate chain %d\n", i); - if (gnutls_x509_crt_init (&cert) < 0) - return -1; - - if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) { - gnutls_x509_crt_deinit (cert); - return -1; - } - - if (gnutls_x509_crt_get_expiration_time (cert) < now) { - VNC_DEBUG("The certificate has expired\n"); - gnutls_x509_crt_deinit (cert); - return -1; - } - - if (gnutls_x509_crt_get_activation_time (cert) > now) { - VNC_DEBUG("The certificate is not yet activated\n"); - gnutls_x509_crt_deinit (cert); - return -1; - } - - if (gnutls_x509_crt_get_activation_time (cert) > now) { - VNC_DEBUG("The certificate is not yet activated\n"); - gnutls_x509_crt_deinit (cert); - return -1; - } - - gnutls_x509_crt_deinit (cert); - } - - return 0; -} - - -static int start_auth_vencrypt_subauth(VncState *vs) -{ - switch (vs->subauth) { - case VNC_AUTH_VENCRYPT_TLSNONE: - case VNC_AUTH_VENCRYPT_X509NONE: - VNC_DEBUG("Accept TLS auth none\n"); - vnc_write_u32(vs, 0); /* Accept auth completion */ - vnc_read_when(vs, protocol_client_init, 1); - break; - - case VNC_AUTH_VENCRYPT_TLSVNC: - case VNC_AUTH_VENCRYPT_X509VNC: - VNC_DEBUG("Start TLS auth VNC\n"); - return start_auth_vnc(vs); - - default: /* Should not be possible, but just in case */ - VNC_DEBUG("Reject auth %d\n", vs->auth); - vnc_write_u8(vs, 1); - if (vs->minor >= 8) { - static const char err[] = "Unsupported authentication type"; - vnc_write_u32(vs, sizeof(err)); - vnc_write(vs, err, sizeof(err)); - } - vnc_client_error(vs); - } - - return 0; -} - -static void vnc_handshake_io(void *opaque); - -static int vnc_continue_handshake(struct VncState *vs) { - int ret; - - if ((ret = gnutls_handshake(vs->tls_session)) < 0) { - if (!gnutls_error_is_fatal(ret)) { - VNC_DEBUG("Handshake interrupted (blocking)\n"); - if (!gnutls_record_get_direction(vs->tls_session)) - qemu_set_fd_handler(vs->csock, vnc_handshake_io, NULL, vs); - else - qemu_set_fd_handler(vs->csock, NULL, vnc_handshake_io, vs); - return 0; - } - VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret)); - vnc_client_error(vs); - return -1; - } - - if (vs->x509verify) { - if (vnc_validate_certificate(vs) < 0) { - VNC_DEBUG("Client verification failed\n"); - vnc_client_error(vs); - return -1; - } else { - VNC_DEBUG("Client verification passed\n"); - } - } - - VNC_DEBUG("Handshake done, switching to TLS data mode\n"); - vs->wiremode = VNC_WIREMODE_TLS; - qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); - - return start_auth_vencrypt_subauth(vs); -} - -static void vnc_handshake_io(void *opaque) { - struct VncState *vs = (struct VncState *)opaque; - - VNC_DEBUG("Handshake IO continue\n"); - vnc_continue_handshake(vs); -} - -#define NEED_X509_AUTH(vs) \ - ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ - (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ - (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN) - - -static int vnc_start_tls(struct VncState *vs) { - static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; - static const int protocol_priority[]= { GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0 }; - static const int kx_anon[] = {GNUTLS_KX_ANON_DH, 0}; - static const int kx_x509[] = {GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0}; - - VNC_DEBUG("Do TLS setup\n"); - if (vnc_tls_initialize() < 0) { - VNC_DEBUG("Failed to init TLS\n"); - vnc_client_error(vs); - return -1; - } - if (vs->tls_session == NULL) { - if (gnutls_init(&vs->tls_session, GNUTLS_SERVER) < 0) { - vnc_client_error(vs); - return -1; - } - - if (gnutls_set_default_priority(vs->tls_session) < 0) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - vnc_client_error(vs); - return -1; - } - - if (gnutls_kx_set_priority(vs->tls_session, NEED_X509_AUTH(vs) ? kx_x509 : kx_anon) < 0) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - vnc_client_error(vs); - return -1; - } - - if (gnutls_certificate_type_set_priority(vs->tls_session, cert_type_priority) < 0) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - vnc_client_error(vs); - return -1; - } - - if (gnutls_protocol_set_priority(vs->tls_session, protocol_priority) < 0) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - vnc_client_error(vs); - return -1; - } - - if (NEED_X509_AUTH(vs)) { - gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(vs); - if (!x509_cred) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - vnc_client_error(vs); - return -1; - } - if (gnutls_credentials_set(vs->tls_session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - gnutls_certificate_free_credentials(x509_cred); - vnc_client_error(vs); - return -1; - } - if (vs->x509verify) { - VNC_DEBUG("Requesting a client certificate\n"); - gnutls_certificate_server_set_request (vs->tls_session, GNUTLS_CERT_REQUEST); - } - - } else { - gnutls_anon_server_credentials anon_cred = vnc_tls_initialize_anon_cred(); - if (!anon_cred) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - vnc_client_error(vs); - return -1; - } - if (gnutls_credentials_set(vs->tls_session, GNUTLS_CRD_ANON, anon_cred) < 0) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - gnutls_anon_free_server_credentials(anon_cred); - vnc_client_error(vs); - return -1; - } - } - - gnutls_transport_set_ptr(vs->tls_session, (gnutls_transport_ptr_t)vs); - gnutls_transport_set_push_function(vs->tls_session, vnc_tls_push); - gnutls_transport_set_pull_function(vs->tls_session, vnc_tls_pull); - } - - VNC_DEBUG("Start TLS handshake process\n"); - return vnc_continue_handshake(vs); -} - -static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len) -{ - int auth = read_u32(data, 0); - - if (auth != vs->subauth) { - VNC_DEBUG("Rejecting auth %d\n", auth); - vnc_write_u8(vs, 0); /* Reject auth */ - vnc_flush(vs); - vnc_client_error(vs); - } else { - VNC_DEBUG("Accepting auth %d, starting handshake\n", auth); - vnc_write_u8(vs, 1); /* Accept auth */ - vnc_flush(vs); - - if (vnc_start_tls(vs) < 0) { - VNC_DEBUG("Failed to complete TLS\n"); - return 0; - } - } - return 0; -} - -static int protocol_client_vencrypt_init(VncState *vs, uint8_t *data, size_t len) -{ - if (data[0] != 0 || - data[1] != 2) { - VNC_DEBUG("Unsupported VeNCrypt protocol %d.%d\n", (int)data[0], (int)data[1]); - vnc_write_u8(vs, 1); /* Reject version */ - vnc_flush(vs); - vnc_client_error(vs); - } else { - VNC_DEBUG("Sending allowed auth %d\n", vs->subauth); - vnc_write_u8(vs, 0); /* Accept version */ - vnc_write_u8(vs, 1); /* Number of sub-auths */ - vnc_write_u32(vs, vs->subauth); /* The supported auth */ - vnc_flush(vs); - vnc_read_when(vs, protocol_client_vencrypt_auth, 4); - } - return 0; -} - -static int start_auth_vencrypt(VncState *vs) -{ - /* Send VeNCrypt version 0.2 */ - vnc_write_u8(vs, 0); - vnc_write_u8(vs, 2); - - vnc_read_when(vs, protocol_client_vencrypt_init, 2); - return 0; -} -#endif /* CONFIG_VNC_TLS */ - static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len) { /* We only advertise 1 auth scheme at a time, so client @@ -2122,17 +1665,19 @@ static int protocol_client_auth(VncState vnc_write_u32(vs, 0); /* Accept auth completion */ vnc_flush(vs); } - vnc_read_when(vs, protocol_client_init, 1); + start_client_init(vs); break; case VNC_AUTH_VNC: VNC_DEBUG("Start VNC auth\n"); - return start_auth_vnc(vs); + start_auth_vnc(vs); + break; #ifdef CONFIG_VNC_TLS case VNC_AUTH_VENCRYPT: VNC_DEBUG("Accept VeNCrypt auth\n");; - return start_auth_vencrypt(vs); + start_auth_vencrypt(vs); + break; #endif /* CONFIG_VNC_TLS */ default: /* Should not be possible, but just in case */ @@ -2185,7 +1730,7 @@ static int protocol_version(VncState *vs VNC_DEBUG("Tell client auth none\n"); vnc_write_u32(vs, vs->auth); vnc_flush(vs); - vnc_read_when(vs, protocol_client_init, 1); + start_client_init(vs); } else if (vs->auth == VNC_AUTH_VNC) { VNC_DEBUG("Tell client VNC auth\n"); vnc_write_u32(vs, vs->auth); @@ -2282,61 +1827,6 @@ void vnc_display_init(DisplayState *ds) vs->as.endianness = 0; } -#ifdef CONFIG_VNC_TLS -static int vnc_set_x509_credential(VncState *vs, - const char *certdir, - const char *filename, - char **cred, - int ignoreMissing) -{ - struct stat sb; - - if (*cred) { - qemu_free(*cred); - *cred = NULL; - } - - *cred = qemu_malloc(strlen(certdir) + strlen(filename) + 2); - - strcpy(*cred, certdir); - strcat(*cred, "/"); - strcat(*cred, filename); - - VNC_DEBUG("Check %s\n", *cred); - if (stat(*cred, &sb) < 0) { - qemu_free(*cred); - *cred = NULL; - if (ignoreMissing && errno == ENOENT) - return 0; - return -1; - } - - return 0; -} - -static int vnc_set_x509_credential_dir(VncState *vs, - const char *certdir) -{ - if (vnc_set_x509_credential(vs, certdir, X509_CA_CERT_FILE, &vs->x509cacert, 0) < 0) - goto cleanup; - if (vnc_set_x509_credential(vs, certdir, X509_CA_CRL_FILE, &vs->x509cacrl, 1) < 0) - goto cleanup; - if (vnc_set_x509_credential(vs, certdir, X509_SERVER_CERT_FILE, &vs->x509cert, 0) < 0) - goto cleanup; - if (vnc_set_x509_credential(vs, certdir, X509_SERVER_KEY_FILE, &vs->x509key, 0) < 0) - goto cleanup; - - return 0; - - cleanup: - qemu_free(vs->x509cacert); - qemu_free(vs->x509cacrl); - qemu_free(vs->x509cert); - qemu_free(vs->x509key); - vs->x509cacert = vs->x509cacrl = vs->x509cert = vs->x509key = NULL; - return -1; -} -#endif /* CONFIG_VNC_TLS */ void vnc_display_close(DisplayState *ds) { @@ -2361,17 +1851,13 @@ void vnc_display_close(DisplayState *ds) buffer_reset(&vs->output); vs->need_update = 0; #ifdef CONFIG_VNC_TLS - if (vs->tls_session) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - } - vs->wiremode = VNC_WIREMODE_CLEAR; + vnc_tls_client_cleanup(vs); #endif /* CONFIG_VNC_TLS */ } vs->auth = VNC_AUTH_INVALID; #ifdef CONFIG_VNC_TLS vs->subauth = VNC_AUTH_INVALID; - vs->x509verify = 0; + vs->tls.x509verify = 0; #endif audio_del(vs); } @@ -2428,7 +1914,7 @@ int vnc_display_open(DisplayState *ds, c char *start, *end; x509 = 1; /* Require x509 certificates */ if (strncmp(options, "x509verify", 10) == 0) - vs->x509verify = 1; /* ...and verify client certs */ + vs->tls.x509verify = 1; /* ...and verify client certs */ /* Now check for 'x509=/some/path' postfix * and use that to setup x509 certificate/key paths */ @@ -2439,7 +1925,7 @@ int vnc_display_open(DisplayState *ds, c char *path = qemu_strndup(start + 1, len); VNC_DEBUG("Trying certificate path '%s'\n", path); - if (vnc_set_x509_credential_dir(vs, path) < 0) { + if (vnc_tls_set_x509_creds_dir(vs, path) < 0) { fprintf(stderr, "Failed to find x509 certificates/keys in %s\n", path); qemu_free(path); qemu_free(vs->display); diff -r 95baa502a2b8 vnc.h --- a/vnc.h Thu Feb 12 12:28:21 2009 +0000 +++ b/vnc.h Thu Feb 12 12:28:24 2009 +0000 @@ -33,19 +33,22 @@ #include "audio/audio.h" #include -#ifdef CONFIG_VNC_TLS -#include -#include -#endif /* CONFIG_VNC_TLS */ - #include "keymaps.h" +// #define _VNC_DEBUG 1 + /***************************************************************************** * * Core data structures * *****************************************************************************/ +#ifdef _VNC_DEBUG +#define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define VNC_DEBUG(fmt, ...) do { } while (0) +#endif + #define VNC_MAX_WIDTH 2048 @@ -74,6 +77,11 @@ typedef struct Buffer uint8_t *buffer; } Buffer; +#ifdef CONFIG_VNC_TLS +#include "vnc-tls.h" +#include "vnc-auth-vencrypt.h" +#endif + struct VncState { QEMUTimer *timer; @@ -99,21 +107,11 @@ struct VncState char *password; int auth; #ifdef CONFIG_VNC_TLS - int subauth; - int x509verify; - - char *x509cacert; - char *x509cacrl; - char *x509cert; - char *x509key; + int subauth; /* Used by VeNCrypt */ + struct VncStateTLS tls; #endif char challenge[VNC_AUTH_CHALLENGE_SIZE]; -#ifdef CONFIG_VNC_TLS - int wiremode; - gnutls_session_t tls_session; -#endif - Buffer output; Buffer input; kbd_layout_t *kbd_layout; @@ -153,12 +151,6 @@ enum { VNC_AUTH_VENCRYPT = 19 }; -#ifdef CONFIG_VNC_TLS -enum { - VNC_WIREMODE_CLEAR, - VNC_WIREMODE_TLS, -}; - enum { VNC_AUTH_VENCRYPT_PLAIN = 256, VNC_AUTH_VENCRYPT_TLSNONE = 257, @@ -169,12 +161,6 @@ enum { VNC_AUTH_VENCRYPT_X509PLAIN = 262, }; -#define X509_CA_CERT_FILE "ca-cert.pem" -#define X509_CA_CRL_FILE "ca-crl.pem" -#define X509_SERVER_KEY_FILE "server-key.pem" -#define X509_SERVER_CERT_FILE "server-cert.pem" - -#endif /* CONFIG_VNC_TLS */ /***************************************************************************** * @@ -243,4 +229,38 @@ enum { #define VNC_FEATURE_TIGHT_MASK (1 << VNC_FEATURE_TIGHT) #define VNC_FEATURE_ZLIB_MASK (1 << VNC_FEATURE_ZLIB) + +/***************************************************************************** + * + * Internal APIs + * + *****************************************************************************/ + +/* Event loop functions */ +void vnc_client_read(void *opaque); +void vnc_client_write(void *opaque); + + +/* Protocol I/O functions */ +void vnc_write(VncState *vs, const void *data, size_t len); +void vnc_write_u32(VncState *vs, uint32_t value); +void vnc_write_s32(VncState *vs, int32_t value); +void vnc_write_u16(VncState *vs, uint16_t value); +void vnc_write_u8(VncState *vs, uint8_t value); +void vnc_flush(VncState *vs); +void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting); + + +/* Buffer I/O functions */ +uint8_t read_u8(uint8_t *data, size_t offset); +uint16_t read_u16(uint8_t *data, size_t offset); +int32_t read_s32(uint8_t *data, size_t offset); +uint32_t read_u32(uint8_t *data, size_t offset); + +/* Protocol stage functions */ +void vnc_client_error(VncState *vs); + +void start_client_init(VncState *vs); +void start_auth_vnc(VncState *vs); + #endif /* __VNC_H */ -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|