summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--qemu-options.hx2
-rw-r--r--ui/vnc-tls.c61
-rw-r--r--ui/vnc-ws.c63
-rw-r--r--ui/vnc-ws.h3
-rw-r--r--ui/vnc.c86
-rw-r--r--ui/vnc.h5
6 files changed, 178 insertions, 42 deletions
diff --git a/qemu-options.hx b/qemu-options.hx
index e86cc2439d..fb62b75ccb 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 8d4cc8e47c..50275de64f 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 3e3020916c..df89315733 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 039a58765c..95c1b0aeae 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 b90281b77b..89108de223 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1111,6 +1111,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
@@ -1132,17 +1149,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());
 }
@@ -1240,6 +1260,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
@@ -1261,17 +1297,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());
 }
@@ -2761,7 +2800,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 fea39adcc7..6e9921387f 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -276,9 +276,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;