Patchwork [v2] TLS support for VNC Websockets

login
register
mail settings
Submitter Tim Hardeck
Date April 23, 2013, 2:33 p.m.
Message ID <1366727581-5772-1-git-send-email-thardeck@suse.de>
Download mbox | patch
Permalink /patch/238930/
State New
Headers show

Comments

Tim Hardeck - April 23, 2013, 2:33 p.m.
Added TLS support to the VNC QEMU Websockets implementation.
VNC-TLS needs to be enabled for this feature to be used.

The required certificates are specified as in case of VNC-TLS
with the VNC parameter "x509=<path>".

If the server certificate isn't signed by a rooth authority it needs to
be manually imported in the browser because at least in case of Firefox
and Chrome there is no user dialog, the connection just gets canceled.

As a side note VEncrypt over Websocket doesn't work atm because TLS can't
be stacked in the current implementation. (It also didn't work before)
Nevertheless to my knowledge there is no HTML 5 VNC client which supports
it and the Websocket connection can be encrypted with regular TLS now so
it should be fine for most use cases.

Signed-off-by: Tim Hardeck <thardeck@suse.de>
---

Changes v2

* a peek buffer of 4 Byte is enough
---
 qemu-options.hx |  2 ++
 ui/vnc-tls.c    | 61 +++++++++++++++++++++++++---------------
 ui/vnc-ws.c     | 63 ++++++++++++++++++++++++++++++++++++++++++
 ui/vnc-ws.h     |  3 ++
 ui/vnc.c        | 86 ++++++++++++++++++++++++++++++++++++++++++++-------------
 ui/vnc.h        |  5 +++-
 6 files changed, 178 insertions(+), 42 deletions(-)
Anthony Liguori - April 30, 2013, 2:58 p.m.
Tim Hardeck <thardeck@suse.de> writes:

> Added TLS support to the VNC QEMU Websockets implementation.
> VNC-TLS needs to be enabled for this feature to be used.
>
> The required certificates are specified as in case of VNC-TLS
> with the VNC parameter "x509=<path>".
>
> If the server certificate isn't signed by a rooth authority it needs to
> be manually imported in the browser because at least in case of Firefox
> and Chrome there is no user dialog, the connection just gets canceled.
>
> As a side note VEncrypt over Websocket doesn't work atm because TLS can't
> be stacked in the current implementation. (It also didn't work before)
> Nevertheless to my knowledge there is no HTML 5 VNC client which supports
> it and the Websocket connection can be encrypted with regular TLS now so
> it should be fine for most use cases.
>
> Signed-off-by: Tim Hardeck <thardeck@suse.de>

It would have been nice to perhaps split out a refactoring patch of the
existing TLS code but it looks okay as-is.

I'm not a TLS expert but:

Reviewed-by: Anthony Liguori <aliguori@us.ibm.com>

I'll give folks a couple more days to review and then I'll apply.  Thanks.

Regards,

Anthony Liguori

> ---
>
> Changes v2
>
> * a peek buffer of 4 Byte is enough
> ---
>  qemu-options.hx |  2 ++
>  ui/vnc-tls.c    | 61 +++++++++++++++++++++++++---------------
>  ui/vnc-ws.c     | 63 ++++++++++++++++++++++++++++++++++++++++++
>  ui/vnc-ws.h     |  3 ++
>  ui/vnc.c        | 86 ++++++++++++++++++++++++++++++++++++++++++++-------------
>  ui/vnc.h        |  5 +++-
>  6 files changed, 178 insertions(+), 42 deletions(-)
>
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 7cd6002..e19db6f 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1127,6 +1127,8 @@ 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}.
>  
>  @item password
>  
> diff --git a/ui/vnc-tls.c b/ui/vnc-tls.c
> index 8d4cc8e..50275de 100644
> --- a/ui/vnc-tls.c
> +++ b/ui/vnc-tls.c
> @@ -334,29 +334,38 @@ static int vnc_set_gnutls_priority(gnutls_session_t s, int x509)
>  
>  int vnc_tls_client_setup(struct VncState *vs,
>                           int needX509Creds) {
> +    VncStateTLS *tls;
>  
>      VNC_DEBUG("Do TLS setup\n");
> +#ifdef CONFIG_VNC_WS
> +    if (vs->websocket) {
> +        tls = &vs->ws_tls;
> +    } else
> +#endif /* CONFIG_VNC_WS */
> +    {
> +        tls = &vs->tls;
> +    }
>      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) {
> +    if (tls->session == NULL) {
> +        if (gnutls_init(&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;
> +        if (gnutls_set_default_priority(tls->session) < 0) {
> +            gnutls_deinit(tls->session);
> +            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;
> +        if (vnc_set_gnutls_priority(tls->session, needX509Creds) < 0) {
> +            gnutls_deinit(tls->session);
> +            tls->session = NULL;
>              vnc_client_error(vs);
>              return -1;
>          }
> @@ -364,43 +373,43 @@ int vnc_tls_client_setup(struct VncState *vs,
>          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;
> +                gnutls_deinit(tls->session);
> +                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;
> +            if (gnutls_credentials_set(tls->session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) {
> +                gnutls_deinit(tls->session);
> +                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);
> +                gnutls_certificate_server_set_request (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;
> +                gnutls_deinit(tls->session);
> +                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;
> +            if (gnutls_credentials_set(tls->session, GNUTLS_CRD_ANON, anon_cred) < 0) {
> +                gnutls_deinit(tls->session);
> +                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);
> +        gnutls_transport_set_ptr(tls->session, (gnutls_transport_ptr_t)vs);
> +        gnutls_transport_set_push_function(tls->session, vnc_tls_push);
> +        gnutls_transport_set_pull_function(tls->session, vnc_tls_pull);
>      }
>      return 0;
>  }
> @@ -414,6 +423,14 @@ void vnc_tls_client_cleanup(struct VncState *vs)
>      }
>      vs->tls.wiremode = VNC_WIREMODE_CLEAR;
>      g_free(vs->tls.dname);
> +#ifdef CONFIG_VNC_WS
> +    if (vs->ws_tls.session) {
> +        gnutls_deinit(vs->ws_tls.session);
> +        vs->ws_tls.session = NULL;
> +    }
> +    vs->ws_tls.wiremode = VNC_WIREMODE_CLEAR;
> +    g_free(vs->ws_tls.dname);
> +#endif /* CONFIG_VNC_WS */
>  }
>  
>  
> diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
> index 3e30209..df89315 100644
> --- a/ui/vnc-ws.c
> +++ b/ui/vnc-ws.c
> @@ -20,6 +20,69 @@
>  
>  #include "vnc.h"
>  
> +#ifdef CONFIG_VNC_TLS
> +#include "qemu/sockets.h"
> +
> +static void vncws_tls_handshake_io(void *opaque);
> +
> +static int vncws_start_tls_handshake(struct VncState *vs)
> +{
> +    int ret = gnutls_handshake(vs->ws_tls.session);
> +
> +    if (ret < 0) {
> +        if (!gnutls_error_is_fatal(ret)) {
> +            VNC_DEBUG("Handshake interrupted (blocking)\n");
> +            if (!gnutls_record_get_direction(vs->ws_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;
> +    }
> +
> +    VNC_DEBUG("Handshake done, switching to TLS data mode\n");
> +    vs->ws_tls.wiremode = VNC_WIREMODE_TLS;
> +    qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
> +
> +    return 0;
> +}
> +
> +static void vncws_tls_handshake_io(void *opaque)
> +{
> +    struct VncState *vs = (struct VncState *)opaque;
> +
> +    VNC_DEBUG("Handshake IO continue\n");
> +    vncws_start_tls_handshake(vs);
> +}
> +
> +void vncws_tls_handshake_peek(void *opaque)
> +{
> +    VncState *vs = opaque;
> +    long ret;
> +
> +    if (!vs->ws_tls.session) {
> +        char peek[4];
> +        ret = qemu_recv(vs->csock, peek, sizeof(peek), MSG_PEEK);
> +        if (ret && (strncmp(peek, "\x16", 1) == 0
> +                    || strncmp(peek, "\x80", 1) == 0)) {
> +            VNC_DEBUG("TLS Websocket connection recognized");
> +            vnc_tls_client_setup(vs, 1);
> +            vncws_start_tls_handshake(vs);
> +        } else {
> +            vncws_handshake_read(vs);
> +        }
> +    } else {
> +        qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
> +    }
> +}
> +#endif /* CONFIG_VNC_TLS */
> +
>  void vncws_handshake_read(void *opaque)
>  {
>      VncState *vs = opaque;
> diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
> index 039a587..95c1b0a 100644
> --- a/ui/vnc-ws.h
> +++ b/ui/vnc-ws.h
> @@ -74,6 +74,9 @@ enum {
>      WS_OPCODE_PONG = 0xA
>  };
>  
> +#ifdef CONFIG_VNC_TLS
> +void vncws_tls_handshake_peek(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 5ddb696..99d5e61 100644
> --- a/ui/vnc.c
> +++ b/ui/vnc.c
> @@ -1118,6 +1118,23 @@ void vnc_client_error(VncState *vs)
>      vnc_disconnect_start(vs);
>  }
>  
> +#ifdef CONFIG_VNC_TLS
> +static long vnc_client_write_tls(gnutls_session_t *session,
> +                                 const uint8_t *data,
> +                                 size_t datalen)
> +{
> +    long ret = gnutls_write(*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 write a chunk of data to the client socket. The data may
> @@ -1139,17 +1156,20 @@ long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
>      long ret;
>  #ifdef CONFIG_VNC_TLS
>      if (vs->tls.session) {
> -        ret = gnutls_write(vs->tls.session, data, datalen);
> -        if (ret < 0) {
> -            if (ret == GNUTLS_E_AGAIN)
> -                errno = EAGAIN;
> -            else
> -                errno = EIO;
> -            ret = -1;
> +        ret = vnc_client_write_tls(&vs->tls.session, data, datalen);
> +    } else {
> +#ifdef CONFIG_VNC_WS
> +        if (vs->ws_tls.session) {
> +            ret = vnc_client_write_tls(&vs->ws_tls.session, data, datalen);
> +        } else
> +#endif /* CONFIG_VNC_WS */
> +#endif /* CONFIG_VNC_TLS */
> +        {
> +            ret = send(vs->csock, (const void *)data, datalen, 0);
>          }
> -    } else
> +#ifdef CONFIG_VNC_TLS
> +    }
>  #endif /* CONFIG_VNC_TLS */
> -        ret = send(vs->csock, (const void *)data, datalen, 0);
>      VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret);
>      return vnc_client_io_error(vs, ret, socket_error());
>  }
> @@ -1247,6 +1267,22 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
>      vs->read_handler_expect = expecting;
>  }
>  
> +#ifdef CONFIG_VNC_TLS
> +static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
> +                                size_t datalen)
> +{
> +    long 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
> @@ -1268,17 +1304,20 @@ long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
>      long ret;
>  #ifdef CONFIG_VNC_TLS
>      if (vs->tls.session) {
> -        ret = gnutls_read(vs->tls.session, data, datalen);
> -        if (ret < 0) {
> -            if (ret == GNUTLS_E_AGAIN)
> -                errno = EAGAIN;
> -            else
> -                errno = EIO;
> -            ret = -1;
> +        ret = vnc_client_read_tls(&vs->tls.session, data, datalen);
> +    } else {
> +#ifdef CONFIG_VNC_WS
> +        if (vs->ws_tls.session) {
> +            ret = vnc_client_read_tls(&vs->ws_tls.session, data, datalen);
> +        } else
> +#endif /* CONFIG_VNC_WS */
> +#endif /* CONFIG_VNC_TLS */
> +        {
> +            ret = qemu_recv(vs->csock, data, datalen, 0);
>          }
> -    } else
> +#ifdef CONFIG_VNC_TLS
> +    }
>  #endif /* CONFIG_VNC_TLS */
> -        ret = qemu_recv(vs->csock, data, datalen, 0);
>      VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret);
>      return vnc_client_io_error(vs, ret, socket_error());
>  }
> @@ -2736,7 +2775,16 @@ static void vnc_connect(VncDisplay *vd, int csock, int skipauth, bool websocket)
>  #ifdef CONFIG_VNC_WS
>      if (websocket) {
>          vs->websocket = 1;
> -        qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
> +#ifdef CONFIG_VNC_TLS
> +        if (vd->tls.x509cert) {
> +            qemu_set_fd_handler2(vs->csock, NULL, vncws_tls_handshake_peek,
> +                                 NULL, vs);
> +        } else
> +#endif /* CONFIG_VNC_TLS */
> +        {
> +            qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read,
> +                                 NULL, vs);
> +        }
>      } else
>  #endif /* CONFIG_VNC_WS */
>      {
> diff --git a/ui/vnc.h b/ui/vnc.h
> index 58e002e..e54a89a 100644
> --- a/ui/vnc.h
> +++ b/ui/vnc.h
> @@ -278,9 +278,12 @@ struct VncState
>      VncStateSASL sasl;
>  #endif
>  #ifdef CONFIG_VNC_WS
> +#ifdef CONFIG_VNC_TLS
> +    VncStateTLS ws_tls;
> +#endif /* CONFIG_VNC_TLS */
>      bool encode_ws;
>      bool websocket;
> -#endif
> +#endif /* CONFIG_VNC_WS */
>  
>      QObject *info;
>  
> -- 
> 1.8.1.4
Anthony Liguori - May 3, 2013, 6:59 p.m.
Applied.  Thanks.

Regards,

Anthony Liguori

Patch

diff --git a/qemu-options.hx b/qemu-options.hx
index 7cd6002..e19db6f 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1127,6 +1127,8 @@  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}.
 
 @item password
 
diff --git a/ui/vnc-tls.c b/ui/vnc-tls.c
index 8d4cc8e..50275de 100644
--- a/ui/vnc-tls.c
+++ b/ui/vnc-tls.c
@@ -334,29 +334,38 @@  static int vnc_set_gnutls_priority(gnutls_session_t s, int x509)
 
 int vnc_tls_client_setup(struct VncState *vs,
                          int needX509Creds) {
+    VncStateTLS *tls;
 
     VNC_DEBUG("Do TLS setup\n");
+#ifdef CONFIG_VNC_WS
+    if (vs->websocket) {
+        tls = &vs->ws_tls;
+    } else
+#endif /* CONFIG_VNC_WS */
+    {
+        tls = &vs->tls;
+    }
     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) {
+    if (tls->session == NULL) {
+        if (gnutls_init(&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;
+        if (gnutls_set_default_priority(tls->session) < 0) {
+            gnutls_deinit(tls->session);
+            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;
+        if (vnc_set_gnutls_priority(tls->session, needX509Creds) < 0) {
+            gnutls_deinit(tls->session);
+            tls->session = NULL;
             vnc_client_error(vs);
             return -1;
         }
@@ -364,43 +373,43 @@  int vnc_tls_client_setup(struct VncState *vs,
         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;
+                gnutls_deinit(tls->session);
+                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;
+            if (gnutls_credentials_set(tls->session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) {
+                gnutls_deinit(tls->session);
+                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);
+                gnutls_certificate_server_set_request (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;
+                gnutls_deinit(tls->session);
+                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;
+            if (gnutls_credentials_set(tls->session, GNUTLS_CRD_ANON, anon_cred) < 0) {
+                gnutls_deinit(tls->session);
+                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);
+        gnutls_transport_set_ptr(tls->session, (gnutls_transport_ptr_t)vs);
+        gnutls_transport_set_push_function(tls->session, vnc_tls_push);
+        gnutls_transport_set_pull_function(tls->session, vnc_tls_pull);
     }
     return 0;
 }
@@ -414,6 +423,14 @@  void vnc_tls_client_cleanup(struct VncState *vs)
     }
     vs->tls.wiremode = VNC_WIREMODE_CLEAR;
     g_free(vs->tls.dname);
+#ifdef CONFIG_VNC_WS
+    if (vs->ws_tls.session) {
+        gnutls_deinit(vs->ws_tls.session);
+        vs->ws_tls.session = NULL;
+    }
+    vs->ws_tls.wiremode = VNC_WIREMODE_CLEAR;
+    g_free(vs->ws_tls.dname);
+#endif /* CONFIG_VNC_WS */
 }
 
 
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index 3e30209..df89315 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -20,6 +20,69 @@ 
 
 #include "vnc.h"
 
+#ifdef CONFIG_VNC_TLS
+#include "qemu/sockets.h"
+
+static void vncws_tls_handshake_io(void *opaque);
+
+static int vncws_start_tls_handshake(struct VncState *vs)
+{
+    int ret = gnutls_handshake(vs->ws_tls.session);
+
+    if (ret < 0) {
+        if (!gnutls_error_is_fatal(ret)) {
+            VNC_DEBUG("Handshake interrupted (blocking)\n");
+            if (!gnutls_record_get_direction(vs->ws_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;
+    }
+
+    VNC_DEBUG("Handshake done, switching to TLS data mode\n");
+    vs->ws_tls.wiremode = VNC_WIREMODE_TLS;
+    qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
+
+    return 0;
+}
+
+static void vncws_tls_handshake_io(void *opaque)
+{
+    struct VncState *vs = (struct VncState *)opaque;
+
+    VNC_DEBUG("Handshake IO continue\n");
+    vncws_start_tls_handshake(vs);
+}
+
+void vncws_tls_handshake_peek(void *opaque)
+{
+    VncState *vs = opaque;
+    long ret;
+
+    if (!vs->ws_tls.session) {
+        char peek[4];
+        ret = qemu_recv(vs->csock, peek, sizeof(peek), MSG_PEEK);
+        if (ret && (strncmp(peek, "\x16", 1) == 0
+                    || strncmp(peek, "\x80", 1) == 0)) {
+            VNC_DEBUG("TLS Websocket connection recognized");
+            vnc_tls_client_setup(vs, 1);
+            vncws_start_tls_handshake(vs);
+        } else {
+            vncws_handshake_read(vs);
+        }
+    } else {
+        qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
+    }
+}
+#endif /* CONFIG_VNC_TLS */
+
 void vncws_handshake_read(void *opaque)
 {
     VncState *vs = opaque;
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
index 039a587..95c1b0a 100644
--- a/ui/vnc-ws.h
+++ b/ui/vnc-ws.h
@@ -74,6 +74,9 @@  enum {
     WS_OPCODE_PONG = 0xA
 };
 
+#ifdef CONFIG_VNC_TLS
+void vncws_tls_handshake_peek(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 5ddb696..99d5e61 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1118,6 +1118,23 @@  void vnc_client_error(VncState *vs)
     vnc_disconnect_start(vs);
 }
 
+#ifdef CONFIG_VNC_TLS
+static long vnc_client_write_tls(gnutls_session_t *session,
+                                 const uint8_t *data,
+                                 size_t datalen)
+{
+    long ret = gnutls_write(*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 write a chunk of data to the client socket. The data may
@@ -1139,17 +1156,20 @@  long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
     long ret;
 #ifdef CONFIG_VNC_TLS
     if (vs->tls.session) {
-        ret = gnutls_write(vs->tls.session, data, datalen);
-        if (ret < 0) {
-            if (ret == GNUTLS_E_AGAIN)
-                errno = EAGAIN;
-            else
-                errno = EIO;
-            ret = -1;
+        ret = vnc_client_write_tls(&vs->tls.session, data, datalen);
+    } else {
+#ifdef CONFIG_VNC_WS
+        if (vs->ws_tls.session) {
+            ret = vnc_client_write_tls(&vs->ws_tls.session, data, datalen);
+        } else
+#endif /* CONFIG_VNC_WS */
+#endif /* CONFIG_VNC_TLS */
+        {
+            ret = send(vs->csock, (const void *)data, datalen, 0);
         }
-    } else
+#ifdef CONFIG_VNC_TLS
+    }
 #endif /* CONFIG_VNC_TLS */
-        ret = send(vs->csock, (const void *)data, datalen, 0);
     VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret);
     return vnc_client_io_error(vs, ret, socket_error());
 }
@@ -1247,6 +1267,22 @@  void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
     vs->read_handler_expect = expecting;
 }
 
+#ifdef CONFIG_VNC_TLS
+static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
+                                size_t datalen)
+{
+    long 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
@@ -1268,17 +1304,20 @@  long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
     long ret;
 #ifdef CONFIG_VNC_TLS
     if (vs->tls.session) {
-        ret = gnutls_read(vs->tls.session, data, datalen);
-        if (ret < 0) {
-            if (ret == GNUTLS_E_AGAIN)
-                errno = EAGAIN;
-            else
-                errno = EIO;
-            ret = -1;
+        ret = vnc_client_read_tls(&vs->tls.session, data, datalen);
+    } else {
+#ifdef CONFIG_VNC_WS
+        if (vs->ws_tls.session) {
+            ret = vnc_client_read_tls(&vs->ws_tls.session, data, datalen);
+        } else
+#endif /* CONFIG_VNC_WS */
+#endif /* CONFIG_VNC_TLS */
+        {
+            ret = qemu_recv(vs->csock, data, datalen, 0);
         }
-    } else
+#ifdef CONFIG_VNC_TLS
+    }
 #endif /* CONFIG_VNC_TLS */
-        ret = qemu_recv(vs->csock, data, datalen, 0);
     VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret);
     return vnc_client_io_error(vs, ret, socket_error());
 }
@@ -2736,7 +2775,16 @@  static void vnc_connect(VncDisplay *vd, int csock, int skipauth, bool websocket)
 #ifdef CONFIG_VNC_WS
     if (websocket) {
         vs->websocket = 1;
-        qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
+#ifdef CONFIG_VNC_TLS
+        if (vd->tls.x509cert) {
+            qemu_set_fd_handler2(vs->csock, NULL, vncws_tls_handshake_peek,
+                                 NULL, vs);
+        } else
+#endif /* CONFIG_VNC_TLS */
+        {
+            qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read,
+                                 NULL, vs);
+        }
     } else
 #endif /* CONFIG_VNC_WS */
     {
diff --git a/ui/vnc.h b/ui/vnc.h
index 58e002e..e54a89a 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -278,9 +278,12 @@  struct VncState
     VncStateSASL sasl;
 #endif
 #ifdef CONFIG_VNC_WS
+#ifdef CONFIG_VNC_TLS
+    VncStateTLS ws_tls;
+#endif /* CONFIG_VNC_TLS */
     bool encode_ws;
     bool websocket;
-#endif
+#endif /* CONFIG_VNC_WS */
 
     QObject *info;