From 3e305e4a4752f70c0b5c3cf5b43ec957881714f7 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Thu, 6 Aug 2015 14:39:32 +0100 Subject: [PATCH] ui: convert VNC server to use QCryptoTLSSession Switch VNC server over to using the QCryptoTLSSession object for the TLS session. This removes the direct use of gnutls from the VNC server code. It also removes most knowledge about TLS certificate handling from the VNC server code. This has the nice effect that all the CONFIG_VNC_TLS conditionals go away and the user gets an actual error message when requesting TLS instead of it being silently ignored. With this change, the existing configuration options for enabling TLS with -vnc are deprecated. Old syntax for anon-DH credentials: -vnc hostname:0,tls New syntax: -object tls-creds-anon,id=tls0,endpoint=server \ -vnc hostname:0,tls-creds=tls0 Old syntax for x509 credentials, no client certs: -vnc hostname:0,tls,x509=/path/to/certs New syntax: -object tls-creds-x509,id=tls0,dir=/path/to/certs,endpoint=server,verify-peer=no \ -vnc hostname:0,tls-creds=tls0 Old syntax for x509 credentials, requiring client certs: -vnc hostname:0,tls,x509verify=/path/to/certs New syntax: -object tls-creds-x509,id=tls0,dir=/path/to/certs,endpoint=server,verify-peer=yes \ -vnc hostname:0,tls-creds=tls0 This aligns VNC with the way TLS credentials are to be configured in the future for chardev, nbd and migration backends. It also has the benefit that the same TLS credentials can be shared across multiple VNC server instances, if desired. If someone uses the deprecated syntax, it will internally result in the creation of a 'tls-creds' object with an ID based on the VNC server ID. This allows backwards compat with the CLI syntax, while still deleting all the original TLS code from the VNC server. Signed-off-by: Daniel P. Berrange --- configure | 31 --- qemu-options.hx | 28 ++- ui/Makefile.objs | 2 +- ui/vnc-auth-sasl.c | 36 ++-- ui/vnc-auth-vencrypt.c | 80 ++++--- ui/vnc-tls.c | 474 ----------------------------------------- ui/vnc-tls.h | 69 ------ ui/vnc-ws.c | 82 +++---- ui/vnc-ws.h | 2 - ui/vnc.c | 340 ++++++++++++++++++----------- ui/vnc.h | 15 +- 11 files changed, 361 insertions(+), 798 deletions(-) delete mode 100644 ui/vnc-tls.c delete mode 100644 ui/vnc-tls.h diff --git a/configure b/configure index bdd302c3bf..018ba0b78f 100755 --- a/configure +++ b/configure @@ -242,7 +242,6 @@ vnc="yes" sparse="no" uuid="" vde="" -vnc_tls="" vnc_sasl="" vnc_jpeg="" vnc_png="" @@ -883,10 +882,6 @@ for opt do ;; --disable-strip) strip_opt="no" ;; - --disable-vnc-tls) vnc_tls="no" - ;; - --enable-vnc-tls) vnc_tls="yes" - ;; --disable-vnc-sasl) vnc_sasl="no" ;; --enable-vnc-sasl) vnc_sasl="yes" @@ -2409,28 +2404,6 @@ EOF fi fi -########################################## -# VNC TLS/WS detection -if test "$vnc" = "yes" -a "$vnc_tls" != "no" ; then - cat > $TMPC < -int main(void) { gnutls_session_t s; gnutls_init(&s, GNUTLS_SERVER); return 0; } -EOF - vnc_tls_cflags=`$pkg_config --cflags gnutls 2> /dev/null` - vnc_tls_libs=`$pkg_config --libs gnutls 2> /dev/null` - if compile_prog "$vnc_tls_cflags" "$vnc_tls_libs" ; then - if test "$vnc_tls" != "no" ; then - vnc_tls=yes - fi - libs_softmmu="$vnc_tls_libs $libs_softmmu" - QEMU_CFLAGS="$QEMU_CFLAGS $vnc_tls_cflags" - else - if test "$vnc_tls" = "yes" ; then - feature_not_found "vnc-tls" "Install gnutls devel" - fi - vnc_tls=no - fi -fi ########################################## # VNC SASL detection @@ -4601,7 +4574,6 @@ echo "Block whitelist (ro) $block_drv_ro_whitelist" echo "VirtFS support $virtfs" echo "VNC support $vnc" if test "$vnc" = "yes" ; then - echo "VNC TLS support $vnc_tls" echo "VNC SASL support $vnc_sasl" echo "VNC JPEG support $vnc_jpeg" echo "VNC PNG support $vnc_png" @@ -4810,9 +4782,6 @@ echo "CONFIG_BDRV_RO_WHITELIST=$block_drv_ro_whitelist" >> $config_host_mak if test "$vnc" = "yes" ; then echo "CONFIG_VNC=y" >> $config_host_mak fi -if test "$vnc_tls" = "yes" ; then - echo "CONFIG_VNC_TLS=y" >> $config_host_mak -fi if test "$vnc_sasl" = "yes" ; then echo "CONFIG_VNC_SASL=y" >> $config_host_mak fi diff --git a/qemu-options.hx b/qemu-options.hx index 3f2e25bf24..7e147b8aac 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1217,8 +1217,9 @@ By definition the Websocket port is 5700+@var{display}. If @var{host} is specified connections will only be allowed from this host. As an alternative the Websocket port could be specified by using @code{websocket}=@var{port}. -TLS encryption for the Websocket connection is supported if the required -certificates are specified with the VNC option @option{x509}. +If no TLS credentials are provided, the websocket connection runs in +unencrypted mode. If TLS credentials are provided, the websocket connection +requires encrypted client connections. @item password @@ -1239,6 +1240,20 @@ date and time). You can also use keywords "now" or "never" for the expiration time to allow password to expire immediately or never expire. +@item tls-creds=@var{ID} + +Provides the ID of a set of TLS credentials to use to secure the +VNC server. They will apply to both the normal VNC server socket +and the websocket socket (if enabled). Setting TLS credentials +will cause the VNC server socket to enable the VeNCrypt auth +mechanism. The credentials should have been previously created +using the @option{-object tls-creds} argument. + +The @option{tls-creds} parameter obsoletes the @option{tls}, +@option{x509}, and @option{x509verify} options, and as such +it is not permitted to set both new and old type options at +the same time. + @item tls Require that client use TLS when communicating with the VNC server. This @@ -1246,6 +1261,9 @@ uses anonymous TLS credentials so is susceptible to a man-in-the-middle attack. It is recommended that this option be combined with either the @option{x509} or @option{x509verify} options. +This option is now deprecated in favor of using the @option{tls-creds} +argument. + @item x509=@var{/path/to/certificate/dir} Valid if @option{tls} is specified. Require that x509 credentials are used @@ -1255,6 +1273,9 @@ to provide authentication of the client when this is used. The path following this option specifies where the x509 certificates are to be loaded from. See the @ref{vnc_security} section for details on generating certificates. +This option is now deprecated in favour of using the @option{tls-creds} +argument. + @item x509verify=@var{/path/to/certificate/dir} Valid if @option{tls} is specified. Require that x509 credentials are used @@ -1268,6 +1289,9 @@ path following this option specifies where the x509 certificates are to be loaded from. See the @ref{vnc_security} section for details on generating certificates. +This option is now deprecated in favour of using the @option{tls-creds} +argument. + @item sasl Require that the client use SASL to authenticate with the VNC server. diff --git a/ui/Makefile.objs b/ui/Makefile.objs index c62d4d9722..0034fbb49f 100644 --- a/ui/Makefile.objs +++ b/ui/Makefile.objs @@ -2,7 +2,7 @@ vnc-obj-y += vnc.o vnc-obj-y += vnc-enc-zlib.o vnc-enc-hextile.o vnc-obj-y += vnc-enc-tight.o vnc-palette.o vnc-obj-y += vnc-enc-zrle.o -vnc-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o +vnc-obj-y += vnc-auth-vencrypt.o vnc-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o vnc-obj-y += vnc-ws.o vnc-obj-y += vnc-jobs.o diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c index 62a5fc4bf1..fc732bdbac 100644 --- a/ui/vnc-auth-sasl.c +++ b/ui/vnc-auth-sasl.c @@ -525,21 +525,24 @@ void start_auth_sasl(VncState *vs) goto authabort; } -#ifdef CONFIG_VNC_TLS /* Inform SASL that we've got an external SSF layer from TLS/x509 */ if (vs->auth == VNC_AUTH_VENCRYPT && vs->subauth == VNC_AUTH_VENCRYPT_X509SASL) { - gnutls_cipher_algorithm_t cipher; + Error *local_err = NULL; + int keysize; sasl_ssf_t ssf; - cipher = gnutls_cipher_get(vs->tls.session); - if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) { - VNC_DEBUG("%s", "cannot TLS get cipher size\n"); + keysize = qcrypto_tls_session_get_key_size(vs->tls, + &local_err); + if (keysize < 0) { + VNC_DEBUG("cannot TLS get cipher size: %s\n", + error_get_pretty(local_err)); + error_free(local_err); sasl_dispose(&vs->sasl.conn); vs->sasl.conn = NULL; goto authabort; } - ssf *= 8; /* tls key size is bytes, sasl wants bits */ + ssf = keysize * CHAR_BIT; /* tls key size is bytes, sasl wants bits */ err = sasl_setprop(vs->sasl.conn, SASL_SSF_EXTERNAL, &ssf); if (err != SASL_OK) { @@ -549,20 +552,19 @@ void start_auth_sasl(VncState *vs) vs->sasl.conn = NULL; goto authabort; } - } else -#endif /* CONFIG_VNC_TLS */ + } else { vs->sasl.wantSSF = 1; + } memset (&secprops, 0, sizeof secprops); - /* Inform SASL that we've got an external SSF layer from TLS */ - if (vs->vd->is_unix -#ifdef CONFIG_VNC_TLS - /* Disable SSF, if using TLS+x509+SASL only. TLS without x509 - is not sufficiently strong */ - || (vs->auth == VNC_AUTH_VENCRYPT && - vs->subauth == VNC_AUTH_VENCRYPT_X509SASL) -#endif /* CONFIG_VNC_TLS */ - ) { + /* Inform SASL that we've got an external SSF layer from TLS. + * + * Disable SSF, if using TLS+x509+SASL only. TLS without x509 + * is not sufficiently strong + */ + if (vs->vd->is_unix || + (vs->auth == VNC_AUTH_VENCRYPT && + vs->subauth == VNC_AUTH_VENCRYPT_X509SASL)) { /* If we've got TLS or UNIX domain sock, we don't care about SSF */ secprops.min_ssf = 0; secprops.max_ssf = 0; diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c index 8fc965b4ad..44ac2fae63 100644 --- a/ui/vnc-auth-vencrypt.c +++ b/ui/vnc-auth-vencrypt.c @@ -67,38 +67,42 @@ static void vnc_tls_handshake_io(void *opaque); static int vnc_start_vencrypt_handshake(VncState *vs) { - int ret; + Error *err = NULL; - 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 (qcrypto_tls_session_handshake(vs->tls, &err) < 0) { + goto error; } - if (vs->vd->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"); + switch (qcrypto_tls_session_get_handshake_status(vs->tls)) { + case QCRYPTO_TLS_HANDSHAKE_COMPLETE: + VNC_DEBUG("Handshake done, checking credentials\n"); + if (qcrypto_tls_session_check_credentials(vs->tls, &err) < 0) { + goto error; } + VNC_DEBUG("Client verification passed, starting TLS I/O\n"); + qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); + + start_auth_vencrypt_subauth(vs); + break; + + case QCRYPTO_TLS_HANDSHAKE_RECVING: + VNC_DEBUG("Handshake interrupted (blocking read)\n"); + qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs); + break; + + case QCRYPTO_TLS_HANDSHAKE_SENDING: + VNC_DEBUG("Handshake interrupted (blocking write)\n"); + qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs); + break; } - VNC_DEBUG("Handshake done, switching to TLS data mode\n"); - qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); - - start_auth_vencrypt_subauth(vs); - return 0; + + error: + VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err)); + error_free(err); + vnc_client_error(vs); + return -1; } static void vnc_tls_handshake_io(void *opaque) @@ -110,14 +114,6 @@ static void vnc_tls_handshake_io(void *opaque) } - -#define NEED_X509_AUTH(vs) \ - ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ - (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ - (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN || \ - (vs)->subauth == VNC_AUTH_VENCRYPT_X509SASL) - - static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len) { int auth = read_u32(data, 0); @@ -128,15 +124,29 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len vnc_flush(vs); vnc_client_error(vs); } else { + Error *err = NULL; 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"); + vs->tls = qcrypto_tls_session_new(vs->vd->tlscreds, + NULL, + vs->vd->tlsaclname, + QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, + &err); + if (!vs->tls) { + VNC_DEBUG("Failed to setup TLS %s\n", + error_get_pretty(err)); + error_free(err); + vnc_client_error(vs); return 0; } + qcrypto_tls_session_set_callbacks(vs->tls, + vnc_tls_push, + vnc_tls_pull, + vs); + VNC_DEBUG("Start TLS VeNCrypt handshake process\n"); if (vnc_start_vencrypt_handshake(vs) < 0) { VNC_DEBUG("Failed to start TLS handshake\n"); diff --git a/ui/vnc-tls.c b/ui/vnc-tls.c deleted file mode 100644 index 028fc4db1f..0000000000 --- a/ui/vnc-tls.c +++ /dev/null @@ -1,474 +0,0 @@ -/* - * 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 "qemu-x509.h" -#include "vnc.h" -#include "qemu/sockets.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-hellman 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) { - VncState *vs = (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) { - VncState *vs = (VncState *)transport; - int ret; - - retry: - ret = qemu_recv(vs->csock, data, len, 0); - if (ret < 0) { - if (errno == EINTR) - goto retry; - return -1; - } - return ret; -} - - -static gnutls_anon_server_credentials_t vnc_tls_initialize_anon_cred(void) -{ - gnutls_anon_server_credentials_t 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(VncDisplay *vd) -{ - gnutls_certificate_credentials_t x509_cred; - int ret; - - if (!vd->tls.x509cacert) { - VNC_DEBUG("No CA x509 certificate specified\n"); - return NULL; - } - if (!vd->tls.x509cert) { - VNC_DEBUG("No server x509 certificate specified\n"); - return NULL; - } - if (!vd->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, - vd->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, - vd->tls.x509cert, - vd->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 (vd->tls.x509cacrl) { - if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, - vd->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(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; - } - - if (i == 0) { - size_t dnameSize = 1024; - vs->tls.dname = g_malloc(dnameSize); - requery: - if ((ret = gnutls_x509_crt_get_dn (cert, vs->tls.dname, &dnameSize)) != 0) { - if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { - vs->tls.dname = g_realloc(vs->tls.dname, dnameSize); - goto requery; - } - gnutls_x509_crt_deinit (cert); - VNC_DEBUG("Cannot get client distinguished name: %s", - gnutls_strerror (ret)); - return -1; - } - - if (vs->vd->tls.x509verify) { - int allow; - if (!vs->vd->tls.acl) { - VNC_DEBUG("no ACL activated, allowing access"); - gnutls_x509_crt_deinit (cert); - continue; - } - - allow = qemu_acl_party_is_allowed(vs->vd->tls.acl, - vs->tls.dname); - - VNC_DEBUG("TLS x509 ACL check for %s is %s\n", - vs->tls.dname, allow ? "allowed" : "denied"); - if (!allow) { - gnutls_x509_crt_deinit (cert); - return -1; - } - } - } - - gnutls_x509_crt_deinit (cert); - } - - return 0; -} - -#if defined(GNUTLS_VERSION_NUMBER) && \ - GNUTLS_VERSION_NUMBER >= 0x020200 /* 2.2.0 */ - -static int vnc_set_gnutls_priority(gnutls_session_t s, int x509) -{ - const char *priority = x509 ? "NORMAL" : "NORMAL:+ANON-DH"; - int rc; - - rc = gnutls_priority_set_direct(s, priority, NULL); - if (rc != GNUTLS_E_SUCCESS) { - return -1; - } - return 0; -} - -#else - -static int vnc_set_gnutls_priority(gnutls_session_t s, int x509) -{ - static const int cert_types[] = { GNUTLS_CRT_X509, 0 }; - static const int protocols[] = { - 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 - }; - int rc; - - rc = gnutls_kx_set_priority(s, x509 ? kx_x509 : kx_anon); - if (rc != GNUTLS_E_SUCCESS) { - return -1; - } - - rc = gnutls_certificate_type_set_priority(s, cert_types); - if (rc != GNUTLS_E_SUCCESS) { - return -1; - } - - rc = gnutls_protocol_set_priority(s, protocols); - if (rc != GNUTLS_E_SUCCESS) { - return -1; - } - return 0; -} - -#endif - -int vnc_tls_client_setup(VncState *vs, - int needX509Creds) { - 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 (vnc_set_gnutls_priority(vs->tls.session, needX509Creds) < 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->vd); - 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->vd->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_t 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(VncState *vs) -{ - if (vs->tls.session) { - gnutls_deinit(vs->tls.session); - vs->tls.session = NULL; - } - g_free(vs->tls.dname); -} - - - -static int vnc_set_x509_credential(VncDisplay *vd, - const char *certdir, - const char *filename, - char **cred, - int ignoreMissing) -{ - struct stat sb; - - g_free(*cred); - *cred = g_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) { - g_free(*cred); - *cred = NULL; - if (ignoreMissing && errno == ENOENT) - return 0; - return -1; - } - - return 0; -} - - -int vnc_tls_set_x509_creds_dir(VncDisplay *vd, - const char *certdir) -{ - if (vnc_set_x509_credential(vd, certdir, X509_CA_CERT_FILE, &vd->tls.x509cacert, 0) < 0) - goto cleanup; - if (vnc_set_x509_credential(vd, certdir, X509_CA_CRL_FILE, &vd->tls.x509cacrl, 1) < 0) - goto cleanup; - if (vnc_set_x509_credential(vd, certdir, X509_SERVER_CERT_FILE, &vd->tls.x509cert, 0) < 0) - goto cleanup; - if (vnc_set_x509_credential(vd, certdir, X509_SERVER_KEY_FILE, &vd->tls.x509key, 0) < 0) - goto cleanup; - - return 0; - - cleanup: - g_free(vd->tls.x509cacert); - g_free(vd->tls.x509cacrl); - g_free(vd->tls.x509cert); - g_free(vd->tls.x509key); - vd->tls.x509cacert = vd->tls.x509cacrl = vd->tls.x509cert = vd->tls.x509key = NULL; - return -1; -} - diff --git a/ui/vnc-tls.h b/ui/vnc-tls.h deleted file mode 100644 index f9829c7824..0000000000 --- a/ui/vnc-tls.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 - -#include "qemu/acl.h" - -typedef struct VncDisplayTLS VncDisplayTLS; -typedef struct VncStateTLS VncStateTLS; - -/* Server state */ -struct VncDisplayTLS { - int x509verify; /* Non-zero if server requests & validates client cert */ - qemu_acl *acl; - - /* Paths to x509 certs/keys */ - char *x509cacert; - char *x509cacrl; - char *x509cert; - char *x509key; -}; - -/* Per client state */ -struct VncStateTLS { - gnutls_session_t session; - - /* Client's Distinguished Name from the x509 cert */ - char *dname; -}; - -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(VncDisplay *vd, - const char *path); - - -#endif /* __QEMU_VNC_TLS_H__ */ - diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c index b4cb6bde70..175ea50b4b 100644 --- a/ui/vnc-ws.c +++ b/ui/vnc-ws.c @@ -22,60 +22,70 @@ #include "qemu/main-loop.h" #include "crypto/hash.h" -#ifdef CONFIG_VNC_TLS -#include "qemu/sockets.h" - static int vncws_start_tls_handshake(VncState *vs) { - int ret = gnutls_handshake(vs->tls.session); + Error *err = NULL; - if (ret < 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, vncws_tls_handshake_io, - NULL, vs); - } else { - qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io, - vs); - } - return 0; - } - VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret)); - vnc_client_error(vs); - return -1; + if (qcrypto_tls_session_handshake(vs->tls, &err) < 0) { + goto error; } - if (vs->vd->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"); + switch (qcrypto_tls_session_get_handshake_status(vs->tls)) { + case QCRYPTO_TLS_HANDSHAKE_COMPLETE: + VNC_DEBUG("Handshake done, checking credentials\n"); + if (qcrypto_tls_session_check_credentials(vs->tls, &err) < 0) { + goto error; } - } + VNC_DEBUG("Client verification passed, starting TLS I/O\n"); + qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); + break; - VNC_DEBUG("Handshake done, switching to TLS data mode\n"); - qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); + case QCRYPTO_TLS_HANDSHAKE_RECVING: + VNC_DEBUG("Handshake interrupted (blocking read)\n"); + qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs); + break; + + case QCRYPTO_TLS_HANDSHAKE_SENDING: + VNC_DEBUG("Handshake interrupted (blocking write)\n"); + qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io, vs); + break; + } return 0; + + error: + VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err)); + error_free(err); + vnc_client_error(vs); + return -1; } void vncws_tls_handshake_io(void *opaque) { VncState *vs = (VncState *)opaque; + Error *err = NULL; - if (!vs->tls.session) { - VNC_DEBUG("TLS Websocket setup\n"); - if (vnc_tls_client_setup(vs, vs->vd->tls.x509cert != NULL) < 0) { - return; - } + vs->tls = qcrypto_tls_session_new(vs->vd->tlscreds, + NULL, + vs->vd->tlsaclname, + QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, + &err); + if (!vs->tls) { + VNC_DEBUG("Failed to setup TLS %s\n", + error_get_pretty(err)); + error_free(err); + vnc_client_error(vs); + return; } - VNC_DEBUG("Handshake IO continue\n"); + + qcrypto_tls_session_set_callbacks(vs->tls, + vnc_tls_push, + vnc_tls_pull, + vs); + + VNC_DEBUG("Start TLS WS handshake process\n"); vncws_start_tls_handshake(vs); } -#endif /* CONFIG_VNC_TLS */ void vncws_handshake_read(void *opaque) { diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h index 94942258ec..4ab0a8c899 100644 --- a/ui/vnc-ws.h +++ b/ui/vnc-ws.h @@ -72,9 +72,7 @@ enum { WS_OPCODE_PONG = 0xA }; -#ifdef CONFIG_VNC_TLS void vncws_tls_handshake_io(void *opaque); -#endif /* CONFIG_VNC_TLS */ void vncws_handshake_read(void *opaque); long vnc_client_write_ws(VncState *vs); long vnc_client_read_ws(VncState *vs); diff --git a/ui/vnc.c b/ui/vnc.c index 19ad0889d1..d73966afc5 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -41,6 +41,9 @@ #include "ui/input.h" #include "qapi-event.h" #include "crypto/hash.h" +#include "crypto/tlscredsanon.h" +#include "crypto/tlscredsx509.h" +#include "qom/object_interfaces.h" #define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT #define VNC_REFRESH_INTERVAL_INC 50 @@ -222,7 +225,6 @@ static const char *vnc_auth_name(VncDisplay *vd) { case VNC_AUTH_TLS: return "tls"; case VNC_AUTH_VENCRYPT: -#ifdef CONFIG_VNC_TLS switch (vd->subauth) { case VNC_AUTH_VENCRYPT_PLAIN: return "vencrypt+plain"; @@ -245,9 +247,6 @@ static const char *vnc_auth_name(VncDisplay *vd) { default: return "vencrypt"; } -#else - return "vencrypt"; -#endif case VNC_AUTH_SASL: return "sasl"; } @@ -275,13 +274,12 @@ static void vnc_client_cache_auth(VncState *client) return; } -#ifdef CONFIG_VNC_TLS - if (client->tls.session && - client->tls.dname) { - client->info->has_x509_dname = true; - client->info->x509_dname = g_strdup(client->tls.dname); + if (client->tls) { + client->info->x509_dname = + qcrypto_tls_session_get_peer_name(client->tls); + client->info->has_x509_dname = + client->info->x509_dname != NULL; } -#endif #ifdef CONFIG_VNC_SASL if (client->sasl.conn && client->sasl.username) { @@ -358,12 +356,10 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client) info->base->family = inet_netfamily(sa.ss_family); info->base->websocket = client->websocket; -#ifdef CONFIG_VNC_TLS - if (client->tls.session && client->tls.dname) { - info->has_x509_dname = true; - info->x509_dname = g_strdup(client->tls.dname); + if (client->tls) { + info->x509_dname = qcrypto_tls_session_get_peer_name(client->tls); + info->has_x509_dname = info->x509_dname != NULL; } -#endif #ifdef CONFIG_VNC_SASL if (client->sasl.conn && client->sasl.username) { info->has_sasl_username = true; @@ -513,7 +509,6 @@ static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info) break; case VNC_AUTH_VENCRYPT: info->auth = VNC_PRIMARY_AUTH_VENCRYPT; -#ifdef CONFIG_VNC_TLS info->has_vencrypt = true; switch (vd->subauth) { case VNC_AUTH_VENCRYPT_PLAIN: @@ -547,7 +542,6 @@ static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info) info->has_vencrypt = false; break; } -#endif break; case VNC_AUTH_SASL: info->auth = VNC_PRIMARY_AUTH_SASL; @@ -1237,9 +1231,7 @@ void vnc_disconnect_finish(VncState *vs) vnc_tight_clear(vs); vnc_zrle_clear(vs); -#ifdef CONFIG_VNC_TLS - vnc_tls_client_cleanup(vs); -#endif /* CONFIG_VNC_TLS */ + qcrypto_tls_session_free(vs->tls); #ifdef CONFIG_VNC_SASL vnc_sasl_client_cleanup(vs); #endif /* CONFIG_VNC_SASL */ @@ -1300,23 +1292,40 @@ void vnc_client_error(VncState *vs) vnc_disconnect_start(vs); } -#ifdef CONFIG_VNC_TLS -static ssize_t vnc_client_write_tls(gnutls_session_t *session, - const uint8_t *data, - size_t datalen) + +ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque) { - ssize_t ret = gnutls_write(*session, data, datalen); + VncState *vs = opaque; + ssize_t ret; + + retry: + ret = qemu_recv(vs->csock, buf, len, 0); if (ret < 0) { - if (ret == GNUTLS_E_AGAIN) { - errno = EAGAIN; - } else { - errno = EIO; + if (errno == EINTR) { + goto retry; } - ret = -1; + return -1; } return ret; } -#endif /* CONFIG_VNC_TLS */ + + +ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque) +{ + VncState *vs = opaque; + ssize_t ret; + + retry: + ret = send(vs->csock, buf, len, 0); + if (ret < 0) { + if (errno == EINTR) { + goto retry; + } + return -1; + } + return ret; +} + /* * Called to write a chunk of data to the client socket. The data may @@ -1336,17 +1345,20 @@ static ssize_t vnc_client_write_tls(gnutls_session_t *session, ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) { ssize_t ret; -#ifdef CONFIG_VNC_TLS - if (vs->tls.session) { - ret = vnc_client_write_tls(&vs->tls.session, data, datalen); + int err = 0; + if (vs->tls) { + ret = qcrypto_tls_session_write(vs->tls, (const char *)data, datalen); + if (ret < 0) { + err = errno; + } } else { -#endif /* CONFIG_VNC_TLS */ ret = send(vs->csock, (const void *)data, datalen, 0); -#ifdef CONFIG_VNC_TLS + if (ret < 0) { + err = socket_error(); + } } -#endif /* CONFIG_VNC_TLS */ VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret); - return vnc_client_io_error(vs, ret, socket_error()); + return vnc_client_io_error(vs, ret, err); } @@ -1435,22 +1447,6 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) vs->read_handler_expect = expecting; } -#ifdef CONFIG_VNC_TLS -static ssize_t vnc_client_read_tls(gnutls_session_t *session, uint8_t *data, - size_t datalen) -{ - ssize_t ret = gnutls_read(*session, data, datalen); - if (ret < 0) { - if (ret == GNUTLS_E_AGAIN) { - errno = EAGAIN; - } else { - errno = EIO; - } - ret = -1; - } - return ret; -} -#endif /* CONFIG_VNC_TLS */ /* * Called to read a chunk of data from the client socket. The data may @@ -1470,17 +1466,20 @@ static ssize_t vnc_client_read_tls(gnutls_session_t *session, uint8_t *data, ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) { ssize_t ret; -#ifdef CONFIG_VNC_TLS - if (vs->tls.session) { - ret = vnc_client_read_tls(&vs->tls.session, data, datalen); + int err = -1; + if (vs->tls) { + ret = qcrypto_tls_session_read(vs->tls, (char *)data, datalen); + if (ret < 0) { + err = errno; + } } else { -#endif /* CONFIG_VNC_TLS */ ret = qemu_recv(vs->csock, data, datalen, 0); -#ifdef CONFIG_VNC_TLS + if (ret < 0) { + err = socket_error(); + } } -#endif /* CONFIG_VNC_TLS */ VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret); - return vnc_client_io_error(vs, ret, socket_error()); + return vnc_client_io_error(vs, ret, err); } @@ -2631,12 +2630,10 @@ static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len) start_auth_vnc(vs); break; -#ifdef CONFIG_VNC_TLS case VNC_AUTH_VENCRYPT: VNC_DEBUG("Accept VeNCrypt auth\n"); start_auth_vencrypt(vs); break; -#endif /* CONFIG_VNC_TLS */ #ifdef CONFIG_VNC_SASL case VNC_AUTH_SASL: @@ -3033,12 +3030,9 @@ static void vnc_connect(VncDisplay *vd, int csock, qemu_set_nonblock(vs->csock); if (websocket) { vs->websocket = 1; -#ifdef CONFIG_VNC_TLS if (vd->ws_tls) { qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs); - } else -#endif /* CONFIG_VNC_TLS */ - { + } else { qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); } } else @@ -3194,9 +3188,11 @@ static void vnc_display_close(VncDisplay *vs) } vs->auth = VNC_AUTH_INVALID; vs->subauth = VNC_AUTH_INVALID; -#ifdef CONFIG_VNC_TLS - vs->tls.x509verify = 0; -#endif + if (vs->tlscreds) { + object_unparent(OBJECT(vs->tlscreds)); + } + g_free(vs->tlsaclname); + vs->tlsaclname = NULL; } int vnc_display_password(const char *id, const char *password) @@ -3250,6 +3246,10 @@ static QemuOptsList qemu_vnc_opts = { .name = "websocket", .type = QEMU_OPT_STRING, },{ + .name = "tls-creds", + .type = QEMU_OPT_STRING, + },{ + /* Deprecated in favour of tls-creds */ .name = "x509", .type = QEMU_OPT_STRING, },{ @@ -3286,9 +3286,11 @@ static QemuOptsList qemu_vnc_opts = { .name = "sasl", .type = QEMU_OPT_BOOL, },{ + /* Deprecated in favour of tls-creds */ .name = "tls", .type = QEMU_OPT_BOOL, },{ + /* Deprecated in favour of tls-creds */ .name = "x509verify", .type = QEMU_OPT_STRING, },{ @@ -3306,13 +3308,12 @@ static QemuOptsList qemu_vnc_opts = { }; -static void +static int vnc_display_setup_auth(VncDisplay *vs, bool password, bool sasl, - bool tls, - bool x509, - bool websocket) + bool websocket, + Error **errp) { /* * We have a choice of 3 authentication options @@ -3362,17 +3363,24 @@ vnc_display_setup_auth(VncDisplay *vs, * result has the same security characteristics. */ if (password) { - if (tls) { + if (vs->tlscreds) { vs->auth = VNC_AUTH_VENCRYPT; if (websocket) { vs->ws_tls = true; } - if (x509) { + if (object_dynamic_cast(OBJECT(vs->tlscreds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { VNC_DEBUG("Initializing VNC server with x509 password auth\n"); vs->subauth = VNC_AUTH_VENCRYPT_X509VNC; - } else { + } else if (object_dynamic_cast(OBJECT(vs->tlscreds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { VNC_DEBUG("Initializing VNC server with TLS password auth\n"); vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; + } else { + error_setg(errp, + "Unsupported TLS cred type %s", + object_get_typename(OBJECT(vs->tlscreds))); + return -1; } } else { VNC_DEBUG("Initializing VNC server with password auth\n"); @@ -3385,17 +3393,24 @@ vnc_display_setup_auth(VncDisplay *vs, vs->ws_auth = VNC_AUTH_INVALID; } } else if (sasl) { - if (tls) { + if (vs->tlscreds) { vs->auth = VNC_AUTH_VENCRYPT; if (websocket) { vs->ws_tls = true; } - if (x509) { + if (object_dynamic_cast(OBJECT(vs->tlscreds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { VNC_DEBUG("Initializing VNC server with x509 SASL auth\n"); vs->subauth = VNC_AUTH_VENCRYPT_X509SASL; - } else { + } else if (object_dynamic_cast(OBJECT(vs->tlscreds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { VNC_DEBUG("Initializing VNC server with TLS SASL auth\n"); vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL; + } else { + error_setg(errp, + "Unsupported TLS cred type %s", + object_get_typename(OBJECT(vs->tlscreds))); + return -1; } } else { VNC_DEBUG("Initializing VNC server with SASL auth\n"); @@ -3408,17 +3423,24 @@ vnc_display_setup_auth(VncDisplay *vs, vs->ws_auth = VNC_AUTH_INVALID; } } else { - if (tls) { + if (vs->tlscreds) { vs->auth = VNC_AUTH_VENCRYPT; if (websocket) { vs->ws_tls = true; } - if (x509) { + if (object_dynamic_cast(OBJECT(vs->tlscreds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { VNC_DEBUG("Initializing VNC server with x509 no auth\n"); vs->subauth = VNC_AUTH_VENCRYPT_X509NONE; - } else { + } else if (object_dynamic_cast(OBJECT(vs->tlscreds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { VNC_DEBUG("Initializing VNC server with TLS no auth\n"); vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE; + } else { + error_setg(errp, + "Unsupported TLS cred type %s", + object_get_typename(OBJECT(vs->tlscreds))); + return -1; } } else { VNC_DEBUG("Initializing VNC server with no auth\n"); @@ -3431,8 +3453,55 @@ vnc_display_setup_auth(VncDisplay *vs, vs->ws_auth = VNC_AUTH_INVALID; } } + return 0; } + +/* + * Handle back compat with old CLI syntax by creating some + * suitable QCryptoTLSCreds objects + */ +static QCryptoTLSCreds * +vnc_display_create_creds(bool x509, + bool x509verify, + const char *dir, + const char *id, + Error **errp) +{ + gchar *credsid = g_strdup_printf("tlsvnc%s", id); + Object *parent = object_get_objects_root(); + Object *creds; + Error *err = NULL; + + if (x509) { + creds = object_new_with_props(TYPE_QCRYPTO_TLS_CREDS_X509, + parent, + credsid, + &err, + "endpoint", "server", + "dir", dir, + "verify-peer", x509verify ? "yes" : "no", + NULL); + } else { + creds = object_new_with_props(TYPE_QCRYPTO_TLS_CREDS_ANON, + parent, + credsid, + &err, + "endpoint", "server", + NULL); + } + + g_free(credsid); + + if (err) { + error_propagate(errp, err); + return NULL; + } + + return QCRYPTO_TLS_CREDS(creds); +} + + void vnc_display_open(const char *id, Error **errp) { VncDisplay *vs = vnc_display_find(id); @@ -3447,18 +3516,13 @@ void vnc_display_open(const char *id, Error **errp) char *h; bool has_ipv4 = false; bool has_ipv6 = false; + const char *credid; const char *websocket; - bool tls = false, x509 = false; -#ifdef CONFIG_VNC_TLS - const char *path; -#endif bool sasl = false; #ifdef CONFIG_VNC_SASL int saslErr; #endif -#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL) int acl = 0; -#endif int lock_key_sync = 1; if (!vs) { @@ -3539,32 +3603,67 @@ void vnc_display_open(const char *id, Error **errp) goto fail; } #endif /* CONFIG_VNC_SASL */ - tls = qemu_opt_get_bool(opts, "tls", false); -#ifdef CONFIG_VNC_TLS - path = qemu_opt_get(opts, "x509"); - if (!path) { - path = qemu_opt_get(opts, "x509verify"); - if (path) { - vs->tls.x509verify = true; - } - } - if (path) { - x509 = true; - if (vnc_tls_set_x509_creds_dir(vs, path) < 0) { - error_setg(errp, "Failed to find x509 certificates/keys in %s", - path); + credid = qemu_opt_get(opts, "tls-creds"); + if (credid) { + Object *creds; + if (qemu_opt_get(opts, "tls") || + qemu_opt_get(opts, "x509") || + qemu_opt_get(opts, "x509verify")) { + error_setg(errp, + "'credid' parameter is mutually exclusive with " + "'tls', 'x509' and 'x509verify' parameters"); goto fail; } + + creds = object_resolve_path_component( + object_get_objects_root(), credid); + if (!creds) { + error_setg(errp, "No TLS credentials with id '%s'", + credid); + goto fail; + } + vs->tlscreds = (QCryptoTLSCreds *) + object_dynamic_cast(creds, + TYPE_QCRYPTO_TLS_CREDS); + if (!vs->tlscreds) { + error_setg(errp, "Object with id '%s' is not TLS credentials", + credid); + goto fail; + } + object_ref(OBJECT(vs->tlscreds)); + + if (vs->tlscreds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + error_setg(errp, + "Expecting TLS credentials with a server endpoint"); + goto fail; + } + } else { + const char *path; + bool tls = false, x509 = false, x509verify = false; + tls = qemu_opt_get_bool(opts, "tls", false); + if (tls) { + path = qemu_opt_get(opts, "x509"); + + if (path) { + x509 = true; + } else { + path = qemu_opt_get(opts, "x509verify"); + if (path) { + x509 = true; + x509verify = true; + } + } + vs->tlscreds = vnc_display_create_creds(x509, + x509verify, + path, + vs->id, + errp); + if (!vs->tlscreds) { + goto fail; + } + } } -#else /* ! CONFIG_VNC_TLS */ - if (tls) { - error_setg(errp, "VNC TLS auth requires gnutls support"); - goto fail; - } -#endif /* ! CONFIG_VNC_TLS */ -#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL) acl = qemu_opt_get_bool(opts, "acl", false); -#endif share = qemu_opt_get(opts, "share"); if (share) { @@ -3604,19 +3703,14 @@ void vnc_display_open(const char *id, Error **errp) vs->non_adaptive = true; } -#ifdef CONFIG_VNC_TLS - if (acl && x509 && vs->tls.x509verify) { - char *aclname; - + if (acl) { if (strcmp(vs->id, "default") == 0) { - aclname = g_strdup("vnc.x509dname"); + vs->tlsaclname = g_strdup("vnc.x509dname"); } else { - aclname = g_strdup_printf("vnc.%s.x509dname", vs->id); + vs->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vs->id); } - vs->tls.acl = qemu_acl_init(aclname); - g_free(aclname); - } -#endif + qemu_acl_init(vs->tlsaclname); + } #ifdef CONFIG_VNC_SASL if (acl && sasl) { char *aclname; @@ -3631,7 +3725,9 @@ void vnc_display_open(const char *id, Error **errp) } #endif - vnc_display_setup_auth(vs, password, sasl, tls, x509, websocket); + if (vnc_display_setup_auth(vs, password, sasl, websocket, errp) < 0) { + goto fail; + } #ifdef CONFIG_VNC_SASL if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) { diff --git a/ui/vnc.h b/ui/vnc.h index 194ccf74f8..4dd769cddb 100644 --- a/ui/vnc.h +++ b/ui/vnc.h @@ -33,6 +33,7 @@ #include "ui/console.h" #include "audio/audio.h" #include "qemu/bitmap.h" +#include "crypto/tlssession.h" #include #include @@ -101,10 +102,7 @@ typedef void VncSendHextileTile(VncState *vs, typedef struct VncDisplay VncDisplay; -#ifdef CONFIG_VNC_TLS -#include "vnc-tls.h" #include "vnc-auth-vencrypt.h" -#endif #ifdef CONFIG_VNC_SASL #include "vnc-auth-sasl.h" #endif @@ -181,9 +179,8 @@ struct VncDisplay bool ws_tls; /* Used by websockets */ bool lossy; bool non_adaptive; -#ifdef CONFIG_VNC_TLS - VncDisplayTLS tls; -#endif + QCryptoTLSCreds *tlscreds; + char *tlsaclname; #ifdef CONFIG_VNC_SASL VncDisplaySASL sasl; #endif @@ -284,9 +281,7 @@ struct VncState int auth; int subauth; /* Used by VeNCrypt */ char challenge[VNC_AUTH_CHALLENGE_SIZE]; -#ifdef CONFIG_VNC_TLS - VncStateTLS tls; -#endif + QCryptoTLSSession *tls; #ifdef CONFIG_VNC_SASL VncStateSASL sasl; #endif @@ -515,6 +510,8 @@ void vnc_client_write(void *opaque); ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); +ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque); +ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque); /* Protocol I/O functions */ void vnc_write(VncState *vs, const void *data, size_t len);