From: Quentin Smith Date: Mon, 8 Oct 2007 08:35:12 +0000 (-0400) Subject: SSL support for VNC proxy X-Git-Tag: sipb-xen-vnc-client/1~2 X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-vnc-client.git/commitdiff_plain/46a087a65293d806d15601a775010670baecd066 SSL support for VNC proxy svn path=/trunk/vnc/vnc_javasrc/; revision=143 --- diff --git a/Makefile b/Makefile index af6bcec..7948898 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,8 @@ CLASSES = VncViewer.class RfbProto.class AuthPanel.class VncCanvas.class \ SocketFactory.class HTTPConnectSocketFactory.class \ VNCProxyConnectSocketFactory.class VNCProxyConnectSocket.class \ HTTPConnectSocket.class ReloginPanel.class \ - InStream.class MemInStream.class ZlibInStream.class + InStream.class MemInStream.class ZlibInStream.class \ + VNCProxyConnectSocketWrapper.class SocketWrapper.class SocketWrapper\$$WrappingSocketImpl.class SIPBTrustManager.class SOURCES = VncViewer.java RfbProto.java AuthPanel.java VncCanvas.java \ VncCanvas2.java \ @@ -29,7 +30,10 @@ SOURCES = VncViewer.java RfbProto.java AuthPanel.java VncCanvas.java \ SocketFactory.java HTTPConnectSocketFactory.java \ VNCProxyConnectSocketFactory.java VNCProxyConnectSocket.java \ HTTPConnectSocket.java ReloginPanel.java \ - InStream.java MemInStream.java ZlibInStream.java + InStream.java MemInStream.java ZlibInStream.java \ + VNCProxyConnectSocketWrapper.java SocketWrapper.java SIPBTrustManager.java + +EXTRAJAR = trust.store all: $(CLASSES) $(ARCHIVE) @@ -37,7 +41,7 @@ $(CLASSES): $(SOURCES) $(JC) $(JCFLAGS) -O $(SOURCES) $(ARCHIVE): $(CLASSES) $(MANIFEST) - $(JAR) cfm $(ARCHIVE) $(MANIFEST) $(CLASSES) + $(JAR) cfm $(ARCHIVE) $(MANIFEST) $(CLASSES) $(EXTRAJAR) install: $(CLASSES) $(ARCHIVE) $(CP) $(CLASSES) $(ARCHIVE) $(PAGES) $(INSTALL_DIR) diff --git a/SIPBTrustManager.java b/SIPBTrustManager.java new file mode 100644 index 0000000..891ef95 --- /dev/null +++ b/SIPBTrustManager.java @@ -0,0 +1,122 @@ +/* + * Copyright 2006 Perry Nguyen + * Licensed 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. + */ +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +public class SIPBTrustManager implements X509TrustManager { + private X509TrustManager trustManager; + private final static char[] KEY_STORE_PASSWORD = + { 'f', 'o', 'o', 'b', 'a', 'r' }; + private final static String KEY_STORE_RESOURCE = + "trust.store"; + + private KeyStore loadKeyStore() throws Exception { + InputStream in = getClass().getClassLoader().getResourceAsStream( + KEY_STORE_RESOURCE); + KeyStore ks = null; + try { + if (in == null) { + //log.severe("Unable to open KeyStore"); + throw new NullPointerException(); + } + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + ks.load(in, KEY_STORE_PASSWORD); + /*if (log.isLoggable(Level.FINEST)) { + for (Enumeration aliases = ks.aliases(); + aliases.hasMoreElements();) { + String alias = aliases.nextElement(); + log.finest("ALIAS: " + alias); + } + }*/ + } catch (NoSuchAlgorithmException e) { + throwError(e); + } catch (CertificateException e) { + throwError(e); + } catch (IOException e) { + throwError(e); + } catch (KeyStoreException e) { + throwError(e); + } finally { + try { + if (in != null) + in.close(); + } + catch (IOException e) { } // ignore + } + return ks; + } + private void createTrustManager() { + try { + try { + KeyStore keystore = loadKeyStore(); + TrustManagerFactory factory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + factory.init(keystore); + TrustManager[] trustManagers = factory.getTrustManagers(); + if (trustManagers.length == 0) + throw new IllegalStateException("No trust manager found"); + setTrustManager((X509TrustManager) trustManagers[0]); + } catch (NoSuchAlgorithmException e) { + throwError(e); + } catch (KeyStoreException e) { + throwError(e); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + private void throwError(Exception e) throws Exception { + //HttpClientError error = new HttpClientError(e.getMessage()); + //error.initCause(e); + throw e; + } + public X509TrustManager getTrustManager() { + if (trustManager == null) + createTrustManager(); + return trustManager; + } + + public void setTrustManager(X509TrustManager trustManager) { + this.trustManager = trustManager; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + getTrustManager().checkClientTrusted(chain, authType); + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + getTrustManager().checkServerTrusted(chain, authType); + + } + + public X509Certificate[] getAcceptedIssuers() { + return getTrustManager().getAcceptedIssuers(); + } + +} \ No newline at end of file diff --git a/SocketWrapper.java b/SocketWrapper.java new file mode 100644 index 0000000..2920732 --- /dev/null +++ b/SocketWrapper.java @@ -0,0 +1,252 @@ +/* + * Written by Dawid Kurzyniec and released to the public domain, as explained + * at http://creativecommons.org/licenses/publicdomain + */ + +//package edu.emory.mathcs.util.net; + +import java.io.*; +import java.net.*; +import java.nio.channels.*; + +/** + * Wrapper for sockets which enables to add functionality in subclasses + * on top of existing, connected sockets. It is useful when direct subclassing + * of delegate socket class is not possible, e.g. if the delegate socket is + * created by a library. Possible usage example is socket factory chaining. + * This class delegates all socket-related requests to the wrapped delegate, + * as of JDK 1.4. + * + * @author Dawid Kurzyniec + * @version 1.4 + */ +public abstract class SocketWrapper extends Socket { + + /** + * the wrapped delegate socket. + */ + protected final Socket delegate; + + /** + * Creates new socket wrapper for a given socket. The delegate + * must be connected and bound and it must not be closed. + * @param delegate the delegate socket to wrap + * @throws SocketException if the delegate socket is closed, not bound, + * or not connected + */ + protected SocketWrapper(Socket delegate) throws SocketException { + super(new WrappingSocketImpl(delegate)); + this.delegate = delegate; + } + + public SocketChannel getChannel() { + return delegate.getChannel(); + } + + /** + * Returns true, indicating that the socket is bound. + * + * @return true + */ + public boolean isBound() { + return true; + } + + public boolean isClosed() { + return super.isClosed() || delegate.isClosed(); + } + + /** + * Returns true, indicating that the socket is connected. + * + * @return true + */ + public boolean isConnected() { + return true; + } + + public boolean isInputShutdown() { + return super.isInputShutdown() || delegate.isInputShutdown(); + } + + public boolean isOutputShutdown() { + return super.isInputShutdown() || delegate.isOutputShutdown(); + } + + private static class WrappingSocketImpl extends SocketImpl { + private final Socket delegate; + WrappingSocketImpl(Socket delegate) throws SocketException { + if (delegate == null) { + throw new NullPointerException(); + } + if (delegate.isClosed()) { + throw new SocketException("Delegate server socket is closed"); + } + if (!(delegate.isBound())) { + throw new SocketException("Delegate server socket is not bound"); + } + if (!(delegate.isConnected())) { + throw new SocketException("Delegate server socket is not connected"); + } + this.delegate = delegate; + } + + protected void create(boolean stream) {} + + protected void connect(String host, int port) { + // delegate is always connected, thus this method is never called + throw new UnsupportedOperationException(); + } + + protected void connect(InetAddress address, int port) { + // delegate is always connected, thus this method is never called + throw new UnsupportedOperationException(); + } + + protected void connect(SocketAddress address, int timeout) { + // delegate is always connected, thus this method is never called + throw new UnsupportedOperationException(); + } + + protected void bind(InetAddress host, int port) { + // delegate is always bound, thus this method is never called + throw new UnsupportedOperationException(); + } + + protected void listen(int backlog) { + // this wrapper is never used by a ServerSocket + throw new UnsupportedOperationException(); + } + + protected void accept(SocketImpl s) { + // this wrapper is never used by a ServerSocket + throw new UnsupportedOperationException(); + } + + protected InputStream getInputStream() throws IOException { + return delegate.getInputStream(); + } + + protected OutputStream getOutputStream() throws IOException { + return delegate.getOutputStream(); + } + + protected int available() throws IOException { + return getInputStream().available(); + } + + protected void close() throws IOException { + delegate.close(); + } + + protected void shutdownInput() throws IOException { + delegate.shutdownInput(); + } + + protected void shutdownOutput() throws IOException { + delegate.shutdownOutput(); + } + + protected FileDescriptor getFileDescriptor() { + // this wrapper is never used by a ServerSocket + throw new UnsupportedOperationException(); + } + + protected InetAddress getInetAddress() { + return delegate.getInetAddress(); + } + + protected int getPort() { + return delegate.getPort(); + } + + protected boolean supportsUrgentData() { + return false; // must be overridden in sub-class + } + + protected void sendUrgentData (int data) throws IOException { + delegate.sendUrgentData(data); + } + + protected int getLocalPort() { + return delegate.getLocalPort(); + } + + public Object getOption(int optID) throws SocketException { + switch (optID) { + case SocketOptions.IP_TOS: + return new Integer(delegate.getTrafficClass()); + case SocketOptions.SO_BINDADDR: + return delegate.getLocalAddress(); + case SocketOptions.SO_KEEPALIVE: + return Boolean.valueOf(delegate.getKeepAlive()); + case SocketOptions.SO_LINGER: + return new Integer(delegate.getSoLinger()); + case SocketOptions.SO_OOBINLINE: + return Boolean.valueOf(delegate.getOOBInline()); + case SocketOptions.SO_RCVBUF: + return new Integer(delegate.getReceiveBufferSize()); + case SocketOptions.SO_REUSEADDR: + return Boolean.valueOf(delegate.getReuseAddress()); + case SocketOptions.SO_SNDBUF: + return new Integer(delegate.getSendBufferSize()); + case SocketOptions.SO_TIMEOUT: + return new Integer(delegate.getSoTimeout()); + case SocketOptions.TCP_NODELAY: + return Boolean.valueOf(delegate.getTcpNoDelay()); + case SocketOptions.SO_BROADCAST: + default: + throw new IllegalArgumentException("Unsupported option type"); + } + } + + public void setOption(int optID, Object value) throws SocketException { + switch (optID) { + case SocketOptions.SO_BINDADDR: + throw new IllegalArgumentException("Socket is bound"); + case SocketOptions.SO_KEEPALIVE: + delegate.setKeepAlive(((Boolean)value).booleanValue()); + break; + case SocketOptions.SO_LINGER: + if (value instanceof Boolean) { + delegate.setSoLinger(((Boolean)value).booleanValue(), 0); + } + else { + delegate.setSoLinger(true, ((Integer)value).intValue()); + } + break; + case SocketOptions.SO_OOBINLINE: + delegate.setOOBInline(((Boolean)value).booleanValue()); + break; + case SocketOptions.SO_RCVBUF: + delegate.setReceiveBufferSize(((Integer)value).intValue()); + break; + case SocketOptions.SO_REUSEADDR: + delegate.setReuseAddress(((Boolean)value).booleanValue()); + break; + case SocketOptions.SO_SNDBUF: + delegate.setSendBufferSize(((Integer)value).intValue()); + break; + case SocketOptions.SO_TIMEOUT: + delegate.setSoTimeout(((Integer)value).intValue()); + break; + case SocketOptions.TCP_NODELAY: + delegate.setTcpNoDelay(((Boolean)value).booleanValue()); + break; + case SocketOptions.SO_BROADCAST: + default: + throw new IllegalArgumentException("Unsupported option type"); + } + } + } + + public boolean equals(Object obj) { + if (!(obj instanceof SocketWrapper)) return false; + SocketWrapper that = (SocketWrapper)obj; + return this.delegate.equals(that.delegate); + } + + public int hashCode() { + return delegate.hashCode() ^ 0x01010101; + } +} \ No newline at end of file diff --git a/VNCProxyConnectSocketFactory.java b/VNCProxyConnectSocketFactory.java index c0386f1..d05fcf6 100644 --- a/VNCProxyConnectSocketFactory.java +++ b/VNCProxyConnectSocketFactory.java @@ -26,10 +26,26 @@ import java.applet.*; import java.net.*; +import javax.net.ssl.*; import java.io.*; class VNCProxyConnectSocketFactory implements SocketFactory { + SSLSocketFactory factory; + + public VNCProxyConnectSocketFactory() { + try { + SSLContext c = SSLContext.getInstance("SSL"); + c.init(null, + new TrustManager[] { new SIPBTrustManager() }, + null); + factory = + (SSLSocketFactory)c.getSocketFactory(); + } catch (Exception e) { + e.printStackTrace(); + } + } + public Socket createSocket(String host, int port, Applet applet) throws IOException { @@ -57,8 +73,10 @@ class VNCProxyConnectSocketFactory implements SocketFactory { System.out.println("VNCProxy CONNECT via proxy " + host + " port " + port + " to vm " + vmname); - VNCProxyConnectSocket s = - new VNCProxyConnectSocket(host, port, vmname, authtoken); + SSLSocket ssls = (SSLSocket)factory.createSocket(host, port); + ssls.startHandshake(); + VNCProxyConnectSocketWrapper s = + new VNCProxyConnectSocketWrapper(ssls, vmname, authtoken); return (Socket)s; } diff --git a/VNCProxyConnectSocketWrapper.java b/VNCProxyConnectSocketWrapper.java new file mode 100644 index 0000000..69a0fa8 --- /dev/null +++ b/VNCProxyConnectSocketWrapper.java @@ -0,0 +1,60 @@ +// +// Copyright (C) 2002 Constantin Kaplinsky, Inc. All Rights Reserved. +// Copyright 2007 MIT Student Information Processing Board +// +// This is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// VNCProxySocket.java together with VNCProxySocketFactory.java +// implement an alternate way to connect to VNC servers via one or two +// VNCProxy proxies supporting the VNCProxy VNCCONNECT method. +// + +import java.net.*; +import java.io.*; + +class VNCProxyConnectSocketWrapper extends SocketWrapper { + + public VNCProxyConnectSocketWrapper(Socket sock, + String vmname, String authtoken) + throws IOException { + + super(sock); + + // Send the CONNECT request + getOutputStream().write(("CONNECTVNC " + vmname + + " VNCProxy/1.0\r\nAuth-token: " + authtoken + + "\r\n\r\n").getBytes()); + + // Read the first line of the response + DataInputStream is = new DataInputStream(getInputStream()); + String str = is.readLine(); + + // Check the HTTP error code -- it should be "200" on success + if (!str.startsWith("VNCProxy/1.0 200 ")) { + if (str.startsWith("VNCProxy/1.0 ")) + str = str.substring(13); + throw new IOException("Proxy reports \"" + str + "\""); + } + + // Success -- skip remaining HTTP headers + do { + str = is.readLine(); + } while (str.length() != 0); + } +} + diff --git a/trust.store b/trust.store new file mode 100644 index 0000000..83e9257 Binary files /dev/null and b/trust.store differ