You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2023/01/27 11:52:16 UTC

[cloudstack] branch main updated: kvm: Secure KVM VNC Console Access Using the CA Framework (#7015)

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new eac357cb77f kvm: Secure KVM VNC Console Access Using the CA Framework (#7015)
eac357cb77f is described below

commit eac357cb77f2ef210ce9c247059e74b9ad75265b
Author: Nicolas Vazquez <ni...@gmail.com>
AuthorDate: Fri Jan 27 08:52:06 2023 -0300

    kvm: Secure KVM VNC Console Access Using the CA Framework (#7015)
    
    This PR allows securing the console access through CloudStack to the virtual machines running on KVM. The secure access is achieved through the generated certificates for the CA Framework in CloudStack, that provides mutual TLS connections between agents. These certificates are used to also secure the connection between the console proxies and the VNC ports for VM console access.
    
    This feature is only supported on the KVM hypervisor
    
    Design Document: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Secure+KVM+VNC+connection+using+the+CA+framework
---
 agent/bindir/cloud-setup-agent.in                  |   3 +-
 python/lib/cloudutils/serviceConfig.py             |  20 ++
 scripts/util/keystore-cert-import                  |   6 +
 .../com/cloud/servlet/ConsoleProxyClientParam.java |   5 +
 .../consoleproxy/ConsoleAccessManagerImpl.java     |  11 +-
 .../consoleproxy/ConsoleProxyAjaxHandler.java      |   2 +
 .../consoleproxy/ConsoleProxyAjaxImageHandler.java |   2 +
 .../consoleproxy/ConsoleProxyClientParam.java      |   5 +
 .../ConsoleProxyHttpHandlerHelper.java             |   8 +
 .../consoleproxy/ConsoleProxyNoVNCHandler.java     |   8 +
 .../consoleproxy/ConsoleProxyNoVncClient.java      | 132 +++++++-
 .../com/cloud/consoleproxy/vnc/NoVncClient.java    | 363 ++++++++++++++++++---
 .../com/cloud/consoleproxy/vnc/RfbConstants.java   |   5 +-
 .../cloud/consoleproxy/vnc/network/NioSocket.java  | 123 +++++++
 .../consoleproxy/vnc/network/NioSocketHandler.java |  45 +++
 .../vnc/network/NioSocketHandlerImpl.java          | 116 +++++++
 .../vnc/network/NioSocketInputStream.java          | 202 ++++++++++++
 .../vnc/network/NioSocketOutputStream.java         | 114 +++++++
 .../vnc/network/NioSocketSSLEngineManager.java     | 191 +++++++++++
 .../consoleproxy/vnc/network/NioSocketStream.java  |  89 +++++
 .../vnc/network/NioSocketTLSInputStream.java       |  73 +++++
 .../vnc/network/NioSocketTLSOutputStream.java      |  65 ++++
 .../consoleproxy/vnc/security/NoneVncSecurity.java |  27 ++
 .../consoleproxy/vnc/security/VncAuthSecurity.java |  59 ++++
 .../consoleproxy/vnc/security/VncSecurity.java     |  45 +++
 .../consoleproxy/vnc/security/VncTLSSecurity.java  | 103 ++++++
 .../consoleproxy/ConsoleProxyNoVncClientTest.java  |  32 ++
 systemvm/agent/noVNC/app/styles/base.css           |   6 +
 systemvm/agent/noVNC/app/ui.js                     |  24 +-
 systemvm/agent/noVNC/core/rfb.js                   |   9 +-
 test/integration/smoke/test_vm_life_cycle.py       |   1 +
 31 files changed, 1823 insertions(+), 71 deletions(-)

diff --git a/agent/bindir/cloud-setup-agent.in b/agent/bindir/cloud-setup-agent.in
index cc3fe64ea74..53c6c2f56aa 100755
--- a/agent/bindir/cloud-setup-agent.in
+++ b/agent/bindir/cloud-setup-agent.in
@@ -26,7 +26,7 @@ from cloudutils.configFileOps import  configFileOps
 from cloudutils.globalEnv import globalEnv
 from cloudutils.networkConfig import networkConfig
 from cloudutils.syscfg import sysConfigFactory
-from cloudutils.serviceConfig import configureLibvirtConfig
+from cloudutils.serviceConfig import configureLibvirtConfig, configure_libvirt_tls
 
 from optparse import OptionParser
 
@@ -115,6 +115,7 @@ if __name__ == '__main__':
 
     if not options.auto and options.secure:
         configureLibvirtConfig(True)
+        configure_libvirt_tls(True)
         print("Libvirtd with TLS configured")
         sys.exit(0)
 
diff --git a/python/lib/cloudutils/serviceConfig.py b/python/lib/cloudutils/serviceConfig.py
index 4075b9e8602..0b4820dd7b6 100755
--- a/python/lib/cloudutils/serviceConfig.py
+++ b/python/lib/cloudutils/serviceConfig.py
@@ -587,6 +587,23 @@ class securityPolicyConfigRedhat(serviceCfgBase):
 class securityPolicyConfigSUSE(securityPolicyConfigRedhat):
     pass
 
+
+def configure_libvirt_tls(tls_enabled=False, cfo=None):
+    save = False
+    if not cfo:
+        cfo = configFileOps("/etc/libvirt/qemu.conf")
+        save = True
+
+    if tls_enabled:
+        cfo.addEntry("vnc_tls", "1")
+        cfo.addEntry("vnc_tls_x509_verify", "1")
+        cfo.addEntry("vnc_tls_x509_cert_dir", "\"/etc/pki/libvirt-vnc\"")
+    else:
+        cfo.addEntry("vnc_tls", "0")
+
+    if save:
+        cfo.save()
+
 def configureLibvirtConfig(tls_enabled = True, cfg = None):
     cfo = configFileOps("/etc/libvirt/libvirtd.conf", cfg)
     if tls_enabled:
@@ -639,6 +656,7 @@ class libvirtConfigRedhat(serviceCfgBase):
             cfo.addEntry("user", "\"root\"")
             cfo.addEntry("group", "\"root\"")
             cfo.addEntry("vnc_listen", "\"0.0.0.0\"")
+            configure_libvirt_tls(self.syscfg.env.secure, cfo)
             cfo.save()
 
             self.syscfg.svo.stopService("libvirtd")
@@ -676,6 +694,7 @@ class libvirtConfigSUSE(serviceCfgBase):
             cfo.addEntry("user", "\"root\"")
             cfo.addEntry("group", "\"root\"")
             cfo.addEntry("vnc_listen", "\"0.0.0.0\"")
+            configure_libvirt_tls(self.syscfg.env.secure, cfo)
             cfo.save()
 
             self.syscfg.svo.stopService("libvirtd")
@@ -729,6 +748,7 @@ class libvirtConfigUbuntu(serviceCfgBase):
             cfo.addEntry("security_driver", "\"none\"")
             cfo.addEntry("user", "\"root\"")
             cfo.addEntry("group", "\"root\"")
+            configure_libvirt_tls(self.syscfg.env.secure, cfo)
             cfo.save()
 
             if os.path.exists("/lib/systemd/system/libvirtd.service"):
diff --git a/scripts/util/keystore-cert-import b/scripts/util/keystore-cert-import
index 9e4e7f246b3..23956d4f86f 100755
--- a/scripts/util/keystore-cert-import
+++ b/scripts/util/keystore-cert-import
@@ -114,6 +114,12 @@ if [ -f "$LIBVIRTD_FILE" ]; then
     ln -sf /etc/cloudstack/agent/cloud.crt /etc/pki/libvirt/servercert.pem
     ln -sf /etc/cloudstack/agent/cloud.key /etc/pki/libvirt/private/clientkey.pem
     ln -sf /etc/cloudstack/agent/cloud.key /etc/pki/libvirt/private/serverkey.pem
+
+    # VNC TLS directory and certificates
+    mkdir -p /etc/pki/libvirt-vnc
+    ln -sf /etc/pki/CA/cacert.pem /etc/pki/libvirt-vnc/ca-cert.pem
+    ln -sf /etc/pki/libvirt/servercert.pem /etc/pki/libvirt-vnc/server-cert.pem
+    ln -sf /etc/pki/libvirt/private/serverkey.pem /etc/pki/libvirt-vnc/server-key.pem
     cloudstack-setup-agent -s > /dev/null
 fi
 
diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java
index 7f0fb0ba7f7..e23778c0b98 100644
--- a/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java
+++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java
@@ -22,6 +22,7 @@ public class ConsoleProxyClientParam {
     private int clientHostPort;
     private String clientHostPassword;
     private String clientTag;
+    private String clientDisplayName;
     private String ticket;
     private String locale;
     private String clientTunnelUrl;
@@ -85,6 +86,10 @@ public class ConsoleProxyClientParam {
         this.clientTag = clientTag;
     }
 
+    public String getClientDisplayName() { return this.clientDisplayName; }
+
+    public void setClientDisplayName(String clientDisplayName) { this.clientDisplayName = clientDisplayName; }
+
     public String getTicket() {
         return ticket;
     }
diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java
index 0e3a0f822f6..fb80983415d 100644
--- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java
@@ -33,6 +33,7 @@ import com.cloud.servlet.ConsoleProxyPasswordBasedEncryptor;
 import com.cloud.storage.GuestOSVO;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
+import com.cloud.uservm.UserVm;
 import com.cloud.utils.Pair;
 import com.cloud.utils.Ternary;
 import com.cloud.utils.component.ManagerBase;
@@ -285,11 +286,15 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
         UserVmDetailVO details = userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KEYBOARD);
 
         String tag = vm.getUuid();
+        String displayName = vm.getHostName();
+        if (vm instanceof UserVm) {
+            displayName = ((UserVm) vm).getDisplayName();
+        }
 
         String ticket = genAccessTicket(parsedHostInfo.first(), String.valueOf(port), sid, tag, sessionUuid);
         ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(getEncryptorPassword());
         ConsoleProxyClientParam param = generateConsoleProxyClientParam(parsedHostInfo, port, sid, tag, ticket,
-                sessionUuid, addr, extraSecurityToken, vm, hostVo, details, portInfo, host);
+                sessionUuid, addr, extraSecurityToken, vm, hostVo, details, portInfo, host, displayName);
         String token = encryptor.encryptObject(ConsoleProxyClientParam.class, param);
         int vncPort = consoleProxyManager.getVncPort();
 
@@ -353,12 +358,14 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
                                                                     String sessionUuid, String addr,
                                                                     String extraSecurityToken, VirtualMachine vm,
                                                                     HostVO hostVo, UserVmDetailVO details,
-                                                                    Pair<String, Integer> portInfo, String host) {
+                                                                    Pair<String, Integer> portInfo, String host,
+                                                                    String displayName) {
         ConsoleProxyClientParam param = new ConsoleProxyClientParam();
         param.setClientHostAddress(parsedHostInfo.first());
         param.setClientHostPort(port);
         param.setClientHostPassword(sid);
         param.setClientTag(tag);
+        param.setClientDisplayName(displayName);
         param.setTicket(ticket);
         param.setSessionUuid(sessionUuid);
         param.setSourceIP(addr);
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java
index e000b4767f5..563843840eb 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java
@@ -76,6 +76,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
         String portStr = queryMap.get("port");
         String sid = queryMap.get("sid");
         String tag = queryMap.get("tag");
+        String displayName = queryMap.get("displayname");
         String ticket = queryMap.get("ticket");
         String ajaxSessionIdStr = queryMap.get("sess");
         String eventStr = queryMap.get("event");
@@ -129,6 +130,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
             param.setClientHostPort(port);
             param.setClientHostPassword(sid);
             param.setClientTag(tag);
+            param.setClientDisplayName(displayName);
             param.setTicket(ticket);
             param.setClientTunnelUrl(console_url);
             param.setClientTunnelSession(console_host_session);
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java
index 758cac42eb9..ae319ee4006 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java
@@ -69,6 +69,7 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
         String portStr = queryMap.get("port");
         String sid = queryMap.get("sid");
         String tag = queryMap.get("tag");
+        String displayName = queryMap.get("displayname");
         String ticket = queryMap.get("ticket");
         String keyStr = queryMap.get("key");
         String console_url = queryMap.get("consoleurl");
@@ -113,6 +114,7 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
         param.setClientHostPort(port);
         param.setClientHostPassword(sid);
         param.setClientTag(tag);
+        param.setClientDisplayName(displayName);
         param.setTicket(ticket);
         param.setClientTunnelUrl(console_url);
         param.setClientTunnelSession(console_host_session);
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java
index b27532edd37..aa1f2223a8c 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java
@@ -26,6 +26,7 @@ public class ConsoleProxyClientParam {
     private int clientHostPort;
     private String clientHostPassword;
     private String clientTag;
+    private String clientDisplayName;
     private String ticket;
 
     private String clientTunnelUrl;
@@ -89,6 +90,10 @@ public class ConsoleProxyClientParam {
         this.clientTag = clientTag;
     }
 
+    public String getClientDisplayName() { return this.clientDisplayName; }
+
+    public void setClientDisplayName(String clientDisplayName) { this.clientDisplayName = clientDisplayName; }
+
     public String getTicket() {
         return ticket;
     }
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java
index d51a46680e7..ad2d944ef6f 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java
@@ -71,6 +71,14 @@ public class ConsoleProxyHttpHandlerHelper {
                 } else {
                     s_logger.error("decode token. tag info is not found!");
                 }
+                if (param.getClientDisplayName() != null) {
+                    if (s_logger.isDebugEnabled()) {
+                        s_logger.debug("decode token. displayname: " + param.getClientDisplayName());
+                    }
+                    map.put("displayname", param.getClientDisplayName());
+                } else {
+                    s_logger.error("decode token. displayname info is not found!");
+                }
                 if (param.getClientHostPassword() != null) {
                     map.put("sid", param.getClientHostPassword());
                 } else {
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java
index 72f019bd3d1..849042e7ec4 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java
@@ -29,6 +29,7 @@ import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.websocket.api.Session;
 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
 import org.eclipse.jetty.websocket.api.extensions.Frame;
@@ -79,6 +80,7 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
         String sid = queryMap.get("sid");
         String tag = queryMap.get("tag");
         String ticket = queryMap.get("ticket");
+        String displayName = queryMap.get("displayname");
         String ajaxSessionIdStr = queryMap.get("sess");
         String console_url = queryMap.get("consoleurl");
         String console_host_session = queryMap.get("sessionref");
@@ -126,6 +128,7 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
             param.setClientHostPassword(sid);
             param.setClientTag(tag);
             param.setTicket(ticket);
+            param.setClientDisplayName(displayName);
             param.setClientTunnelUrl(console_url);
             param.setClientTunnelSession(console_host_session);
             param.setLocale(vm_locale);
@@ -174,4 +177,9 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
     public void onFrame(Frame f) throws IOException {
         viewer.sendClientFrame(f);
     }
+
+    @OnWebSocketError
+    public void onError(Throwable cause) {
+        s_logger.error("Error on websocket", cause);
+    }
 }
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java
index f1c19591303..ff777976184 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java
@@ -24,8 +24,8 @@ import org.eclipse.jetty.websocket.api.extensions.Frame;
 import java.awt.Image;
 import java.io.IOException;
 import java.net.URI;
-import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 import com.cloud.consoleproxy.vnc.NoVncClient;
@@ -113,6 +113,15 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
                                 updateFrontEndActivityTime();
                             }
                             connectionAlive = client.isVncOverWebSocketConnectionAlive();
+                        } else if (client.isVncOverNioSocket()) {
+                            byte[] bytesArr;
+                            int nextBytes = client.getNextBytes();
+                            bytesArr = new byte[nextBytes];
+                            client.readBytes(bytesArr, nextBytes);
+                            if (nextBytes > 0) {
+                                session.getRemote().sendBytes(ByteBuffer.wrap(bytesArr));
+                                updateFrontEndActivityTime();
+                            }
                         } else {
                             b = new byte[100];
                             readBytes = client.read(b);
@@ -127,7 +136,7 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
                     }
                     connectionAlive = false;
                 } catch (IOException e) {
-                    e.printStackTrace();
+                    s_logger.error("Error on VNC client", e);
                 }
             }
 
@@ -137,18 +146,117 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
 
     /**
      * Authenticate to VNC server when not using websockets
-     * @throws IOException
+     *
+     * Since we are supporting the 3.8 version of the RFB protocol, there are changes on the stages:
+     * 1. Handshake:
+     *    1.a. Protocol version
+     *    1.b. Security types
+     * 2. Security types
+     * 3. Initialisation
+     *
+     * Reference: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#7protocol-messages
      */
     private void authenticateToVNCServer() throws IOException {
-        if (!client.isVncOverWebSocketConnection()) {
+        if (client.isVncOverWebSocketConnection()) {
+            return;
+        }
+
+        if (!client.isVncOverNioSocket()) {
             String ver = client.handshake();
             session.getRemote().sendBytes(ByteBuffer.wrap(ver.getBytes(), 0, ver.length()));
 
-            byte[] b = client.authenticate(getClientHostPassword());
+            byte[] b = client.authenticateTunnel(getClientHostPassword());
             session.getRemote().sendBytes(ByteBuffer.wrap(b, 0, 4));
+        } else {
+            authenticateVNCServerThroughNioSocket();
+        }
+    }
+
+    /**
+     * Handshaking messages consist on 3 phases:
+     * - ProtocolVersion
+     * - Security
+     * - SecurityResult
+     *
+     * Reference: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#71handshaking-messages
+     */
+    protected void handshakePhase() {
+        handshakeProtocolVersion();
+        int securityType = handshakeSecurity();
+        handshakeSecurityResult(securityType);
+
+        client.waitForNoVNCReply();
+    }
+
+    protected void handshakeSecurityResult(int secType) {
+        client.processHandshakeSecurityType(secType, getClientHostPassword(),
+                getClientHostAddress(), getClientHostPort());
+
+        client.processSecurityResultMsg(secType);
+        byte[] securityResultToClient = new byte[] { 0, 0, 0, 0 };
+        sendMessageToVNCClient(securityResultToClient, 4);
+        client.setWaitForNoVnc(true);
+    }
+
+    protected int handshakeSecurity() {
+        int secType = client.handshakeSecurityType();
+        byte[] numberTypesToClient = new byte[] { 1, (byte) secType };
+        sendMessageToVNCClient(numberTypesToClient, 2);
+        return secType;
+    }
+
+    protected void handshakeProtocolVersion() {
+        ByteBuffer verStr = client.handshakeProtocolVersion();
+        sendMessageToVNCClient(verStr.array(), 12);
+    }
+
+    protected void authenticateVNCServerThroughNioSocket() {
+        handshakePhase();
+        initialisationPhase();
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Authenticated successfully");
         }
     }
 
+    /**
+     * Initialisation messages consist on:
+     * - ClientInit
+     * - ServerInit
+     *
+     * Reference: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#73initialisation-messages
+     */
+    private void initialisationPhase() {
+        byte[] serverInitByteArray = client.readServerInit();
+
+        String displayNameForVM = String.format("%s %s", clientParam.getClientDisplayName(),
+                client.isTLSConnectionEstablished() ? "(TLS backend)" : "");
+        byte[] bytesServerInit = rewriteServerNameInServerInit(serverInitByteArray, displayNameForVM);
+
+        sendMessageToVNCClient(bytesServerInit, bytesServerInit.length);
+        client.setWaitForNoVnc(true);
+        client.waitForNoVNCReply();
+    }
+
+    /**
+     * Send a message to the noVNC client
+     */
+    private void sendMessageToVNCClient(byte[] arr, int length) {
+        try {
+            session.getRemote().sendBytes(ByteBuffer.wrap(arr, 0, length));
+        } catch (IOException e) {
+            s_logger.error("Error sending a message to the noVNC client", e);
+        }
+    }
+
+    protected static byte[] rewriteServerNameInServerInit(byte[] serverInitBytes, String serverName) {
+        byte[] serverNameBytes = serverName.getBytes(StandardCharsets.UTF_8);
+        ByteBuffer serverInitBuffer = ByteBuffer.allocate(24 + serverNameBytes.length);
+        serverInitBuffer.put(serverInitBytes, 0, 20);
+        serverInitBuffer.putInt(serverNameBytes.length);
+        serverInitBuffer.put(serverNameBytes);
+        return serverInitBuffer.array();
+    }
+
     /**
      * Connect to a VNC server in one of three possible ways:
      * - When tunnelUrl and tunnelSession are not empty -> via tunnel
@@ -158,27 +266,23 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
     private void connectClientToVNCServer(String tunnelUrl, String tunnelSession, String websocketUrl) {
         try {
             if (StringUtils.isNotBlank(websocketUrl)) {
-                s_logger.info("Connect to VNC over websocket URL: " + websocketUrl);
+                s_logger.info(String.format("Connect to VNC over websocket URL: %s", websocketUrl));
                 client.connectToWebSocket(websocketUrl, session);
             } else if (tunnelUrl != null && !tunnelUrl.isEmpty() && tunnelSession != null
                     && !tunnelSession.isEmpty()) {
                 URI uri = new URI(tunnelUrl);
-                s_logger.info("Connect to VNC server via tunnel. url: " + tunnelUrl + ", session: "
-                        + tunnelSession);
+                s_logger.info(String.format("Connect to VNC server via tunnel. url: %s, session: %s",
+                        tunnelUrl, tunnelSession));
 
                 ConsoleProxy.ensureRoute(uri.getHost());
                 client.connectTo(uri.getHost(), uri.getPort(), uri.getPath() + "?" + uri.getQuery(),
                         tunnelSession, "https".equalsIgnoreCase(uri.getScheme()));
             } else {
-                s_logger.info("Connect to VNC server directly. host: " + getClientHostAddress() + ", port: "
-                        + getClientHostPort());
+                s_logger.info(String.format("Connect to VNC server directly. host: %s, port: %s",
+                        getClientHostAddress(), getClientHostPort()));
                 ConsoleProxy.ensureRoute(getClientHostAddress());
                 client.connectTo(getClientHostAddress(), getClientHostPort());
             }
-        } catch (UnknownHostException e) {
-            s_logger.error("Unexpected exception", e);
-        } catch (IOException e) {
-            s_logger.error("Unexpected exception", e);
         } catch (Throwable e) {
             s_logger.error("Unexpected exception", e);
         }
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java
index 7be6421714e..991a56415fa 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/NoVncClient.java
@@ -22,21 +22,37 @@ import java.io.IOException;
 import java.net.Socket;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
 import java.security.spec.KeySpec;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.DESKeySpec;
+import java.util.Arrays;
+import java.util.List;
 
 import com.cloud.consoleproxy.util.Logger;
 import com.cloud.consoleproxy.util.RawHTTP;
+import com.cloud.consoleproxy.vnc.network.NioSocket;
+import com.cloud.consoleproxy.vnc.network.NioSocketHandler;
+import com.cloud.consoleproxy.vnc.network.NioSocketHandlerImpl;
+import com.cloud.consoleproxy.vnc.network.NioSocketSSLEngineManager;
+import com.cloud.consoleproxy.vnc.security.VncSecurity;
+import com.cloud.consoleproxy.vnc.security.VncTLSSecurity;
 import com.cloud.consoleproxy.websocket.WebSocketReverseProxy;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.commons.lang3.BooleanUtils;
 import org.eclipse.jetty.websocket.api.Session;
 
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+
 public class NoVncClient {
     private static final Logger s_logger = Logger.getLogger(NoVncClient.class);
 
@@ -44,12 +60,18 @@ public class NoVncClient {
     private DataInputStream is;
     private DataOutputStream os;
 
+    private NioSocketHandler nioSocketConnection;
+
     private WebSocketReverseProxy webSocketReverseProxy;
 
+    private boolean flushAfterReceivingNoVNCData = true;
+    private boolean securityPhaseCompleted = false;
+    private Integer writerLeft = null;
+
     public NoVncClient() {
     }
 
-    public void connectTo(String host, int port, String path, String session, boolean useSSL) throws UnknownHostException, IOException {
+    public void connectTo(String host, int port, String path, String session, boolean useSSL) throws IOException {
         if (port < 0) {
             if (useSSL)
                 port = 443;
@@ -59,14 +81,19 @@ public class NoVncClient {
 
         RawHTTP tunnel = new RawHTTP("CONNECT", host, port, path, session, useSSL);
         socket = tunnel.connect();
-        setStreams();
+        setTunnelSocketStreams();
     }
 
-    public void connectTo(String host, int port) throws UnknownHostException, IOException {
+    public void connectTo(String host, int port) {
         // Connect to server
-        s_logger.info("Connecting to VNC server " + host + ":" + port + "...");
-        socket = new Socket(host, port);
-        setStreams();
+        s_logger.info(String.format("Connecting to VNC server %s:%s ...", host, port));
+        try {
+            NioSocket nioSocket = new NioSocket(host, port);
+            this.nioSocketConnection = new NioSocketHandlerImpl(nioSocket);
+        } catch (Exception e) {
+            s_logger.error(String.format("Cannot create socket to host: %s and port %s: %s", host, port,
+                    e.getMessage()), e);
+        }
     }
 
     // VNC over WebSocket connection helpers
@@ -75,6 +102,10 @@ public class NoVncClient {
         webSocketReverseProxy.connect();
     }
 
+    public boolean isVncOverNioSocket() {
+        return this.nioSocketConnection != null;
+    }
+
     public boolean isVncOverWebSocketConnection() {
         return webSocketReverseProxy != null;
     }
@@ -93,11 +124,18 @@ public class NoVncClient {
         }
     }
 
-    private void setStreams() throws IOException {
+    private void setTunnelSocketStreams() throws IOException {
         this.is = new DataInputStream(this.socket.getInputStream());
         this.os = new DataOutputStream(this.socket.getOutputStream());
     }
 
+    public List<VncSecurity> getVncSecurityStack(int secType, String vmPassword, String host, int port) throws IOException {
+        if (secType == RfbConstants.V_ENCRYPT) {
+            secType = getVEncryptSecuritySubtype();
+        }
+        return VncSecurity.getSecurityStack(secType, vmPassword, host, port);
+    }
+
     /**
      * Handshake with VNC server.
      */
@@ -110,9 +148,10 @@ public class NoVncClient {
 
         // Server should use RFB protocol 3.x
         if (!rfbProtocol.contains(RfbConstants.RFB_PROTOCOL_VERSION_MAJOR)) {
-            s_logger.error("Cannot handshake with VNC server. Unsupported protocol version: \"" + rfbProtocol + "\".");
-            throw new RuntimeException(
-                    "Cannot handshake with VNC server. Unsupported protocol version: \"" + rfbProtocol + "\".");
+            String msg = String.format("Cannot handshake with VNC server. Unsupported protocol version: [%s]",
+                    rfbProtocol);
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg);
         }
 
         // Proxy that we support RFB 3.3 only
@@ -122,7 +161,7 @@ public class NoVncClient {
     /**
      * VNC authentication.
      */
-    public byte[] authenticate(String password)
+    public byte[] authenticateTunnel(String password)
             throws IOException {
         // Read security type
         int authType = is.readInt();
@@ -157,7 +196,7 @@ public class NoVncClient {
         }
         // Since we've taken care of the auth, we tell the client that there's no auth
         // going on
-       return new byte[] { 0, 0, 0, 1 };
+        return new byte[] { 0, 0, 0, 1 };
     }
 
     /**
@@ -184,28 +223,15 @@ public class NoVncClient {
 
         // Read security result
         int authResult = in.readInt();
-
-        switch (authResult) {
-            case RfbConstants.VNC_AUTH_OK: {
-                // Nothing to do
-                break;
-            }
-
-            case RfbConstants.VNC_AUTH_TOO_MANY:
-                s_logger.error("Connection to VNC server failed: too many wrong attempts.");
-                throw new RuntimeException("Connection to VNC server failed: too many wrong attempts.");
-
-            case RfbConstants.VNC_AUTH_FAILED:
-                s_logger.error("Connection to VNC server failed: wrong password.");
-                throw new RuntimeException("Connection to VNC server failed: wrong password.");
-
-            default:
-                s_logger.error("Connection to VNC server failed, reason code: " + authResult);
-                throw new RuntimeException("Connection to VNC server failed, reason code: " + authResult);
+        Pair<Boolean, String> pair = processSecurityResultType(authResult);
+        boolean success = BooleanUtils.toBoolean(pair.first());
+        if (!success) {
+            s_logger.error(pair.second());
+            throw new CloudRuntimeException(pair.second());
         }
     }
 
-    private byte flipByte(byte b) {
+    public static byte flipByte(byte b) {
         int b1_8 = (b & 0x1) << 7;
         int b2_7 = (b & 0x2) << 5;
         int b3_6 = (b & 0x4) << 3;
@@ -214,11 +240,12 @@ public class NoVncClient {
         int b6_3 = (b & 0x20) >>> 3;
         int b7_2 = (b & 0x40) >>> 5;
         int b8_1 = (b & 0x80) >>> 7;
-        byte c = (byte) (b1_8 | b2_7 | b3_6 | b4_5 | b5_4 | b6_3 | b7_2 | b8_1);
-        return c;
+        return (byte) (b1_8 | b2_7 | b3_6 | b4_5 | b5_4 | b6_3 | b7_2 | b8_1);
     }
 
-    public byte[] encodePassword(byte[] challenge, String password) throws Exception {
+    public static byte[] encodePassword(byte[] challenge, String password) throws InvalidKeyException,
+            InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException,
+            IllegalBlockSizeException, BadPaddingException {
         // VNC password consist of up to eight ASCII characters.
         byte[] key = { 0, 0, 0, 0, 0, 0, 0, 0 }; // Padding
         byte[] passwordAsciiBytes = password.getBytes(Charset.availableCharsets().get("US-ASCII"));
@@ -235,8 +262,61 @@ public class NoVncClient {
         Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
         cipher.init(Cipher.ENCRYPT_MODE, secretKey);
 
-        byte[] response = cipher.doFinal(challenge);
-        return response;
+        return cipher.doFinal(challenge);
+    }
+
+    private void agreeVEncryptVersion() throws IOException {
+        int majorVEncryptVersion = nioSocketConnection.readUnsignedInteger(8);
+        int minorVEncryptVersion = nioSocketConnection.readUnsignedInteger(8);
+        int vEncryptVersion = (majorVEncryptVersion << 8) | minorVEncryptVersion;
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("VEncrypt version offered by the server: " + vEncryptVersion);
+        }
+        nioSocketConnection.writeUnsignedInteger(8, majorVEncryptVersion);
+        if (vEncryptVersion >= 2) {
+            nioSocketConnection.writeUnsignedInteger(8, 2);
+            nioSocketConnection.flushWriteBuffer();
+        } else {
+            nioSocketConnection.writeUnsignedInteger(8, 0);
+            nioSocketConnection.flushWriteBuffer();
+            throw new CloudRuntimeException("Server reported an unsupported VeNCrypt version");
+        }
+        int ack = nioSocketConnection.readUnsignedInteger(8);
+        if (ack != 0) {
+            throw new IOException("The VNC server did not agree on the VEncrypt version");
+        }
+    }
+
+    private int selectVEncryptSubtype() {
+        int numberOfSubtypes = nioSocketConnection.readUnsignedInteger(8);
+        if (numberOfSubtypes <= 0) {
+            throw new CloudRuntimeException("The server reported no VeNCrypt sub-types");
+        }
+        for (int i = 0; i < numberOfSubtypes; i++) {
+            nioSocketConnection.waitForBytesAvailableForReading(4);
+            int subtype = nioSocketConnection.readUnsignedInteger(32);
+            if (subtype == RfbConstants.V_ENCRYPT_X509_VNC) {
+                if (s_logger.isDebugEnabled()) {
+                    s_logger.debug("Selected VEncrypt subtype " + subtype);
+                }
+                return subtype;
+            }
+        }
+        throw new CloudRuntimeException("Could not select a VEncrypt subtype");
+    }
+    /**
+     * Obtain the VEncrypt subtype from the VNC server
+     *
+     * Reference: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#724vencrypt
+     */
+    protected int getVEncryptSecuritySubtype() throws IOException {
+        agreeVEncryptVersion();
+
+        int selectedSubtype = selectVEncryptSubtype();
+        nioSocketConnection.writeUnsignedInteger(32, selectedSubtype);
+        nioSocketConnection.flushWriteBuffer();
+
+        return selectedSubtype;
     }
 
     public int read(byte[] b) throws IOException {
@@ -246,9 +326,208 @@ public class NoVncClient {
     public void write(byte[] b) throws IOException {
         if (isVncOverWebSocketConnection()) {
             proxyMsgOverWebSocketConnection(ByteBuffer.wrap(b));
+        } else if (isVncOverNioSocket()) {
+            writeDataNioSocketConnection(b);
         } else {
             os.write(b);
         }
     }
 
+    private void writeDataAfterSecurityPhase(byte[] data) {
+        nioSocketConnection.writeBytes(ByteBuffer.wrap(data), data.length);
+        nioSocketConnection.flushWriteBuffer();
+        if (writerLeft == null) {
+            writerLeft = 3;
+            setWaitForNoVnc(false);
+        } else if (writerLeft > 0) {
+            writerLeft--;
+        }
+    }
+
+    private void writeDataBeforeSecurityPhase(byte[] data) {
+        nioSocketConnection.writeBytes(data, 0, data.length);
+        if (flushAfterReceivingNoVNCData) {
+            nioSocketConnection.flushWriteBuffer();
+            flushAfterReceivingNoVNCData = false;
+        }
+    }
+
+    protected void writeDataNioSocketConnection(byte[] data) {
+        if (securityPhaseCompleted) {
+            writeDataAfterSecurityPhase(data);
+        } else {
+            writeDataBeforeSecurityPhase(data);
+        }
+
+        if (!securityPhaseCompleted || (writerLeft != null && writerLeft == 0)) {
+            setWaitForNoVnc(false);
+        }
+    }
+
+    /**
+     * Starts the handshake with the VNC server - ProtocolVersion
+     *
+     * Reference: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#711protocolversion
+     */
+    public ByteBuffer handshakeProtocolVersion() {
+        ByteBuffer verStr = ByteBuffer.allocate(12);
+
+        s_logger.debug("Reading RFB protocol version");
+
+        nioSocketConnection.readBytes(verStr, 12);
+
+        verStr.clear();
+        String supportedRfbVersion = RfbConstants.RFB_PROTOCOL_VERSION + "\n";
+        verStr.put(supportedRfbVersion.getBytes()).flip();
+
+        setWaitForNoVnc(true);
+        return verStr;
+    }
+
+    public void waitForNoVNCReply() {
+        int cycles = 0;
+        while (isWaitForNoVnc()) {
+            cycles++;
+        }
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug(String.format("Waited %d cycles for NoVnc", cycles));
+        }
+    }
+
+    /**
+     * Once the protocol version has been decided, the server and client must agree on the type
+     * of security to be used on the connection.
+     *
+     * Reference: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#712security
+     */
+    public int handshakeSecurityType() {
+        waitForNoVNCReply();
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Processing security types message");
+        }
+
+        int selectedSecurityType = RfbConstants.CONNECTION_FAILED;
+
+        List<Integer> supportedSecurityTypes = Arrays.asList(RfbConstants.NO_AUTH, RfbConstants.VNC_AUTH,
+                RfbConstants.V_ENCRYPT, RfbConstants.V_ENCRYPT_X509_VNC);
+
+        nioSocketConnection.waitForBytesAvailableForReading(1);
+        int serverOfferedSecurityTypes = nioSocketConnection.readUnsignedInteger(8);
+        if (serverOfferedSecurityTypes == 0) {
+            throw new CloudRuntimeException("No security types provided by the server");
+        }
+
+        for (int i = 0; i < serverOfferedSecurityTypes; i++) {
+            int serverSecurityType = nioSocketConnection.readUnsignedInteger(8);
+            if (s_logger.isDebugEnabled()) {
+                s_logger.debug(String.format("Server offers security type: %s", serverSecurityType));
+            }
+            if (supportedSecurityTypes.contains(serverSecurityType)) {
+                selectedSecurityType = serverSecurityType;
+                if (s_logger.isDebugEnabled()) {
+                    s_logger.debug(String.format("Selected supported security type: %s", selectedSecurityType));
+                }
+                break;
+            }
+        }
+        this.flushAfterReceivingNoVNCData = true;
+        setWaitForNoVnc(true);
+        return selectedSecurityType;
+    }
+
+    private final Object lock = new Object();
+    public void setWaitForNoVnc(boolean val) {
+        synchronized (lock) {
+            this.waitForNoVnc = val;
+        }
+    }
+
+    public boolean isWaitForNoVnc() {
+        synchronized (lock) {
+            return this.waitForNoVnc;
+        }
+    }
+
+    private boolean waitForNoVnc = false;
+
+    private Pair<Boolean, String> processSecurityResultType(int authResult) {
+        boolean result = false;
+        String message;
+        switch (authResult) {
+            case RfbConstants.VNC_AUTH_OK: {
+                result = true;
+                message = "Security completed";
+                break;
+            }
+            case RfbConstants.VNC_AUTH_TOO_MANY:
+                message = "Connection to VNC server failed: too many wrong attempts.";
+                break;
+            case RfbConstants.VNC_AUTH_FAILED:
+                message = "Connection to VNC server failed: wrong password.";
+                break;
+            default:
+                message = String.format("Connection to VNC server failed, reason code: %s", authResult);
+        }
+        return new Pair<>(result, message);
+    }
+
+    public void processSecurityResultMsg(int securityType) {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("Processing security result message");
+        }
+
+        int result;
+        if (securityType == RfbConstants.NO_AUTH) {
+            result = RfbConstants.VNC_AUTH_OK;
+        } else {
+            nioSocketConnection.waitForBytesAvailableForReading(1);
+            result = nioSocketConnection.readUnsignedInteger(32);
+        }
+
+        Pair<Boolean, String> securityResultType = processSecurityResultType(result);
+        boolean success = BooleanUtils.toBoolean(securityResultType.first());
+        if (success) {
+            securityPhaseCompleted = true;
+        } else {
+            s_logger.error(securityResultType.second());
+            String reason = nioSocketConnection.readString();
+            String msg = String.format("%s - Reason: %s", securityResultType.second(), reason);
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+    }
+
+    public byte[] readServerInit() {
+        return nioSocketConnection.readServerInit();
+    }
+
+    public int getNextBytes() {
+        return nioSocketConnection.readNextBytes();
+    }
+
+    public boolean isTLSConnectionEstablished() {
+        return nioSocketConnection.isTLSConnection();
+    }
+
+    public void readBytes(byte[] arr, int len) {
+        nioSocketConnection.readNextByteArray(arr, len);
+    }
+
+    public void processHandshakeSecurityType(int secType, String vmPassword, String host, int port) {
+        waitForNoVNCReply();
+
+        try {
+            List<VncSecurity> vncSecurityStack = getVncSecurityStack(secType, vmPassword, host, port);
+            for (VncSecurity security : vncSecurityStack) {
+                security.process(this.nioSocketConnection);
+                if (security instanceof VncTLSSecurity) {
+                    s_logger.debug("Setting new streams with SSLEngineManger after TLS security has passed");
+                    NioSocketSSLEngineManager sslEngineManager = ((VncTLSSecurity) security).getSSLEngineManager();
+                    nioSocketConnection.startTLSConnection(sslEngineManager);
+                }
+            }
+        } catch (IOException e) {
+            s_logger.error("Error processing handshake security type " + secType, e);
+        }
+    }
 }
\ No newline at end of file
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/RfbConstants.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/RfbConstants.java
index 7bf4f8dbd7e..3e44ce7d1a6 100644
--- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/RfbConstants.java
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/RfbConstants.java
@@ -22,7 +22,7 @@ public interface RfbConstants {
 
     public static final String RFB_PROTOCOL_VERSION_MAJOR = "RFB 003.";
     // public static final String VNC_PROTOCOL_VERSION_MINOR = "003";
-    public static final String VNC_PROTOCOL_VERSION_MINOR = "003";
+    public static final String VNC_PROTOCOL_VERSION_MINOR = "008";
     public static final String RFB_PROTOCOL_VERSION = RFB_PROTOCOL_VERSION_MAJOR + VNC_PROTOCOL_VERSION_MINOR;
 
     /**
@@ -39,7 +39,8 @@ public interface RfbConstants {
     /**
      * Server authorization type
      */
-    public final static int CONNECTION_FAILED = 0, NO_AUTH = 1, VNC_AUTH = 2;
+    public final static int CONNECTION_FAILED = 0, NO_AUTH = 1, VNC_AUTH = 2,
+        V_ENCRYPT = 19, V_ENCRYPT_X509_VNC = 261;
 
     /**
      * Server authorization reply.
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocket.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocket.java
new file mode 100644
index 00000000000..d1779042d86
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocket.java
@@ -0,0 +1,123 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import org.apache.log4j.Logger;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.util.Set;
+
+public class NioSocket {
+
+    private SocketChannel socketChannel;
+    private Selector writeSelector;
+    private Selector readSelector;
+
+    private static final int CONNECTION_TIMEOUT_MILLIS = 3000;
+    private static final Logger s_logger = Logger.getLogger(NioSocket.class);
+
+    private void initializeSocket() {
+        try {
+            socketChannel = SocketChannel.open();
+            socketChannel.configureBlocking(false);
+            socketChannel.socket().setSoTimeout(5000);
+            writeSelector = Selector.open();
+            readSelector = Selector.open();
+            socketChannel.register(writeSelector, SelectionKey.OP_WRITE);
+            socketChannel.register(readSelector, SelectionKey.OP_READ);
+        } catch (IOException e) {
+            s_logger.error("Could not initialize NioSocket: " + e.getMessage(), e);
+        }
+    }
+
+    private void waitForSocketSelectorConnected(Selector selector) {
+        try {
+            while (selector.select(CONNECTION_TIMEOUT_MILLIS) <= 0) {
+                s_logger.debug("Waiting for ready operations to connect to the socket");
+            }
+            Set<SelectionKey> keys = selector.selectedKeys();
+            for (SelectionKey selectionKey: keys) {
+                if (selectionKey.isConnectable()) {
+                    if (socketChannel.isConnectionPending()) {
+                        socketChannel.finishConnect();
+                    }
+                    s_logger.debug("Connected to the socket");
+                    break;
+                }
+            }
+        } catch (IOException e) {
+            s_logger.error(String.format("Error waiting for socket selector ready: %s", e.getMessage()), e);
+        }
+    }
+
+    private void connectSocket(String host, int port) {
+        try {
+            socketChannel.connect(new InetSocketAddress(host, port));
+            Selector selector = Selector.open();
+            socketChannel.register(selector, SelectionKey.OP_CONNECT);
+
+            waitForSocketSelectorConnected(selector);
+            socketChannel.socket().setTcpNoDelay(false);
+        } catch (IOException e) {
+            s_logger.error(String.format("Error creating NioSocket to %s:%s: %s", host, port, e.getMessage()), e);
+        }
+    }
+
+    public NioSocket(String host, int port) {
+        initializeSocket();
+        connectSocket(host, port);
+    }
+
+    protected int select(boolean read, Integer timeout) {
+        try {
+            Selector selector = read ? readSelector : writeSelector;
+            selector.selectedKeys().clear();
+            return timeout == null ? selector.select() : selector.selectNow();
+        } catch (IOException e) {
+            s_logger.error(String.format("Error obtaining %s select: %s", read ? "read" : "write", e.getMessage()), e);
+            return -1;
+        }
+    }
+
+    protected int readFromSocketChannel(ByteBuffer readBuffer, int len) {
+        try {
+            int readBytes = socketChannel.read(readBuffer.slice().limit(len));
+            int position = readBuffer.position();
+            readBuffer.position(position + readBytes);
+            return Math.max(readBytes, 0);
+        } catch (Exception e) {
+            s_logger.error("Error reading from socket channel: " + e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    protected int writeToSocketChannel(ByteBuffer buf, int len) {
+        try {
+            int writtenBytes = socketChannel.write(buf.slice().limit(len));
+            buf.position(buf.position() + writtenBytes);
+            return writtenBytes;
+        } catch (java.io.IOException e) {
+            s_logger.error("Error writing bytes to socket channel: " + e.getMessage(), e);
+            return 0;
+        }
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketHandler.java
new file mode 100644
index 00000000000..e1ccd6feb11
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketHandler.java
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import java.nio.ByteBuffer;
+
+public interface NioSocketHandler {
+
+    // Getters
+    NioSocketInputStream getInputStream();
+    NioSocketOutputStream getOutputStream();
+
+    // Read operations
+    int readUnsignedInteger(int sizeInBits);
+    void readBytes(ByteBuffer data, int length);
+    String readString();
+    byte[] readServerInit();
+    int readNextBytes();
+    void readNextByteArray(byte[] arr, int len);
+
+    // Write operations
+    void writeUnsignedInteger(int sizeInBits, int value);
+    void writeBytes(byte[] data, int dataPtr, int length);
+    void writeBytes(ByteBuffer data, int length);
+
+    // Additional operations
+    void waitForBytesAvailableForReading(int bytes);
+    void flushWriteBuffer();
+    void startTLSConnection(NioSocketSSLEngineManager sslEngineManager);
+    boolean isTLSConnection();
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketHandlerImpl.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketHandlerImpl.java
new file mode 100644
index 00000000000..27414aed8ab
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketHandlerImpl.java
@@ -0,0 +1,116 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import org.apache.log4j.Logger;
+
+import java.nio.ByteBuffer;
+
+public class NioSocketHandlerImpl implements NioSocketHandler {
+
+    private NioSocketInputStream inputStream;
+    private NioSocketOutputStream outputStream;
+    private boolean isTLS = false;
+
+    private static final int DEFAULT_BUF_SIZE = 16384;
+
+    private static final Logger s_logger = Logger.getLogger(NioSocketHandlerImpl.class);
+
+    public NioSocketHandlerImpl(NioSocket socket) {
+        this.inputStream = new NioSocketInputStream(DEFAULT_BUF_SIZE, socket);
+        this.outputStream = new NioSocketOutputStream(DEFAULT_BUF_SIZE, socket);
+    }
+
+    @Override
+    public int readUnsignedInteger(int sizeInBits) {
+        return inputStream.readUnsignedInteger(sizeInBits);
+    }
+
+    @Override
+    public void writeUnsignedInteger(int sizeInBits, int value) {
+        outputStream.writeUnsignedInteger(sizeInBits, value);
+    }
+
+    @Override
+    public void readBytes(ByteBuffer data, int length) {
+        inputStream.readBytes(data, length);
+    }
+
+    @Override
+    public void waitForBytesAvailableForReading(int bytes) {
+        while (!inputStream.checkForSizeWithoutWait(bytes)) {
+            s_logger.trace("Waiting for inStream to be ready");
+        }
+    }
+
+    @Override
+    public void writeBytes(byte[] data, int dataPtr, int length) {
+        outputStream.writeBytes(data, dataPtr, length);
+    }
+
+    @Override
+    public void writeBytes(ByteBuffer data, int length) {
+        outputStream.writeBytes(data, length);
+    }
+
+    @Override
+    public void flushWriteBuffer() {
+        outputStream.flushWriteBuffer();
+    }
+
+    @Override
+    public void startTLSConnection(NioSocketSSLEngineManager sslEngineManager) {
+        this.inputStream = new NioSocketTLSInputStream(sslEngineManager, this.inputStream.socket);
+        this.outputStream = new NioSocketTLSOutputStream(sslEngineManager, this.outputStream.socket);
+        this.isTLS = true;
+    }
+
+    @Override
+    public boolean isTLSConnection() {
+        return this.isTLS;
+    }
+
+    @Override
+    public String readString() {
+        return inputStream.readString();
+    }
+
+    @Override
+    public byte[] readServerInit() {
+        return inputStream.readServerInit();
+    }
+
+    @Override
+    public int readNextBytes() {
+        return inputStream.getNextBytes();
+    }
+
+    @Override
+    public void readNextByteArray(byte[] arr, int len) {
+        inputStream.readNextByteArrayFromReadBuffer(arr, len);
+    }
+
+    @Override
+    public NioSocketInputStream getInputStream() {
+        return inputStream;
+    }
+
+    @Override
+    public NioSocketOutputStream getOutputStream() {
+        return outputStream;
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketInputStream.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketInputStream.java
new file mode 100644
index 00000000000..8747afd85e2
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketInputStream.java
@@ -0,0 +1,202 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+public class NioSocketInputStream extends NioSocketStream {
+
+    public NioSocketInputStream(int bufferSize, NioSocket socket) {
+        super(bufferSize, socket);
+    }
+
+    public int getReadBytesAvailableToFitSize(int itemSize, int numberItems, boolean wait) {
+        int window = endPosition - currentPosition;
+        if (itemSize > window) {
+            return rearrangeBufferToFitSize(numberItems, itemSize, wait);
+        }
+        return Math.min(window / itemSize, numberItems);
+    }
+
+    protected void moveDataToBufferStart() {
+        if (endPosition - currentPosition != 0) {
+            System.arraycopy(buffer, currentPosition, buffer, 0, endPosition - currentPosition);
+        }
+        offset += currentPosition;
+        endPosition -= currentPosition;
+        currentPosition = 0;
+    }
+
+    protected boolean canUseReadSelector(boolean wait) {
+        int n = -1;
+        Integer timeout = !wait ? 0 : null;
+        while (n < 0) {
+            n = socket.select(true, timeout);
+        }
+        return n > 0 || wait;
+    }
+
+    protected int readBytesToBuffer(ByteBuffer buf, int bytesToRead, boolean wait) {
+        if (!canUseReadSelector(wait)) {
+            return 0;
+        }
+        int readBytes = socket.readFromSocketChannel(buf, bytesToRead);
+        if (readBytes == 0) {
+            throw new CloudRuntimeException("End of stream exception");
+        }
+        return readBytes;
+    }
+
+    protected int rearrangeBufferToFitSize(int numberItems, int itemSize, boolean wait) {
+        checkItemSizeOnBuffer(itemSize);
+
+        moveDataToBufferStart();
+
+        while (endPosition < itemSize) {
+            int remainingBufferSize = buffer.length - endPosition;
+            int desiredCapacity = itemSize * numberItems;
+            int bytesToRead = Math.min(remainingBufferSize, Math.max(desiredCapacity, 8));
+
+            ByteBuffer buf = ByteBuffer.wrap(buffer).position(endPosition);
+            int n = readBytesToBuffer(buf, bytesToRead, wait);
+            if (n == 0) {
+                return 0;
+            }
+            endPosition += n;
+        }
+
+        int window = endPosition - currentPosition;
+        return Math.min(window / itemSize, numberItems);
+    }
+
+    protected Pair<Integer, byte[]> readAndCopyUnsignedInteger(int sizeInBits) {
+        checkUnsignedIntegerSize(sizeInBits);
+        int bytes = sizeInBits / 8;
+        getReadBytesAvailableToFitSize(bytes, 1, true);
+        byte[] unsignedIntegerArray = Arrays.copyOfRange(buffer, currentPosition, currentPosition + bytes);
+        currentPosition += bytes;
+        return new Pair<>(convertByteArrayToUnsignedInteger(unsignedIntegerArray), unsignedIntegerArray);
+    }
+
+    protected int readUnsignedInteger(int sizeInBits) {
+        Pair<Integer, byte[]> pair = readAndCopyUnsignedInteger(sizeInBits);
+        return pair.first();
+    }
+
+    protected void readBytes(ByteBuffer data, int length) {
+        while (length > 0) {
+            int n = getReadBytesAvailableToFitSize(1, length, true);
+            data.put(buffer, currentPosition, n);
+            currentPosition += n;
+            length -= n;
+        }
+    }
+
+    public boolean checkForSizeWithoutWait(int size) {
+        return getReadBytesAvailableToFitSize(size, 1, false) != 0;
+    }
+
+    protected final String readString() {
+        int len = readUnsignedInteger(32);
+
+        ByteBuffer str = ByteBuffer.allocate(len);
+        readBytes(str, len);
+        return new String(str.array(), StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Read ServerInit message and return it as a byte[] for noVNC
+     * Reference: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#732serverinit
+     */
+    public byte[] readServerInit() {
+        // Read width, height, pixel format and VM name
+        byte[] bytesRead = new byte[] {};
+        Pair<Integer, byte[]> widthPair = readAndCopyUnsignedInteger(16);
+        bytesRead = ArrayUtils.addAll(bytesRead, widthPair.second());
+        Pair<Integer, byte[]> heightPair = readAndCopyUnsignedInteger(16);
+        bytesRead = ArrayUtils.addAll(bytesRead, heightPair.second());
+
+        byte[] pixelFormatByteArr = readPixelFormat();
+        bytesRead = ArrayUtils.addAll(bytesRead, pixelFormatByteArr);
+
+        Pair<Integer, byte[]> pair = readAndCopyUnsignedInteger(32);
+        int len = pair.first();
+        bytesRead = ArrayUtils.addAll(bytesRead, pair.second());
+
+        ByteBuffer str = ByteBuffer.allocate(len);
+        readBytes(str, len);
+        return ArrayUtils.addAll(bytesRead, str.array());
+    }
+
+    protected final void skipReadBytes(int bytes) {
+        while (bytes > 0) {
+            int n = getReadBytesAvailableToFitSize(1, bytes, true);
+            currentPosition += n;
+            bytes -= n;
+        }
+    }
+
+    /**
+     * Read PixelFormat and return it as byte[]
+     */
+    private byte[] readPixelFormat() {
+        Pair<Integer, byte[]> bppPair = readAndCopyUnsignedInteger(8);
+        byte[] ret = bppPair.second();
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(8).second());
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(8).second());
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(8).second());
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(16).second());
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(16).second());
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(16).second());
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(8).second());
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(8).second());
+        ret = ArrayUtils.addAll(ret, readAndCopyUnsignedInteger(8).second());
+        skipReadBytes(3);
+        return ArrayUtils.addAll(ret, (byte) 0, (byte) 0, (byte) 0);
+    }
+
+    protected int getNextBytes() {
+        int size = 200;
+        while (size > 0) {
+            if (checkForSizeWithoutWait(size)) {
+                break;
+            }
+            size--;
+        }
+        return size;
+    }
+
+    protected void readNextByteArrayFromReadBuffer(byte[] arr, int len) {
+        copyBytesFromReadBuffer(len, arr);
+    }
+
+    protected void copyBytesFromReadBuffer(int length, byte[] arr) {
+        int ptr = 0;
+        while (length > 0) {
+            int n = getReadBytesAvailableToFitSize(1, length, true);
+            readBytes(ByteBuffer.wrap(arr, ptr, n), n);
+            ptr += n;
+            length -= n;
+        }
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketOutputStream.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketOutputStream.java
new file mode 100644
index 00000000000..03ead7a1a36
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketOutputStream.java
@@ -0,0 +1,114 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+
+import java.nio.ByteBuffer;
+
+public class NioSocketOutputStream extends NioSocketStream {
+
+    private int sendPosition;
+
+    public NioSocketOutputStream(int bufferSize, NioSocket socket) {
+        super(bufferSize, socket);
+        this.endPosition = bufferSize;
+        this.sendPosition = 0;
+    }
+
+    protected final int checkWriteBufferForSingleItems(int items) {
+        int window = endPosition - currentPosition;
+        return window < 1 ?
+                rearrangeWriteBuffer(1, items) :
+                Math.min(window, items);
+    }
+
+    public final void checkWriteBufferForSize(int itemSize) {
+        if (itemSize > endPosition - currentPosition) {
+            rearrangeWriteBuffer(itemSize, 1);
+        }
+    }
+
+    public void flushWriteBuffer() {
+        while (sendPosition < currentPosition) {
+            int writtenBytes = writeFromWriteBuffer(buffer, sendPosition, currentPosition - sendPosition);
+
+            if (writtenBytes == 0) {
+                throw new CloudRuntimeException("Timeout exception");
+            }
+
+            sendPosition += writtenBytes;
+            offset += writtenBytes;
+        }
+
+        if (sendPosition == currentPosition) {
+            sendPosition = start;
+            currentPosition = start;
+        }
+    }
+
+    protected boolean canUseWriteSelector() {
+        int n = -1;
+        while (n < 0) {
+            n = socket.select(false, null);
+        }
+        return n > 0;
+    }
+
+    private int writeFromWriteBuffer(byte[] data, int dataPtr, int length) {
+        if (!canUseWriteSelector()) {
+            return 0;
+        }
+        return socket.writeToSocketChannel(ByteBuffer.wrap(data, dataPtr, length), length);
+    }
+
+    protected int rearrangeWriteBuffer(int itemSize, int numberItems) {
+        checkItemSizeOnBuffer(itemSize);
+
+        flushWriteBuffer();
+
+        int window = endPosition - currentPosition;
+        return Math.min(window / itemSize, numberItems);
+
+    }
+
+    protected void writeUnsignedInteger(int sizeInBits, int value) {
+        checkUnsignedIntegerSize(sizeInBits);
+        int bytes = sizeInBits / 8;
+        checkWriteBufferForSize(bytes);
+        placeUnsignedIntegerToBuffer(bytes, value);
+    }
+
+    protected void writeBytes(byte[] data, int dataPtr, int length) {
+        int dataEnd = dataPtr + length;
+        while (dataPtr < dataEnd) {
+            int n = checkWriteBufferForSingleItems(dataEnd - dataPtr);
+            System.arraycopy(data, dataPtr, buffer, currentPosition, n);
+            currentPosition += n;
+            dataPtr += n;
+        }
+    }
+
+    protected void writeBytes(ByteBuffer data, int length) {
+        while (length > 0) {
+            int n = checkWriteBufferForSingleItems(length);
+            data.get(buffer, currentPosition, n);
+            currentPosition += n;
+            length -= n;
+        }
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketSSLEngineManager.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketSSLEngineManager.java
new file mode 100644
index 00000000000..2b0229b7567
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketSSLEngineManager.java
@@ -0,0 +1,191 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class NioSocketSSLEngineManager {
+
+    private final SSLEngine engine;
+
+    private final ByteBuffer myNetData;
+    private final ByteBuffer peerNetData;
+
+    private final Executor executor;
+    private final NioSocketInputStream inputStream;
+    private final NioSocketOutputStream outputStream;
+
+    public NioSocketSSLEngineManager(SSLEngine sslEngine, NioSocketHandler socket) {
+        this.inputStream = socket.getInputStream();
+        this.outputStream = socket.getOutputStream();
+        engine = sslEngine;
+
+        executor = Executors.newSingleThreadExecutor();
+
+        int pktBufSize = engine.getSession().getPacketBufferSize();
+        myNetData = ByteBuffer.allocate(pktBufSize);
+        peerNetData = ByteBuffer.allocate(pktBufSize);
+    }
+
+    private void handshakeNeedUnwrap(ByteBuffer peerAppData) throws SSLException {
+        peerNetData.flip();
+        SSLEngineResult result = engine.unwrap(peerNetData, peerAppData);
+        peerNetData.compact();
+
+        switch (result.getStatus()) {
+            case BUFFER_UNDERFLOW:
+                int avail = inputStream.getReadBytesAvailableToFitSize(1, peerNetData.remaining(),
+                        false);
+                inputStream.readBytes(peerNetData, avail);
+                break;
+            case OK:
+            case BUFFER_OVERFLOW:
+                break;
+            case CLOSED:
+                engine.closeInbound();
+                break;
+        }
+    }
+
+    private void handshakeNeedWrap(ByteBuffer myAppData) throws SSLException {
+        SSLEngineResult result = engine.wrap(myAppData, myNetData);
+
+        switch (result.getStatus()) {
+            case OK:
+                myNetData.flip();
+                outputStream.writeBytes(myNetData, myNetData.remaining());
+                outputStream.flushWriteBuffer();
+                myNetData.compact();
+                break;
+            case CLOSED:
+                engine.closeOutbound();
+                break;
+            case BUFFER_OVERFLOW:
+            case BUFFER_UNDERFLOW:
+                break;
+        }
+    }
+
+    private void handleHandshakeStatus(SSLEngineResult.HandshakeStatus handshakeStatus,
+                                       ByteBuffer peerAppData, ByteBuffer myAppData) throws SSLException {
+        switch (handshakeStatus) {
+            case NEED_UNWRAP:
+            case NEED_UNWRAP_AGAIN:
+                handshakeNeedUnwrap(peerAppData);
+                break;
+
+            case NEED_WRAP:
+                handshakeNeedWrap(myAppData);
+                break;
+
+            case NEED_TASK:
+                executeTasks();
+                break;
+
+            case FINISHED:
+            case NOT_HANDSHAKING:
+                break;
+        }
+    }
+
+    public void doHandshake() throws SSLException {
+        engine.beginHandshake();
+        SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
+
+        int appBufSize = engine.getSession().getApplicationBufferSize();
+        ByteBuffer peerAppData = ByteBuffer.allocate(appBufSize);
+        ByteBuffer myAppData = ByteBuffer.allocate(appBufSize);
+
+        while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED &&
+                handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+            handleHandshakeStatus(handshakeStatus, peerAppData, myAppData);
+            handshakeStatus = engine.getHandshakeStatus();
+        }
+    }
+
+    private void executeTasks() {
+        Runnable task;
+        while ((task = engine.getDelegatedTask()) != null) {
+            executor.execute(task);
+        }
+    }
+
+    public int read(ByteBuffer data) throws IOException {
+        peerNetData.flip();
+        SSLEngineResult result = engine.unwrap(peerNetData, data);
+        peerNetData.compact();
+
+        switch (result.getStatus()) {
+            case OK :
+                return result.bytesProduced();
+            case BUFFER_UNDERFLOW:
+                // attempt to drain the underlying buffer first
+                int need = peerNetData.remaining();
+                int available = inputStream.getReadBytesAvailableToFitSize(1, need, false);
+                inputStream.readBytes(peerNetData, available);
+                break;
+            case CLOSED:
+                engine.closeInbound();
+                break;
+            case BUFFER_OVERFLOW:
+                break;
+        }
+        return 0;
+    }
+
+    public int write(ByteBuffer data) throws IOException {
+        int n = 0;
+        while (data.hasRemaining()) {
+            SSLEngineResult result = engine.wrap(data, myNetData);
+            n += result.bytesConsumed();
+            switch (result.getStatus()) {
+                case OK:
+                    myNetData.flip();
+                    outputStream.writeBytes(myNetData, myNetData.remaining());
+                    outputStream.flushWriteBuffer();
+                    myNetData.compact();
+                    break;
+
+                case BUFFER_OVERFLOW:
+                    myNetData.flip();
+                    outputStream.writeBytes(myNetData, myNetData.remaining());
+                    myNetData.compact();
+                    break;
+
+                case CLOSED:
+                    engine.closeOutbound();
+                    break;
+
+                case BUFFER_UNDERFLOW:
+                    break;
+            }
+        }
+        return n;
+    }
+
+    public SSLSession getSession() {
+        return engine.getSession();
+    }
+
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketStream.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketStream.java
new file mode 100644
index 00000000000..66c18f09fd3
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketStream.java
@@ -0,0 +1,89 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.log4j.Logger;
+
+public class NioSocketStream {
+
+    protected byte[] buffer;
+    protected int currentPosition;
+    protected int offset;
+    protected int endPosition;
+    protected int start;
+    protected NioSocket socket;
+
+    private static final Logger s_logger = Logger.getLogger(NioSocketStream.class);
+
+    public NioSocketStream(int bufferSize, NioSocket socket) {
+        this.buffer = new byte[bufferSize];
+        this.currentPosition = 0;
+        this.offset = 0;
+        this.endPosition = 0;
+        this.start = 0;
+        this.socket = socket;
+    }
+
+    protected boolean isUnsignedIntegerSizeAllowed(int sizeInBits) {
+        return sizeInBits % 8 == 0 && sizeInBits > 0 && sizeInBits <= 32;
+    }
+
+    protected void checkUnsignedIntegerSize(int sizeInBits) {
+        if (!isUnsignedIntegerSizeAllowed(sizeInBits)) {
+            String msg = "Unsupported size in bits for unsigned integer reading " + sizeInBits;
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+    }
+
+    protected int convertByteArrayToUnsignedInteger(byte[] readBytes) {
+        if (readBytes.length == 1) {
+            return readBytes[0] & 0xff;
+        } else if (readBytes.length == 2) {
+            int signed = readBytes[0] << 8 | readBytes[1] & 0xff;
+            return signed & 0xffff;
+        } else if (readBytes.length == 4) {
+            return (readBytes[0] << 24) | (readBytes[1] & 0xff) << 16 |
+                    (readBytes[2] & 0xff) << 8 | (readBytes[3] & 0xff);
+        } else {
+            throw new CloudRuntimeException("Error reading unsigned integer from socket stream");
+        }
+    }
+
+    protected void placeUnsignedIntegerToBuffer(int bytes, int value) {
+        if (bytes == 1) {
+            buffer[currentPosition++] = (byte) value;
+        } else if (bytes == 2) {
+            buffer[currentPosition++] = (byte) (value >> 8);
+            buffer[currentPosition++] = (byte) value;
+        } else if (bytes == 4) {
+            buffer[currentPosition++] = (byte) (value >> 24);
+            buffer[currentPosition++] = (byte) (value >> 16);
+            buffer[currentPosition++] = (byte) (value >> 8);
+            buffer[currentPosition++] = (byte) value;
+        }
+    }
+
+    protected void checkItemSizeOnBuffer(int itemSize) {
+        if (itemSize > buffer.length) {
+            String msg = String.format("Item size: %s exceeds the buffer size: %s", itemSize, buffer.length);
+            s_logger.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketTLSInputStream.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketTLSInputStream.java
new file mode 100644
index 00000000000..15a3e15fd05
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketTLSInputStream.java
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.log4j.Logger;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class NioSocketTLSInputStream extends NioSocketInputStream {
+
+    private final NioSocketSSLEngineManager sslEngineManager;
+
+    private static final Logger s_logger = Logger.getLogger(NioSocketTLSInputStream.class);
+
+    public NioSocketTLSInputStream(NioSocketSSLEngineManager sslEngineManager, NioSocket socket) {
+        super(sslEngineManager.getSession().getApplicationBufferSize(), socket);
+        this.sslEngineManager = sslEngineManager;
+    }
+
+    protected int readFromSSLEngineManager(byte[] buffer, int startPos, int length) {
+        try {
+            int readBytes = sslEngineManager.read(ByteBuffer.wrap(buffer, startPos, length));
+            if (readBytes < 0) {
+                throw new CloudRuntimeException(String.format("Invalid number of read bytes frm SSL engine manager %s",
+                        readBytes));
+            }
+            return readBytes;
+        } catch (IOException e) {
+            s_logger.error(String.format("Error reading from SSL engine manager: %s", e.getMessage()), e);
+        }
+        return 0;
+    }
+
+    @Override
+    protected int rearrangeBufferToFitSize(int numberItems, int itemSize, boolean wait) {
+        checkItemSizeOnBuffer(itemSize);
+
+        if (endPosition - currentPosition != 0) {
+            System.arraycopy(buffer, currentPosition, buffer, 0, endPosition - currentPosition);
+        }
+
+        offset += currentPosition - start;
+        endPosition -= currentPosition - start;
+        currentPosition = start;
+
+        while ((endPosition - start) < itemSize) {
+            int n = readFromSSLEngineManager(buffer, endPosition, start + buffer.length - endPosition);
+            if (!wait && n == 0) {
+                return 0;
+            }
+            endPosition += n;
+        }
+
+        int window = endPosition - currentPosition;
+        return Math.min(window / itemSize, numberItems);
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketTLSOutputStream.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketTLSOutputStream.java
new file mode 100644
index 00000000000..8ee01af059e
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/network/NioSocketTLSOutputStream.java
@@ -0,0 +1,65 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.network;
+
+import org.apache.log4j.Logger;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class NioSocketTLSOutputStream extends NioSocketOutputStream {
+
+    private final NioSocketSSLEngineManager sslEngineManager;
+
+    private static final Logger s_logger = Logger.getLogger(NioSocketTLSOutputStream.class);
+
+    public NioSocketTLSOutputStream(NioSocketSSLEngineManager sslEngineManager, NioSocket socket) {
+        super(sslEngineManager.getSession().getApplicationBufferSize(), socket);
+        this.sslEngineManager = sslEngineManager;
+    }
+
+    @Override
+    public void flushWriteBuffer() {
+        int sentUpTo = start;
+        while (sentUpTo < currentPosition) {
+            int n = writeThroughSSLEngineManager(buffer, sentUpTo, currentPosition - sentUpTo);
+            sentUpTo += n;
+            offset += n;
+        }
+
+        currentPosition = start;
+    }
+
+    protected int writeThroughSSLEngineManager(byte[] data, int startPos, int length) {
+        try {
+            return sslEngineManager.write(ByteBuffer.wrap(data, startPos, length));
+        } catch (IOException e) {
+            s_logger.error(String.format("Error writing though SSL engine manager: %s", e.getMessage()), e);
+            return 0;
+        }
+    }
+
+    @Override
+    protected int rearrangeWriteBuffer(int itemSize, int numberItems) {
+        checkItemSizeOnBuffer(itemSize);
+
+        flushWriteBuffer();
+
+        int window = endPosition - currentPosition;
+        return Math.min(window / itemSize, numberItems);
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/NoneVncSecurity.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/NoneVncSecurity.java
new file mode 100644
index 00000000000..f9b088d1b9e
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/NoneVncSecurity.java
@@ -0,0 +1,27 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.security;
+
+import com.cloud.consoleproxy.vnc.network.NioSocketHandler;
+
+public class NoneVncSecurity implements VncSecurity {
+
+    @Override
+    public void process(NioSocketHandler socketHandler) {
+        // No auth required
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncAuthSecurity.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncAuthSecurity.java
new file mode 100644
index 00000000000..3a394eb6339
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncAuthSecurity.java
@@ -0,0 +1,59 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.security;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.cloud.consoleproxy.vnc.NoVncClient;
+import com.cloud.consoleproxy.vnc.network.NioSocketHandler;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class VncAuthSecurity implements VncSecurity {
+
+    private final String vmPass;
+
+    private static final int VNC_AUTH_CHALLENGE_SIZE = 16;
+    private static final Logger s_logger = Logger.getLogger(VncAuthSecurity.class);
+
+    public VncAuthSecurity(String vmPass) {
+        this.vmPass = vmPass;
+    }
+
+    @Override
+    public void process(NioSocketHandler socketHandler) throws IOException {
+        s_logger.info("VNC server requires password authentication");
+
+        // Read the challenge & obtain the user's password
+        ByteBuffer challenge = ByteBuffer.allocate(VNC_AUTH_CHALLENGE_SIZE);
+        socketHandler.readBytes(challenge, VNC_AUTH_CHALLENGE_SIZE);
+
+        byte[] encodedPassword;
+        try {
+            encodedPassword = NoVncClient.encodePassword(challenge.array(), vmPass);
+        } catch (Exception e) {
+            s_logger.error("Cannot encrypt client password to send to server: " + e.getMessage());
+            throw new CloudRuntimeException("Cannot encrypt client password to send to server: " + e.getMessage());
+        }
+
+        // Return the response to the server
+        socketHandler.writeBytes(ByteBuffer.wrap(encodedPassword), encodedPassword.length);
+        socketHandler.flushWriteBuffer();
+        s_logger.info("Finished VNCAuth security");
+    }
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncSecurity.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncSecurity.java
new file mode 100644
index 00000000000..2423e2be0d3
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncSecurity.java
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.security;
+
+import com.cloud.consoleproxy.vnc.RfbConstants;
+import com.cloud.consoleproxy.vnc.network.NioSocketHandler;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public interface VncSecurity {
+
+    static List<VncSecurity> getSecurityStack(int securityType, String vmPassword, String host, int port) {
+        switch (securityType) {
+            case RfbConstants.NO_AUTH:
+                return Collections.singletonList(new NoneVncSecurity());
+            case RfbConstants.VNC_AUTH:
+                return Collections.singletonList(new VncAuthSecurity(vmPassword));
+            // Do not add VEncrypt type = 19 but its supported subtypes
+            case RfbConstants.V_ENCRYPT_X509_VNC:
+                return Arrays.asList(new VncTLSSecurity(host, port), new VncAuthSecurity(vmPassword));
+            default:
+                throw new CloudRuntimeException("Unsupported security type " + securityType);
+        }
+    }
+
+    void process(NioSocketHandler socketHandler) throws IOException;
+}
diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncTLSSecurity.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncTLSSecurity.java
new file mode 100644
index 00000000000..c11be02a3c2
--- /dev/null
+++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/security/VncTLSSecurity.java
@@ -0,0 +1,103 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy.vnc.security;
+
+import com.cloud.consoleproxy.util.Logger;
+import com.cloud.consoleproxy.vnc.RfbConstants;
+import com.cloud.consoleproxy.vnc.network.NioSocketHandler;
+import com.cloud.consoleproxy.vnc.network.NioSocketSSLEngineManager;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.nio.Link;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+
+public class VncTLSSecurity implements VncSecurity {
+
+    private static final Logger s_logger = Logger.getLogger(VncTLSSecurity.class);
+
+    private SSLContext ctx;
+    private SSLEngine engine;
+    private NioSocketSSLEngineManager manager;
+
+    private final String host;
+    private final int port;
+
+    public VncTLSSecurity(String host, int port) {
+        this.host = host;
+        this.port = port;
+    }
+
+    private void initGlobal() {
+        try {
+            ctx = Link.initClientSSLContext();
+        } catch (GeneralSecurityException | IOException e) {
+            throw new CloudRuntimeException("Unable to initialize SSL context", e);
+        }
+    }
+
+    private void setParam() {
+        engine = ctx.createSSLEngine(this.host, this.port);
+        engine.setUseClientMode(true);
+
+        String[] supported = engine.getSupportedProtocols();
+        ArrayList<String> enabled = new ArrayList<>();
+        for (String s : supported) {
+            if (s.matches("TLS.*") || s.matches("X509.*")) {
+                enabled.add(s);
+            }
+        }
+        engine.setEnabledProtocols(enabled.toArray(new String[0]));
+
+        engine.setEnabledCipherSuites(engine.getSupportedCipherSuites());
+    }
+
+    @Override
+    public void process(NioSocketHandler socketHandler) {
+        s_logger.info("Processing VNC TLS security");
+
+        initGlobal();
+
+        if (manager == null) {
+            if (socketHandler.readUnsignedInteger(8) == 0) {
+                int result = socketHandler.readUnsignedInteger(32);
+                String reason;
+                if (result == RfbConstants.VNC_AUTH_FAILED || result == RfbConstants.VNC_AUTH_TOO_MANY) {
+                    reason = socketHandler.readString();
+                } else {
+                    reason = "Authentication failure (protocol error)";
+                }
+                throw new CloudRuntimeException(reason);
+            }
+            setParam();
+        }
+
+        try {
+            manager = new NioSocketSSLEngineManager(engine, socketHandler);
+            manager.doHandshake();
+        } catch(java.lang.Exception e) {
+            throw new CloudRuntimeException(e.toString());
+        }
+    }
+
+    public NioSocketSSLEngineManager getSSLEngineManager() {
+        return manager;
+    }
+}
diff --git a/services/console-proxy/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyNoVncClientTest.java b/services/console-proxy/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyNoVncClientTest.java
new file mode 100644
index 00000000000..92665945eba
--- /dev/null
+++ b/services/console-proxy/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyNoVncClientTest.java
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.consoleproxy;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ConsoleProxyNoVncClientTest {
+    @Test
+    public void rewriteServerNameInServerInitTest() {
+        String serverName = "server123, backend:TLS";
+        byte[] serverInitTestBytes = new byte[]{ 4, 0, 3, 0, 32, 24, 0, 1, 0, -1, 0, -1, 0, -1, 16, 8, 0, 0, 0, 0, 0, 0, 0, 15, 81, 69, 77, 85, 32, 40, 105, 45, 50, 45, 56, 45, 86, 77, 41};
+        byte[] newServerInit = ConsoleProxyNoVncClient.rewriteServerNameInServerInit(serverInitTestBytes, serverName);
+
+        byte[] expectedBytes = new byte[]{4, 0, 3, 0, 32, 24, 0, 1, 0, -1, 0, -1, 0, -1, 16, 8, 0, 0, 0, 0, 0, 0, 0, 22, 115, 101, 114, 118, 101, 114, 49, 50, 51, 44, 32, 98, 97, 99, 107, 101, 110, 100, 58, 84, 76, 83};
+        Assert.assertArrayEquals(newServerInit, expectedBytes);
+    }
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/styles/base.css b/systemvm/agent/noVNC/app/styles/base.css
index c7893b3205c..ca236a9e112 100644
--- a/systemvm/agent/noVNC/app/styles/base.css
+++ b/systemvm/agent/noVNC/app/styles/base.css
@@ -771,6 +771,12 @@ select:active {
 #noVNC_status.noVNC_status_warn::before {
   content: url("../images/warning.svg") " ";
 }
+#noVNC_status.noVNC_status_tls_success {
+  background: rgba(6, 199, 38, 0.9);
+}
+#noVNC_status.noVNC_status_tls_success::before {
+  content: url("../images/connect.svg") " ";
+}
 
 /* ----------------------------------------
  * Connect Dialog
diff --git a/systemvm/agent/noVNC/app/ui.js b/systemvm/agent/noVNC/app/ui.js
index f503f55c6c9..05026acedb3 100644
--- a/systemvm/agent/noVNC/app/ui.js
+++ b/systemvm/agent/noVNC/app/ui.js
@@ -455,6 +455,9 @@ const UI = {
         if (typeof statusType === 'undefined') {
             statusType = 'normal';
         }
+        if (UI.getSetting('encrypt')) {
+            statusType = 'encrypted';
+        }
 
         // Don't overwrite more severe visible statuses and never
         // errors. Only shows the first error.
@@ -471,15 +474,23 @@ const UI = {
         clearTimeout(UI.statusTimeout);
 
         switch (statusType) {
+            case 'encrypted':
+                statusElem.classList.remove("noVNC_status_warn");
+                statusElem.classList.remove("noVNC_status_normal");
+                statusElem.classList.remove("noVNC_status_error");
+                statusElem.classList.add("noVNC_status_tls_success");
+                break;
             case 'error':
                 statusElem.classList.remove("noVNC_status_warn");
                 statusElem.classList.remove("noVNC_status_normal");
+                statusElem.classList.remove("noVNC_status_tls_success");
                 statusElem.classList.add("noVNC_status_error");
                 break;
             case 'warning':
             case 'warn':
                 statusElem.classList.remove("noVNC_status_error");
                 statusElem.classList.remove("noVNC_status_normal");
+                statusElem.classList.remove("noVNC_status_tls_success");
                 statusElem.classList.add("noVNC_status_warn");
                 break;
             case 'normal':
@@ -487,6 +498,7 @@ const UI = {
             default:
                 statusElem.classList.remove("noVNC_status_error");
                 statusElem.classList.remove("noVNC_status_warn");
+                statusElem.classList.remove("noVNC_status_tls_success");
                 statusElem.classList.add("noVNC_status_normal");
                 break;
         }
@@ -494,9 +506,9 @@ const UI = {
         statusElem.textContent = text;
         statusElem.classList.add("noVNC_open");
 
-        // If no time was specified, show the status for 1.5 seconds
+        // If no time was specified, show the status for 4 seconds
         if (typeof time === 'undefined') {
-            time = 1500;
+            time = 4000;
         }
 
         // Error messages do not timeout
@@ -1101,9 +1113,9 @@ const UI = {
 
         let msg;
         if (UI.getSetting('encrypt')) {
-            msg = _("Connected");
+            msg = _("Connected (encrypted) to ") + UI.desktopName;
         } else {
-            msg = _("Connected")
+            msg = _("Connected (unencrypted) to ") + UI.desktopName;
         }
         UI.showStatus(msg);
         UI.updateVisualState('connected');
@@ -1662,6 +1674,10 @@ const UI = {
         UI.desktopName = e.detail.name;
         // Display the desktop name in the document title
         document.title = e.detail.name + " - " + PAGE_TITLE;
+        if (e.detail.name.includes('(TLS backend)')) {
+            UI.forceSetting('encrypt', true);
+            UI.enableSetting('encrypt');
+        }
     },
 
     bell(e) {
diff --git a/systemvm/agent/noVNC/core/rfb.js b/systemvm/agent/noVNC/core/rfb.js
index c38e8e5e6f1..056e55f439a 100644
--- a/systemvm/agent/noVNC/core/rfb.js
+++ b/systemvm/agent/noVNC/core/rfb.js
@@ -1634,7 +1634,10 @@ export default class RFB extends EventTargetMixin {
 
     _negotiateAuthentication() {
         switch (this._rfbAuthScheme) {
+            // Let CloudStack handle the authentication (RFB 3.8 requires the client to select the auth scheme)
             case 1:  // no auth
+            case 2:  // VNC authentication
+            case 19: // VeNCrypt Security Type
                 if (this._rfbVersion >= 3.8) {
                     this._rfbInitState = 'SecurityResult';
                     return true;
@@ -1645,15 +1648,9 @@ export default class RFB extends EventTargetMixin {
             case 22:  // XVP auth
                 return this._negotiateXvpAuth();
 
-            case 2:  // VNC authentication
-                return this._negotiateStdVNCAuth();
-
             case 16:  // TightVNC Security Type
                 return this._negotiateTightAuth();
 
-            case 19:  // VeNCrypt Security Type
-                return this._negotiateVeNCryptAuth();
-
             case 129:  // TightVNC UNIX Security Type
                 return this._negotiateTightUnixAuth();
 
diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py
index d9878b09765..aef039c98eb 100644
--- a/test/integration/smoke/test_vm_life_cycle.py
+++ b/test/integration/smoke/test_vm_life_cycle.py
@@ -1009,6 +1009,7 @@ class TestSecuredVmMigration(cloudstackTestCase):
                       sed -i 's/listen_tls.*/listen_tls=0/g' /etc/libvirt/libvirtd.conf && \
                       sed -i 's/listen_tcp.*/listen_tcp=1/g' /etc/libvirt/libvirtd.conf && \
                       sed -i '/.*_file=.*/d' /etc/libvirt/libvirtd.conf && \
+                      sed -i 's/vnc_tls.*/vnc_tls=0/g' /etc/libvirt/qemu.conf && \
                       service libvirtd start ; \
                       service libvirt-bin start ; \
                       sleep 30 ; \