summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
authorBenoit Foucher <benoit@zeroc.com>2007-11-26 10:27:33 +0100
committerBenoit Foucher <benoit@zeroc.com>2007-11-26 10:27:33 +0100
commita680e1c9cb73caa66d2cab436e28e95924dd8dbd (patch)
tree473abb5c732c0cc5cb61b44ed5f74fc74d83b1ec /java/src
parentMerge branch 'master' of ssh://cvs.zeroc.com/home/git/ice (diff)
downloadice-a680e1c9cb73caa66d2cab436e28e95924dd8dbd.tar.bz2
ice-a680e1c9cb73caa66d2cab436e28e95924dd8dbd.tar.xz
ice-a680e1c9cb73caa66d2cab436e28e95924dd8dbd.zip
Remove JDK 1.4 IceSSL plugin
Diffstat (limited to 'java/src')
-rw-r--r--java/src/IceInternal/Time.java19
-rw-r--r--java/src/IceSSL/AcceptorI.java318
-rw-r--r--java/src/IceSSL/CertificateVerifier.java23
-rw-r--r--java/src/IceSSL/ConnectionInfo.java52
-rw-r--r--java/src/IceSSL/ConnectionInvalidException.java30
-rw-r--r--java/src/IceSSL/ConnectorI.java221
-rw-r--r--java/src/IceSSL/EndpointFactoryI.java50
-rw-r--r--java/src/IceSSL/EndpointI.java518
-rw-r--r--java/src/IceSSL/Instance.java995
-rw-r--r--java/src/IceSSL/PasswordCallback.java36
-rw-r--r--java/src/IceSSL/Plugin.java54
-rw-r--r--java/src/IceSSL/PluginFactory.java19
-rw-r--r--java/src/IceSSL/PluginI.java68
-rw-r--r--java/src/IceSSL/RFC2253.java417
-rw-r--r--java/src/IceSSL/TransceiverI.java854
-rw-r--r--java/src/IceSSL/TrustManager.java295
-rw-r--r--java/src/IceSSL/Util.java127
-rw-r--r--java/src/IceSSL/X509KeyManagerI.java70
-rw-r--r--java/src/IceSSL/X509TrustManagerI.java50
19 files changed, 4216 insertions, 0 deletions
diff --git a/java/src/IceInternal/Time.java b/java/src/IceInternal/Time.java
new file mode 100644
index 00000000000..d77879ef836
--- /dev/null
+++ b/java/src/IceInternal/Time.java
@@ -0,0 +1,19 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceInternal;
+
+final public class Time
+{
+ static public long
+ currentMonotonicTimeMillis()
+ {
+ return System.nanoTime() / 1000000;
+ }
+};
diff --git a/java/src/IceSSL/AcceptorI.java b/java/src/IceSSL/AcceptorI.java
new file mode 100644
index 00000000000..04f2b3331ab
--- /dev/null
+++ b/java/src/IceSSL/AcceptorI.java
@@ -0,0 +1,318 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+final class AcceptorI implements IceInternal.Acceptor
+{
+ public java.nio.channels.ServerSocketChannel
+ fd()
+ {
+ return _fd;
+ }
+
+ public void
+ close()
+ {
+ if(_instance.networkTraceLevel() >= 1)
+ {
+ String s = "stopping to accept ssl connections at " + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ java.nio.channels.ServerSocketChannel fd;
+ java.nio.channels.Selector selector;
+ synchronized(this)
+ {
+ fd = _fd;
+ selector = _selector;
+ _fd = null;
+ _selector = null;
+ }
+ if(fd != null)
+ {
+ try
+ {
+ fd.close();
+ }
+ catch(java.io.IOException ex)
+ {
+ // Ignore.
+ }
+ }
+ if(selector != null)
+ {
+ try
+ {
+ selector.close();
+ }
+ catch(java.io.IOException ex)
+ {
+ // Ignore.
+ }
+ }
+ }
+
+ public void
+ listen()
+ {
+ // Nothing to do.
+
+ if(_instance.networkTraceLevel() >= 1)
+ {
+ String s = "accepting ssl connections at " + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+ }
+
+ public IceInternal.Transceiver
+ accept(int timeout)
+ {
+ //
+ // The plugin may not be fully initialized.
+ //
+ if(!_instance.initialized())
+ {
+ Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
+ ex.reason = "IceSSL: plugin is not initialized";
+ throw ex;
+ }
+
+ java.nio.channels.SocketChannel fd = null;
+ while(fd == null)
+ {
+ try
+ {
+ fd = _fd.accept();
+ if(fd == null)
+ {
+ if(_selector == null)
+ {
+ _selector = java.nio.channels.Selector.open();
+ }
+
+ while(true)
+ {
+ try
+ {
+ java.nio.channels.SelectionKey key =
+ _fd.register(_selector, java.nio.channels.SelectionKey.OP_ACCEPT);
+ if(timeout > 0)
+ {
+ if(_selector.select(timeout) == 0)
+ {
+ throw new Ice.TimeoutException();
+ }
+ }
+ else if(timeout == 0)
+ {
+ if(_selector.selectNow() == 0)
+ {
+ throw new Ice.TimeoutException();
+ }
+ }
+ else
+ {
+ _selector.select();
+ }
+
+ break;
+ }
+ catch(java.io.IOException ex)
+ {
+ if(IceInternal.Network.interrupted(ex))
+ {
+ continue;
+ }
+ Ice.SocketException se = new Ice.SocketException();
+ se.initCause(ex);
+ throw se;
+ }
+ }
+ }
+ }
+ catch(java.io.IOException ex)
+ {
+ if(IceInternal.Network.interrupted(ex))
+ {
+ continue;
+ }
+ Ice.SocketException se = new Ice.SocketException();
+ se.initCause(ex);
+ throw se;
+ }
+ }
+
+ //
+ // Check whether this socket is the result of a call to connectToSelf.
+ // Despite the fact that connectToSelf immediately closes the socket,
+ // the server-side handshake process does not raise an exception.
+ // Furthermore, we can't simply proceed with the regular handshake
+ // process because we don't want to pass such a socket to the
+ // certificate verifier (if any).
+ //
+ // In order to detect a call to connectToSelf, we compare the remote
+ // address of the newly-accepted socket to that in _connectToSelfAddr.
+ //
+ java.net.SocketAddress remoteAddr = fd.socket().getRemoteSocketAddress();
+ synchronized(this)
+ {
+ if(remoteAddr.equals(_connectToSelfAddr))
+ {
+ try
+ {
+ fd.close();
+ }
+ catch(java.io.IOException e)
+ {
+ }
+ return null;
+ }
+ }
+
+ javax.net.ssl.SSLEngine engine = null;
+ try
+ {
+ try
+ {
+ java.net.Socket socket = fd.socket();
+ socket.setTcpNoDelay(true);
+ socket.setKeepAlive(true);
+ }
+ catch(java.io.IOException ex)
+ {
+ Ice.SocketException se = new Ice.SocketException();
+ se.initCause(ex);
+ throw se;
+ }
+
+ IceInternal.Network.setBlock(fd, false);
+ IceInternal.Network.setTcpBufSize(fd, _instance.communicator().getProperties(), _logger);
+
+ engine = _instance.createSSLEngine(true);
+ }
+ catch(RuntimeException ex)
+ {
+ try
+ {
+ fd.close();
+ }
+ catch(java.io.IOException e)
+ {
+ // Ignore.
+ }
+ throw ex;
+ }
+
+ if(_instance.networkTraceLevel() >= 1)
+ {
+ _logger.trace(_instance.networkTraceCategory(), "attempting to accept ssl connection\n" +
+ IceInternal.Network.fdToString(fd));
+ }
+
+ return new TransceiverI(_instance, engine, fd, "", true, _adapterName);
+ }
+
+ public void
+ connectToSelf()
+ {
+ java.nio.channels.SocketChannel fd = IceInternal.Network.createTcpSocket();
+ IceInternal.Network.setBlock(fd, false);
+ synchronized(this)
+ {
+ //
+ // connectToSelf is called to wake up the thread blocked in
+ // accept. We remember the originating address for use in
+ // accept. See accept for details.
+ //
+ IceInternal.Network.doConnect(fd, _addr, -1);
+ _connectToSelfAddr = (java.net.InetSocketAddress)fd.socket().getLocalSocketAddress();
+ }
+ IceInternal.Network.closeSocket(fd);
+ }
+
+ public String
+ toString()
+ {
+ return IceInternal.Network.addrToString(_addr);
+ }
+
+ int
+ effectivePort()
+ {
+ return _addr.getPort();
+ }
+
+ AcceptorI(Instance instance, String adapterName, String host, int port)
+ {
+ _instance = instance;
+ _adapterName = adapterName;
+ _logger = instance.communicator().getLogger();
+ _backlog = 0;
+
+ if(_backlog <= 0)
+ {
+ _backlog = 5;
+ }
+
+ try
+ {
+ _fd = IceInternal.Network.createTcpServerSocket();
+ IceInternal.Network.setBlock(_fd, false);
+ IceInternal.Network.setTcpBufSize(_fd, _instance.communicator().getProperties(), _logger);
+ if(!System.getProperty("os.name").startsWith("Windows"))
+ {
+ //
+ // Enable SO_REUSEADDR on Unix platforms to allow
+ // re-using the socket even if it's in the TIME_WAIT
+ // state. On Windows, this doesn't appear to be
+ // necessary and enabling SO_REUSEADDR would actually
+ // not be a good thing since it allows a second
+ // process to bind to an address even it's already
+ // bound by another process.
+ //
+ // TODO: using SO_EXCLUSIVEADDRUSE on Windows would
+ // probably be better but it's only supported by recent
+ // Windows versions (XP SP2, Windows Server 2003).
+ //
+ IceInternal.Network.setReuseAddress(_fd, true);
+ }
+ _addr = new java.net.InetSocketAddress(host, port);
+ if(_instance.networkTraceLevel() >= 2)
+ {
+ String s = "attempting to bind to ssl socket " + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+ _addr = IceInternal.Network.doBind(_fd, _addr);
+ }
+ catch(RuntimeException ex)
+ {
+ _fd = null;
+ throw ex;
+ }
+ }
+
+ protected synchronized void
+ finalize()
+ throws Throwable
+ {
+ IceUtil.Assert.FinalizerAssert(_fd == null);
+
+ super.finalize();
+ }
+
+ private Instance _instance;
+ private String _adapterName;
+ private Ice.Logger _logger;
+ private java.nio.channels.ServerSocketChannel _fd;
+ private int _backlog;
+ private java.net.InetSocketAddress _addr;
+ private java.nio.channels.Selector _selector;
+ private java.net.InetSocketAddress _connectToSelfAddr;
+}
diff --git a/java/src/IceSSL/CertificateVerifier.java b/java/src/IceSSL/CertificateVerifier.java
new file mode 100644
index 00000000000..8426eae8ffe
--- /dev/null
+++ b/java/src/IceSSL/CertificateVerifier.java
@@ -0,0 +1,23 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+//
+// An application can customize the certificate verification process
+// by implementing the CertificateVerifier interface.
+//
+public interface CertificateVerifier
+{
+ //
+ // Return false if the connection should be rejected, or true to
+ // allow it.
+ //
+ boolean verify(ConnectionInfo info);
+}
diff --git a/java/src/IceSSL/ConnectionInfo.java b/java/src/IceSSL/ConnectionInfo.java
new file mode 100644
index 00000000000..ed7340a44a1
--- /dev/null
+++ b/java/src/IceSSL/ConnectionInfo.java
@@ -0,0 +1,52 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+//
+// ConnectionInfo contains information that may be of use to a
+// CertificateVerifier or an application that wants information
+// about its peer.
+//
+public class ConnectionInfo
+{
+ //
+ // The certificate chain. This may be null if the peer did not
+ // supply a certificate. The peer's certificate (if any) is the
+ // first one in the chain.
+ //
+ public java.security.cert.Certificate[] certs;
+
+ //
+ // The name of the negotiated cipher.
+ //
+ public String cipher;
+
+ //
+ // The local TCP/IP host & port.
+ //
+ public java.net.InetSocketAddress localAddr;
+
+ //
+ // The remote TCP/IP host & port.
+ //
+ public java.net.InetSocketAddress remoteAddr;
+
+ //
+ // If the connection is incoming this bool is true, false
+ // otherwise.
+ //
+ boolean incoming;
+
+ //
+ // The name of the object adapter that hosts this endpoint, if
+ // any.
+ //
+ String adapterName;
+}
diff --git a/java/src/IceSSL/ConnectionInvalidException.java b/java/src/IceSSL/ConnectionInvalidException.java
new file mode 100644
index 00000000000..3b7921ae9cf
--- /dev/null
+++ b/java/src/IceSSL/ConnectionInvalidException.java
@@ -0,0 +1,30 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+public final class ConnectionInvalidException extends Ice.LocalException
+{
+ public ConnectionInvalidException()
+ {
+ }
+
+ public ConnectionInvalidException(String reason)
+ {
+ this.reason = reason;
+ }
+
+ public String
+ ice_name()
+ {
+ return "Ice::ConnectionInvalidException";
+ }
+
+ public String reason;
+}
diff --git a/java/src/IceSSL/ConnectorI.java b/java/src/IceSSL/ConnectorI.java
new file mode 100644
index 00000000000..9836a78753a
--- /dev/null
+++ b/java/src/IceSSL/ConnectorI.java
@@ -0,0 +1,221 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+final class ConnectorI implements IceInternal.Connector, java.lang.Comparable
+{
+ final static short TYPE = 2;
+
+ public IceInternal.Transceiver
+ connect(int timeout)
+ {
+ //
+ // The plugin may not be fully initialized.
+ //
+ if(!_instance.initialized())
+ {
+ Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
+ ex.reason = "IceSSL: plugin is not initialized";
+ throw ex;
+ }
+
+ if(_instance.networkTraceLevel() >= 2)
+ {
+ String s = "trying to establish ssl connection to " + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ java.nio.channels.SocketChannel fd = IceInternal.Network.createTcpSocket();
+ IceInternal.Network.setBlock(fd, false);
+ IceInternal.Network.setTcpBufSize(fd, _instance.communicator().getProperties(), _logger);
+ IceInternal.Network.doConnect(fd, _addr, timeout);
+
+ TransceiverI transceiver = null;
+ try
+ {
+ javax.net.ssl.SSLEngine engine = _instance.createSSLEngine(false);
+
+ transceiver = new TransceiverI(_instance, engine, fd, _host, false, "");
+/*
+ transceiver.waitForHandshake(timeout);
+
+ //
+ // Check IceSSL.VerifyPeer.
+ //
+ int verifyPeer =
+ _instance.communicator().getProperties().getPropertyAsIntWithDefault("IceSSL.VerifyPeer", 2);
+ if(verifyPeer > 0)
+ {
+ try
+ {
+ engine.getSession().getPeerCertificates();
+ }
+ catch(javax.net.ssl.SSLPeerUnverifiedException ex)
+ {
+ Ice.SecurityException e = new Ice.SecurityException();
+ e.reason = "IceSSL: server did not supply a certificate";
+ e.initCause(ex);
+ throw e;
+ }
+ }
+*/
+
+/*
+ if(!ctx.verifyPeer(fd, _host, false))
+ {
+ Ice.SecurityException ex = new Ice.SecurityException();
+ ex.reason = "IceSSL: outgoing connection rejected by certificate verifier";
+ throw ex;
+ }
+*/
+ }
+ catch(RuntimeException ex)
+ {
+ try
+ {
+ fd.close();
+ }
+ catch(java.io.IOException e)
+ {
+ // Ignore.
+ }
+ throw ex;
+ }
+
+ if(_instance.networkTraceLevel() >= 1)
+ {
+ String s = "ssl connection established\n" + IceInternal.Network.fdToString(fd);
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ return transceiver;
+ }
+
+ public short
+ type()
+ {
+ return TYPE;
+ }
+
+ public String
+ toString()
+ {
+ return IceInternal.Network.addrToString(_addr);
+ }
+
+ public int
+ hashCode()
+ {
+ return _hashCode;
+ }
+
+ final boolean
+ equivalent(String host, int port)
+ {
+ java.net.InetSocketAddress addr;
+ try
+ {
+ addr = IceInternal.Network.getAddress(host, port);
+ }
+ catch(Ice.DNSException ex)
+ {
+ return false;
+ }
+ return addr.equals(_addr);
+ }
+
+ //
+ // Only for use by EndpointI.
+ //
+ ConnectorI(Instance instance, java.net.InetSocketAddress addr, int timeout, String connectionId)
+ {
+ _instance = instance;
+ _logger = instance.communicator().getLogger();
+ _host = addr.getHostName();
+ _addr = addr;
+ _timeout = timeout;
+ _connectionId = connectionId;
+
+ _hashCode = _addr.getAddress().getHostAddress().hashCode();
+ _hashCode = 5 * _hashCode + _addr.getPort();
+ _hashCode = 5 * _hashCode + _timeout;
+ _hashCode = 5 * _hashCode + _connectionId.hashCode();
+ }
+
+ //
+ // Compare connectors for sorting purposes
+ //
+ public boolean
+ equals(java.lang.Object obj)
+ {
+ return compareTo(obj) == 0;
+ }
+
+ public int
+ compareTo(java.lang.Object obj) // From java.lang.Comparable
+ {
+ ConnectorI p = null;
+
+ try
+ {
+ p = (ConnectorI)obj;
+ }
+ catch(ClassCastException ex)
+ {
+ try
+ {
+ IceInternal.Connector c = (IceInternal.Connector)obj;
+ return type() < c.type() ? -1 : 1;
+ }
+ catch(ClassCastException ee)
+ {
+ assert(false);
+ }
+ }
+
+ if(this == p)
+ {
+ return 0;
+ }
+
+ if(_timeout < p._timeout)
+ {
+ return -1;
+ }
+ else if(p._timeout < _timeout)
+ {
+ return 1;
+ }
+
+ if(!_connectionId.equals(p._connectionId))
+ {
+ return _connectionId.compareTo(p._connectionId);
+ }
+
+ if(_timeout < p._timeout)
+ {
+ return -1;
+ }
+ else if(p._timeout < _timeout)
+ {
+ return 1;
+ }
+
+ return IceInternal.Network.compareAddress(_addr, p._addr);
+ }
+
+ private Instance _instance;
+ private Ice.Logger _logger;
+ private String _host;
+ private java.net.InetSocketAddress _addr;
+ private int _timeout;
+ private String _connectionId;
+ private int _hashCode;
+}
diff --git a/java/src/IceSSL/EndpointFactoryI.java b/java/src/IceSSL/EndpointFactoryI.java
new file mode 100644
index 00000000000..41f1d1eb12c
--- /dev/null
+++ b/java/src/IceSSL/EndpointFactoryI.java
@@ -0,0 +1,50 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+final class EndpointFactoryI implements IceInternal.EndpointFactory
+{
+ EndpointFactoryI(Instance instance)
+ {
+ _instance = instance;
+ }
+
+ public short
+ type()
+ {
+ return EndpointI.TYPE;
+ }
+
+ public String
+ protocol()
+ {
+ return "ssl";
+ }
+
+ public IceInternal.EndpointI
+ create(String str, boolean server)
+ {
+ return new EndpointI(_instance, str, server);
+ }
+
+ public IceInternal.EndpointI
+ read(IceInternal.BasicStream s)
+ {
+ return new EndpointI(_instance, s);
+ }
+
+ public void
+ destroy()
+ {
+ _instance = null;
+ }
+
+ private Instance _instance;
+}
diff --git a/java/src/IceSSL/EndpointI.java b/java/src/IceSSL/EndpointI.java
new file mode 100644
index 00000000000..8cf0f559fa4
--- /dev/null
+++ b/java/src/IceSSL/EndpointI.java
@@ -0,0 +1,518 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+final class EndpointI extends IceInternal.EndpointI
+{
+ final static short TYPE = 2;
+
+ public
+ EndpointI(Instance instance, String ho, int po, int ti, String conId, boolean co)
+ {
+ _instance = instance;
+ _host = ho;
+ _port = po;
+ _timeout = ti;
+ _connectionId = conId;
+ _compress = co;
+ calcHashValue();
+ }
+
+ public
+ EndpointI(Instance instance, String str, boolean server)
+ {
+ _instance = instance;
+ _host = null;
+ _port = 0;
+ _timeout = -1;
+ _compress = false;
+
+ String[] arr = str.split("[ \t\n\r]+");
+
+ int i = 0;
+ while(i < arr.length)
+ {
+ if(arr[i].length() == 0)
+ {
+ i++;
+ continue;
+ }
+
+ String option = arr[i++];
+ if(option.length() != 2 || option.charAt(0) != '-')
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+
+ String argument = null;
+ if(i < arr.length && arr[i].charAt(0) != '-')
+ {
+ argument = arr[i++];
+ }
+
+ switch(option.charAt(1))
+ {
+ case 'h':
+ {
+ if(argument == null)
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+
+ _host = argument;
+ break;
+ }
+
+ case 'p':
+ {
+ if(argument == null)
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+
+ try
+ {
+ _port = Integer.parseInt(argument);
+ }
+ catch(NumberFormatException ex)
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+
+ if(_port < 0 || _port > 65535)
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+
+ break;
+ }
+
+ case 't':
+ {
+ if(argument == null)
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+
+ try
+ {
+ _timeout = Integer.parseInt(argument);
+ }
+ catch(NumberFormatException ex)
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+
+ break;
+ }
+
+ case 'z':
+ {
+ if(argument != null)
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+
+ _compress = true;
+ break;
+ }
+
+ default:
+ {
+ throw new Ice.EndpointParseException("ssl " + str);
+ }
+ }
+ }
+
+ if(_host == null)
+ {
+ _host = _instance.defaultHost();
+ if(_host == null)
+ {
+ if(server)
+ {
+ _host = "0.0.0.0";
+ }
+ else
+ {
+ _host = "127.0.0.1";
+ }
+ }
+ }
+ else if(_host.equals("*"))
+ {
+ _host = "0.0.0.0";
+ }
+ calcHashValue();
+ }
+
+ public
+ EndpointI(Instance instance, IceInternal.BasicStream s)
+ {
+ _instance = instance;
+ s.startReadEncaps();
+ _host = s.readString();
+ _port = s.readInt();
+ _timeout = s.readInt();
+ _compress = s.readBool();
+ s.endReadEncaps();
+ calcHashValue();
+ }
+
+ //
+ // Marshal the endpoint
+ //
+ public void
+ streamWrite(IceInternal.BasicStream s)
+ {
+ s.writeShort(TYPE);
+ s.startWriteEncaps();
+ s.writeString(_host);
+ s.writeInt(_port);
+ s.writeInt(_timeout);
+ s.writeBool(_compress);
+ s.endWriteEncaps();
+ }
+
+ //
+ // Convert the endpoint to its string form
+ //
+ public String
+ _toString()
+ {
+ //
+ // WARNING: Certain features, such as proxy validation in Glacier2,
+ // depend on the format of proxy strings. Changes to toString() and
+ // methods called to generate parts of the reference string could break
+ // these features. Please review for all features that depend on the
+ // format of proxyToString() before changing this and related code.
+ //
+ String s = "ssl -h " + _host + " -p " + _port;
+ if(_timeout != -1)
+ {
+ s += " -t " + _timeout;
+ }
+ if(_compress)
+ {
+ s += " -z";
+ }
+ return s;
+ }
+
+ //
+ // Return the endpoint type
+ //
+ public short
+ type()
+ {
+ return TYPE;
+ }
+
+ //
+ // Return the timeout for the endpoint in milliseconds. 0 means
+ // non-blocking, -1 means no timeout.
+ //
+ public int
+ timeout()
+ {
+ return _timeout;
+ }
+
+ //
+ // Return a new endpoint with a different timeout value, provided
+ // that timeouts are supported by the endpoint. Otherwise the same
+ // endpoint is returned.
+ //
+ public IceInternal.EndpointI
+ timeout(int timeout)
+ {
+ if(timeout == _timeout)
+ {
+ return this;
+ }
+ else
+ {
+ return new EndpointI(_instance, _host, _port, timeout, _connectionId, _compress);
+ }
+ }
+
+ //
+ // Return a new endpoint with a different connection id.
+ //
+ public IceInternal.EndpointI
+ connectionId(String connectionId)
+ {
+ if(connectionId.equals(_connectionId))
+ {
+ return this;
+ }
+ else
+ {
+ return new EndpointI(_instance, _host, _port, _timeout, connectionId, _compress);
+ }
+ }
+
+ //
+ // Return true if the endpoints support bzip2 compress, or false
+ // otherwise.
+ //
+ public boolean
+ compress()
+ {
+ return _compress;
+ }
+
+ //
+ // Return a new endpoint with a different compression value,
+ // provided that compression is supported by the
+ // endpoint. Otherwise the same endpoint is returned.
+ //
+ public IceInternal.EndpointI
+ compress(boolean compress)
+ {
+ if(compress == _compress)
+ {
+ return this;
+ }
+ else
+ {
+ return new EndpointI(_instance, _host, _port, _timeout, _connectionId, compress);
+ }
+ }
+
+ //
+ // Return true if the endpoint is datagram-based.
+ //
+ public boolean
+ datagram()
+ {
+ return false;
+ }
+
+ //
+ // Return true if the endpoint is secure.
+ //
+ public boolean
+ secure()
+ {
+ return true;
+ }
+
+ //
+ // Return true if the endpoint type is unknown.
+ //
+ public boolean
+ unknown()
+ {
+ return false;
+ }
+
+ //
+ // Return a server side transceiver for this endpoint, or null if a
+ // transceiver can only be created by an acceptor. In case a
+ // transceiver is created, this operation also returns a new
+ // "effective" endpoint, which might differ from this endpoint,
+ // for example, if a dynamic port number is assigned.
+ //
+ public IceInternal.Transceiver
+ transceiver(IceInternal.EndpointIHolder endpoint)
+ {
+ endpoint.value = this;
+ return null;
+ }
+
+ //
+ // Return connectors for this endpoint, or empty list if no connector
+ // is available.
+ //
+ public java.util.ArrayList
+ connectors()
+ {
+ java.util.ArrayList connectors = new java.util.ArrayList();
+ java.util.ArrayList addresses = IceInternal.Network.getAddresses(_host, _port);
+ java.util.Iterator p = addresses.iterator();
+ while(p.hasNext())
+ {
+ connectors.add(new ConnectorI(_instance, (java.net.InetSocketAddress)p.next(), _timeout, _connectionId));
+ }
+ return connectors;
+ }
+
+ //
+ // Return an acceptor for this endpoint, or null if no acceptors
+ // is available. In case an acceptor is created, this operation
+ // also returns a new "effective" endpoint, which might differ
+ // from this endpoint, for example, if a dynamic port number is
+ // assigned.
+ //
+ public IceInternal.Acceptor
+ acceptor(IceInternal.EndpointIHolder endpoint, String adapterName)
+ {
+ AcceptorI p = new AcceptorI(_instance, adapterName, _host, _port);
+ endpoint.value = new EndpointI(_instance, _host, p.effectivePort(), _timeout, _connectionId, _compress);
+ return p;
+ }
+
+ //
+ // Expand endpoint out in to separate endpoints for each local
+ // host if listening on INADDR_ANY.
+ //
+ public java.util.ArrayList
+ expand()
+ {
+ java.util.ArrayList endps = new java.util.ArrayList();
+ if(_host.equals("0.0.0.0"))
+ {
+ java.util.ArrayList hosts = IceInternal.Network.getLocalHosts();
+ java.util.Iterator iter = hosts.iterator();
+ while(iter.hasNext())
+ {
+ String host = (String)iter.next();
+ if(hosts.size() == 1 || !host.equals("127.0.0.1"))
+ {
+ endps.add(new EndpointI(_instance, host, _port, _timeout, _connectionId, _compress));
+
+ }
+ }
+ }
+ else
+ {
+ endps.add(this);
+ }
+ return endps;
+ }
+
+ //
+ // Check whether the endpoint is equivalent to a specific Connector.
+ //
+ public boolean
+ equivalent(IceInternal.Connector connector)
+ {
+ ConnectorI sslConnector = null;
+ try
+ {
+ sslConnector = (ConnectorI)connector;
+ }
+ catch(ClassCastException ex)
+ {
+ return false;
+ }
+ return sslConnector.equivalent(_host, _port);
+ }
+
+ public int
+ hashCode()
+ {
+ return _hashCode;
+ }
+
+ //
+ // Compare endpoints for sorting purposes
+ //
+ public boolean
+ equals(java.lang.Object obj)
+ {
+ return compareTo(obj) == 0;
+ }
+
+ public int
+ compareTo(java.lang.Object obj) // From java.lang.Comparable
+ {
+ EndpointI p = null;
+
+ try
+ {
+ p = (EndpointI)obj;
+ }
+ catch(ClassCastException ex)
+ {
+ try
+ {
+ IceInternal.EndpointI e = (IceInternal.EndpointI)obj;
+ return type() < e.type() ? -1 : 1;
+ }
+ catch(ClassCastException ee)
+ {
+ assert(false);
+ }
+ }
+
+ if(this == p)
+ {
+ return 0;
+ }
+
+ if(_port < p._port)
+ {
+ return -1;
+ }
+ else if(p._port < _port)
+ {
+ return 1;
+ }
+
+ if(!_connectionId.equals(p._connectionId))
+ {
+ return _connectionId.compareTo(p._connectionId);
+ }
+
+ if(_timeout < p._timeout)
+ {
+ return -1;
+ }
+ else if(p._timeout < _timeout)
+ {
+ return 1;
+ }
+
+ if(!_compress && p._compress)
+ {
+ return -1;
+ }
+ else if(!p._compress && _compress)
+ {
+ return 1;
+ }
+
+ return _host.compareTo(p._host);
+ }
+
+ public boolean
+ requiresThreadPerConnection()
+ {
+ return false;
+ }
+
+ private void
+ calcHashValue()
+ {
+ try
+ {
+ java.net.InetSocketAddress addr = IceInternal.Network.getAddress(_host, _port);
+ _hashCode = addr.getAddress().getHostAddress().hashCode();
+ }
+ catch(Ice.DNSException ex)
+ {
+ _hashCode = _host.hashCode();
+ }
+ _hashCode = 5 * _hashCode + _port;
+ _hashCode = 5 * _hashCode + _timeout;
+ _hashCode = 5 * _hashCode + _connectionId.hashCode();
+ _hashCode = 5 * _hashCode + (_compress ? 1 : 0);
+ }
+
+ private Instance _instance;
+ private String _host;
+ private int _port;
+ private int _timeout;
+ private String _connectionId = "";
+ private boolean _compress;
+ private int _hashCode;
+}
diff --git a/java/src/IceSSL/Instance.java b/java/src/IceSSL/Instance.java
new file mode 100644
index 00000000000..0e6bb03a571
--- /dev/null
+++ b/java/src/IceSSL/Instance.java
@@ -0,0 +1,995 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+class Instance
+{
+ Instance(Ice.Communicator communicator)
+ {
+ _logger = communicator.getLogger();
+ _facade = Ice.Util.getProtocolPluginFacade(communicator);
+ _securityTraceLevel = communicator.getProperties().getPropertyAsIntWithDefault("IceSSL.Trace.Security", 0);
+ _securityTraceCategory = "Security";
+ _trustManager = new TrustManager(communicator);
+
+ //
+ // Register the endpoint factory. We have to do this now, rather than
+ // in initialize, because the communicator may need to interpret
+ // proxies before the plugin is fully initialized.
+ //
+ _facade.addEndpointFactory(new EndpointFactoryI(this));
+ }
+
+ void
+ initialize()
+ {
+ if(_initialized)
+ {
+ return;
+ }
+
+ final String prefix = "IceSSL.";
+ Ice.Properties properties = communicator().getProperties();
+
+ //
+ // Parse the cipher list.
+ //
+ String ciphers = properties.getProperty(prefix + "Ciphers");
+ if(ciphers.length() > 0)
+ {
+ parseCiphers(ciphers);
+ }
+
+ //
+ // Select protocols.
+ //
+ String protocols = properties.getProperty(prefix + "Protocols");
+ if(protocols.length() > 0)
+ {
+ java.util.ArrayList<String> l = new java.util.ArrayList<String>();
+ String[] arr = protocols.split("[ \t,]+");
+ for(int i = 0; i < arr.length; ++i)
+ {
+ String s = arr[i].toLowerCase();
+ if(s.equals("ssl3") || s.equals("sslv3"))
+ {
+ l.add("SSLv3");
+ }
+ else if(s.equals("tls") || s.equals("tls1") || s.equals("tlsv1"))
+ {
+ l.add("TLSv1");
+ }
+ else
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: unrecognized protocol `" + arr[i] + "'";
+ throw e;
+ }
+ }
+ _protocols = new String[l.size()];
+ l.toArray(_protocols);
+ }
+
+ //
+ // CheckCertName determines whether we compare the name in a peer's
+ // certificate against its hostname.
+ //
+ _checkCertName = properties.getPropertyAsIntWithDefault(prefix + "CheckCertName", 0) > 0;
+
+ //
+ // VerifyDepthMax establishes the maximum length of a peer's certificate
+ // chain, including the peer's certificate. A value of 0 means there is
+ // no maximum.
+ //
+ _verifyDepthMax = properties.getPropertyAsIntWithDefault(prefix + "VerifyDepthMax", 2);
+
+ //
+ // Check for a certificate verifier.
+ //
+ final String certVerifierClass = properties.getProperty(prefix + "CertVerifier");
+ if(certVerifierClass.length() > 0)
+ {
+ if(_verifier != null)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: certificate verifier already installed";
+ throw e;
+ }
+
+ Class cls = null;
+ try
+ {
+ cls = Class.forName(certVerifierClass);
+ }
+ catch(Throwable ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: unable to load certificate verifier class " + certVerifierClass;
+ e.initCause(ex);
+ throw e;
+ }
+
+ try
+ {
+ _verifier = (CertificateVerifier)cls.newInstance();
+ }
+ catch(Throwable ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: unable to instantiate certificate verifier class " + certVerifierClass;
+ e.initCause(ex);
+ throw e;
+ }
+ }
+
+ //
+ // Check for a password callback.
+ //
+ final String passwordCallbackClass = properties.getProperty(prefix + "PasswordCallback");
+ if(passwordCallbackClass.length() > 0)
+ {
+ if(_passwordCallback != null)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: password callback already installed";
+ throw e;
+ }
+
+ Class cls = null;
+ try
+ {
+ cls = Class.forName(passwordCallbackClass);
+ }
+ catch(Throwable ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: unable to load password callback class " + passwordCallbackClass;
+ e.initCause(ex);
+ throw e;
+ }
+
+ try
+ {
+ _passwordCallback = (PasswordCallback)cls.newInstance();
+ }
+ catch(Throwable ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: unable to instantiate password callback class " + passwordCallbackClass;
+ e.initCause(ex);
+ throw e;
+ }
+ }
+
+ //
+ // If the user doesn't supply an SSLContext, we need to create one based
+ // on property settings.
+ //
+ if(_context == null)
+ {
+ try
+ {
+ //
+ // Check for a default directory. We look in this directory for
+ // files mentioned in the configuration.
+ //
+ _defaultDir = properties.getProperty(prefix + "DefaultDir");
+
+ //
+ // We need a SecureRandom object.
+ //
+ // NOTE: The JDK recommends obtaining a SecureRandom object like this:
+ //
+ // java.security.SecureRandom rand = java.security.SecureRandom.getInstance("SHA1PRNG");
+ //
+ // However, there is a bug (6202721) which causes it to always use /dev/random,
+ // which can lead to long delays at program startup. The workaround is to use
+ // the default constructor.
+ //
+ java.security.SecureRandom rand = new java.security.SecureRandom();
+
+ //
+ // Check for seed data for the random number generator.
+ //
+ final String seedFiles = properties.getProperty(prefix + "Random");
+ if(seedFiles.length() > 0)
+ {
+ byte[] seed = null;
+ int start = 0;
+ final String[] arr = seedFiles.split(java.io.File.pathSeparator);
+ for(int i = 0; i < arr.length; ++i)
+ {
+ Ice.StringHolder seedFile = new Ice.StringHolder(arr[i]);
+ if(!checkPath(seedFile, false))
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: random seed file not found:\n" + arr[i];
+ throw e;
+ }
+ java.io.File f = new java.io.File(seedFile.value);
+ int num = (int)f.length();
+ if(seed == null)
+ {
+ seed = new byte[num];
+ }
+ else
+ {
+ byte[] tmp = new byte[seed.length + num];
+ System.arraycopy(seed, 0, tmp, 0, seed.length);
+ start = seed.length;
+ seed = tmp;
+ }
+ try
+ {
+ java.io.FileInputStream in = new java.io.FileInputStream(f);
+ in.read(seed, start, num);
+ in.close();
+ }
+ catch(java.io.IOException ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: error while reading random seed file:\n" + arr[i];
+ e.initCause(ex);
+ throw e;
+ }
+ }
+ rand.setSeed(seed);
+ }
+
+ //
+ // We call nextInt() in order to force the object to perform any time-consuming
+ // initialization tasks now.
+ //
+ rand.nextInt();
+
+ //
+ // The keystore holds private keys and associated certificates.
+ //
+ Ice.StringHolder keystorePath = new Ice.StringHolder(properties.getProperty(prefix + "Keystore"));
+
+ //
+ // The password for the keys.
+ //
+ String password = properties.getProperty(prefix + "Password");
+
+ //
+ // The password for the keystore.
+ //
+ String keystorePassword = properties.getProperty(prefix + "KeystorePassword");
+
+ //
+ // The default keystore type value is "JKS", but it can also be "PKCS12".
+ //
+ final String defaultType = java.security.KeyStore.getDefaultType();
+ final String keystoreType = properties.getPropertyWithDefault(prefix + "KeystoreType", defaultType);
+
+ //
+ // The alias of the key to use in authentication.
+ //
+ final String alias = properties.getProperty(prefix + "Alias");
+
+ //
+ // The truststore holds the certificates of trusted CAs.
+ //
+ Ice.StringHolder truststorePath = new Ice.StringHolder(properties.getProperty(prefix + "Truststore"));
+
+ //
+ // The password for the truststore.
+ //
+ String truststorePassword = properties.getProperty(prefix + "TruststorePassword");
+
+ //
+ // The truststore type defaults to "JKS", but it can also be "PKCS12".
+ //
+ final String truststoreType =
+ properties.getPropertyWithDefault(prefix + "TruststoreType",
+ java.security.KeyStore.getDefaultType());
+
+ //
+ // Collect the key managers.
+ //
+ javax.net.ssl.KeyManager[] keyManagers = null;
+ if(keystorePath.value.length() > 0)
+ {
+ if(!checkPath(keystorePath, false))
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: keystore file not found:\n" + keystorePath.value;
+ throw e;
+ }
+ java.security.KeyStore keys = java.security.KeyStore.getInstance(keystoreType);
+ try
+ {
+ char[] passwordChars = null;
+ if(keystorePassword.length() > 0)
+ {
+ passwordChars = keystorePassword.toCharArray();
+ }
+ else if(_passwordCallback != null)
+ {
+ passwordChars = _passwordCallback.getKeystorePassword();
+ }
+
+ java.io.BufferedInputStream bis =
+ new java.io.BufferedInputStream(new java.io.FileInputStream(keystorePath.value));
+ keys.load(bis, passwordChars);
+
+ if(passwordChars != null)
+ {
+ java.util.Arrays.fill(passwordChars, '\0');
+ }
+ keystorePassword = null;
+ }
+ catch(java.io.IOException ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: unable to load keystore:\n" + keystorePath.value;
+ e.initCause(ex);
+ throw e;
+ }
+
+ String algorithm = javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm();
+ javax.net.ssl.KeyManagerFactory kmf = javax.net.ssl.KeyManagerFactory.getInstance(algorithm);
+ char[] passwordChars = new char[0]; // This password cannot be null.
+ if(password.length() > 0)
+ {
+ passwordChars = password.toCharArray();
+ }
+ else if(_passwordCallback != null)
+ {
+ passwordChars = _passwordCallback.getPassword(alias);
+ }
+ kmf.init(keys, passwordChars);
+ if(passwordChars.length > 0)
+ {
+ java.util.Arrays.fill(passwordChars, '\0');
+ }
+ password = null;
+ keyManagers = kmf.getKeyManagers();
+
+ //
+ // If the user selected a specific alias, we need to wrap the key managers
+ // in order to return the desired alias.
+ //
+ if(alias.length() > 0)
+ {
+ if(!keys.isKeyEntry(alias))
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: keystore does not contain an entry with alias `" + alias + "'";
+ throw e;
+ }
+
+ for(int i = 0; i < keyManagers.length; ++i)
+ {
+ keyManagers[i] = new X509KeyManagerI((javax.net.ssl.X509KeyManager)keyManagers[i], alias);
+ }
+ }
+ }
+
+ //
+ // Collect the trust managers.
+ //
+ javax.net.ssl.TrustManager[] trustManagers = null;
+ if(truststorePath.value.length() > 0)
+ {
+ if(!checkPath(truststorePath, false))
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: truststore file not found:\n" + truststorePath.value;
+ throw e;
+ }
+ java.security.KeyStore ts = java.security.KeyStore.getInstance(truststoreType);
+ try
+ {
+ char[] passwordChars = null;
+ if(truststorePassword.length() > 0)
+ {
+ passwordChars = truststorePassword.toCharArray();
+ }
+ else if(_passwordCallback != null)
+ {
+ passwordChars = _passwordCallback.getTruststorePassword();
+ }
+
+ java.io.BufferedInputStream bis =
+ new java.io.BufferedInputStream(new java.io.FileInputStream(truststorePath.value));
+ ts.load(bis, passwordChars);
+
+ if(passwordChars != null)
+ {
+ java.util.Arrays.fill(passwordChars, '\0');
+ }
+ truststorePassword = null;
+ }
+ catch(java.io.IOException ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: unable to load truststore:\n" + truststorePath.value;
+ e.initCause(ex);
+ throw e;
+ }
+
+ String algorithm = javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm();
+ javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance(algorithm);
+ tmf.init(ts);
+ trustManagers = tmf.getTrustManagers();
+ }
+
+ //
+ // The default TrustManager implementation in IBM's JDK does not accept
+ // anonymous ciphers, so we have to install our own.
+ //
+ if(trustManagers == null)
+ {
+ trustManagers = new javax.net.ssl.TrustManager[1];
+ trustManagers[0] = new X509TrustManagerI(null);
+ }
+ else
+ {
+ for(int i = 0; i < trustManagers.length; ++i)
+ {
+ trustManagers[i] = new X509TrustManagerI((javax.net.ssl.X509TrustManager)trustManagers[i]);
+ }
+ }
+
+ //
+ // Initialize the SSL context.
+ //
+ _context = javax.net.ssl.SSLContext.getInstance("SSL");
+ _context.init(keyManagers, trustManagers, rand);
+ }
+ catch(java.security.GeneralSecurityException ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: unable to initialize context";
+ e.initCause(ex);
+ throw e;
+ }
+ }
+
+ _initialized = true;
+ }
+
+ void
+ context(javax.net.ssl.SSLContext context)
+ {
+ if(_initialized)
+ {
+ Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
+ ex.reason = "IceSSL: plugin is already initialized";
+ throw ex;
+ }
+
+ _context = context;
+ }
+
+ javax.net.ssl.SSLContext
+ context()
+ {
+ return _context;
+ }
+
+ void
+ setCertificateVerifier(CertificateVerifier verifier)
+ {
+ _verifier = verifier;
+ }
+
+ CertificateVerifier
+ getCertificateVerifier()
+ {
+ return _verifier;
+ }
+
+ void
+ setPasswordCallback(PasswordCallback callback)
+ {
+ _passwordCallback = callback;
+ }
+
+ PasswordCallback
+ getPasswordCallback()
+ {
+ return _passwordCallback;
+ }
+
+ Ice.Communicator
+ communicator()
+ {
+ return _facade.getCommunicator();
+ }
+
+ String
+ defaultHost()
+ {
+ return _facade.getDefaultHost();
+ }
+
+ int
+ networkTraceLevel()
+ {
+ return _facade.getNetworkTraceLevel();
+ }
+
+ String
+ networkTraceCategory()
+ {
+ return _facade.getNetworkTraceCategory();
+ }
+
+ int
+ securityTraceLevel()
+ {
+ return _securityTraceLevel;
+ }
+
+ String
+ securityTraceCategory()
+ {
+ return _securityTraceCategory;
+ }
+
+ boolean
+ initialized()
+ {
+ return _initialized;
+ }
+
+ javax.net.ssl.SSLEngine
+ createSSLEngine(boolean incoming)
+ {
+ javax.net.ssl.SSLEngine engine = _context.createSSLEngine();
+ engine.setUseClientMode(!incoming);
+
+ String[] cipherSuites = filterCiphers(engine.getSupportedCipherSuites(), engine.getEnabledCipherSuites());
+ try
+ {
+ engine.setEnabledCipherSuites(cipherSuites);
+ }
+ catch(IllegalArgumentException ex)
+ {
+ Ice.SecurityException e = new Ice.SecurityException();
+ e.reason = "IceSSL: invalid ciphersuite";
+ e.initCause(ex);
+ throw e;
+ }
+
+ if(_securityTraceLevel >= 1)
+ {
+ StringBuffer s = new StringBuffer();
+ s.append("enabling SSL ciphersuites:");
+ for(int i = 0; i < cipherSuites.length; ++i)
+ {
+ s.append("\n " + cipherSuites[i]);
+ }
+ _logger.trace(_securityTraceCategory, s.toString());
+ }
+
+ if(_protocols != null)
+ {
+ try
+ {
+ engine.setEnabledProtocols(_protocols);
+ }
+ catch(IllegalArgumentException ex)
+ {
+ Ice.SecurityException e = new Ice.SecurityException();
+ e.reason = "IceSSL: invalid protocol";
+ e.initCause(ex);
+ throw e;
+ }
+ }
+
+ if(incoming)
+ {
+ int verifyPeer = communicator().getProperties().getPropertyAsIntWithDefault("IceSSL.VerifyPeer", 2);
+ if(verifyPeer == 0)
+ {
+ engine.setWantClientAuth(false);
+ engine.setNeedClientAuth(false);
+ }
+ else if(verifyPeer == 1)
+ {
+ engine.setWantClientAuth(true);
+ }
+ else
+ {
+ engine.setNeedClientAuth(true);
+ }
+ }
+
+ try
+ {
+ engine.beginHandshake();
+ }
+ catch(javax.net.ssl.SSLException ex)
+ {
+ Ice.SecurityException e = new Ice.SecurityException();
+ e.reason = "IceSSL: handshake error";
+ e.initCause(ex);
+ throw e;
+ }
+
+ return engine;
+ }
+
+ String[]
+ filterCiphers(String[] supportedCiphers, String[] defaultCiphers)
+ {
+ java.util.LinkedList<String> result = new java.util.LinkedList<String>();
+ if(_allCiphers)
+ {
+ for(int i = 0; i < supportedCiphers.length; ++i)
+ {
+ result.add(supportedCiphers[i]);
+ }
+ }
+ else if(!_noCiphers)
+ {
+ for(int i = 0; i < defaultCiphers.length; ++i)
+ {
+ result.add(defaultCiphers[i]);
+ }
+ }
+
+ if(_ciphers != null)
+ {
+ for(int i = 0; i < _ciphers.length; ++i)
+ {
+ CipherExpression ce = (CipherExpression)_ciphers[i];
+ if(ce.not)
+ {
+ java.util.Iterator e = result.iterator();
+ while(e.hasNext())
+ {
+ String cipher = (String)e.next();
+ if(ce.cipher != null)
+ {
+ if(ce.cipher.equals(cipher))
+ {
+ e.remove();
+ }
+ }
+ else
+ {
+ assert(ce.re != null);
+ java.util.regex.Matcher m = ce.re.matcher(cipher);
+ if(m.find())
+ {
+ e.remove();
+ }
+ }
+ }
+ }
+ else
+ {
+ if(ce.cipher != null)
+ {
+ result.add(0, ce.cipher);
+ }
+ else
+ {
+ assert(ce.re != null);
+ for(int j = 0; j < supportedCiphers.length; ++j)
+ {
+ java.util.regex.Matcher m = ce.re.matcher(supportedCiphers[j]);
+ if(m.find())
+ {
+ result.add(0, supportedCiphers[j]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ String[] arr = new String[result.size()];
+ result.toArray(arr);
+ return arr;
+ }
+
+ String[]
+ protocols()
+ {
+ return _protocols;
+ }
+
+ void
+ traceConnection(java.nio.channels.SocketChannel fd, javax.net.ssl.SSLEngine engine, boolean incoming)
+ {
+ javax.net.ssl.SSLSession session = engine.getSession();
+ String msg = "SSL summary for " + (incoming ? "incoming" : "outgoing") + " connection\n" +
+ "cipher = " + session.getCipherSuite() + "\n" +
+ "protocol = " + session.getProtocol() + "\n" +
+ IceInternal.Network.fdToString(fd);
+ _logger.trace(_securityTraceCategory, msg);
+ }
+
+ void
+ verifyPeer(ConnectionInfo info, java.nio.channels.SelectableChannel fd, String address, boolean incoming)
+ {
+ if(_verifyDepthMax > 0 && info.certs != null && info.certs.length > _verifyDepthMax)
+ {
+ String msg = (incoming ? "incoming" : "outgoing") + " connection rejected:\n" +
+ "length of peer's certificate chain (" + info.certs.length + ") exceeds maximum of " +
+ _verifyDepthMax + "\n" +
+ IceInternal.Network.fdToString(fd);
+ if(_securityTraceLevel >= 1)
+ {
+ _logger.trace(_securityTraceCategory, msg);
+ }
+ Ice.SecurityException ex = new Ice.SecurityException();
+ ex.reason = msg;
+ throw ex;
+ }
+
+ //
+ // Extract the IP addresses and the DNS names from the subject
+ // alternative names.
+ //
+ if(info.certs != null)
+ {
+ try
+ {
+ java.util.Collection subjectAltNames =
+ ((java.security.cert.X509Certificate)info.certs[0]).getSubjectAlternativeNames();
+ java.util.ArrayList<String> ipAddresses = new java.util.ArrayList<String>();
+ java.util.ArrayList<String> dnsNames = new java.util.ArrayList<String>();
+ if(subjectAltNames != null)
+ {
+ java.util.Iterator i = subjectAltNames.iterator();
+ while(i.hasNext())
+ {
+ java.util.List l = (java.util.List)i.next();
+ assert(!l.isEmpty());
+ Integer n = (Integer)l.get(0);
+ if(n.intValue() == 7)
+ {
+ ipAddresses.add((String)l.get(1));
+ }
+ else if(n.intValue() == 2)
+ {
+ dnsNames.add(((String)l.get(1)).toLowerCase());
+ }
+ }
+ }
+
+ //
+ // Compare the peer's address against the dnsName and ipAddress values.
+ // This is only relevant for an outgoing connection.
+ //
+ if(address.length() > 0)
+ {
+ boolean certNameOK = ipAddresses.contains(address);
+ if(!certNameOK)
+ {
+ certNameOK = dnsNames.contains(address.toLowerCase());
+ }
+
+ //
+ // Log a message if the name comparison fails. If CheckCertName is defined,
+ // we also raise an exception to abort the connection. Don't log a message if
+ // CheckCertName is not defined and a verifier is present.
+ //
+ if(!certNameOK && (_checkCertName || (_securityTraceLevel >= 1 && _verifier == null)))
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("IceSSL: ");
+ if(!_checkCertName)
+ {
+ sb.append("ignoring ");
+ }
+ sb.append("certificate validation failure:\npeer certificate does not contain `" +
+ address + "' in its subjectAltName extension");
+ if(!dnsNames.isEmpty())
+ {
+ sb.append("\nDNS names found in certificate: ");
+ for(int j = 0; j < dnsNames.size(); ++j)
+ {
+ if(j > 0)
+ {
+ sb.append(", ");
+ }
+ sb.append(dnsNames.get(j).toString());
+ }
+ }
+ if(!ipAddresses.isEmpty())
+ {
+ sb.append("\nIP addresses found in certificate: ");
+ for(int j = 0; j < ipAddresses.size(); ++j)
+ {
+ if(j > 0)
+ {
+ sb.append(", ");
+ }
+ sb.append(ipAddresses.get(j).toString());
+ }
+ }
+ if(_securityTraceLevel >= 1)
+ {
+ _logger.trace(_securityTraceCategory, sb.toString());
+ }
+ if(_checkCertName)
+ {
+ Ice.SecurityException ex = new Ice.SecurityException();
+ ex.reason = sb.toString();
+ throw ex;
+ }
+ }
+ }
+ }
+ catch(java.security.cert.CertificateParsingException ex)
+ {
+ assert(false);
+ }
+ }
+
+ if(!_trustManager.verify(info))
+ {
+ String msg = (incoming ? "incoming" : "outgoing") + " connection rejected by trust manager\n" +
+ IceInternal.Network.fdToString(fd);
+ if(_securityTraceLevel >= 1)
+ {
+ _logger.trace(_securityTraceCategory, msg);
+ }
+ Ice.SecurityException ex = new Ice.SecurityException();
+ ex.reason = msg;
+ throw ex;
+ }
+
+ if(_verifier != null && !_verifier.verify(info))
+ {
+ String msg = (incoming ? "incoming" : "outgoing") + " connection rejected by certificate verifier\n" +
+ IceInternal.Network.fdToString(fd);
+
+ if(_securityTraceLevel > 0)
+ {
+ _logger.trace(_securityTraceCategory, msg);
+ }
+
+ Ice.SecurityException ex = new Ice.SecurityException();
+ ex.reason = msg;
+ throw ex;
+ }
+ }
+
+ private void
+ parseCiphers(String ciphers)
+ {
+ java.util.ArrayList<CipherExpression> cipherList = new java.util.ArrayList<CipherExpression>();
+ String[] expr = ciphers.split("[ \t]+");
+ for(int i = 0; i < expr.length; ++i)
+ {
+ if(expr[i].equals("ALL"))
+ {
+ if(i != 0)
+ {
+ Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
+ ex.reason = "IceSSL: `ALL' must be first in cipher list `" + ciphers + "'";
+ throw ex;
+ }
+ _allCiphers = true;
+ }
+ else if(expr[i].equals("NONE"))
+ {
+ if(i != 0)
+ {
+ Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
+ ex.reason = "IceSSL: `NONE' must be first in cipher list `" + ciphers + "'";
+ throw ex;
+ }
+ _noCiphers = true;
+ }
+ else
+ {
+ CipherExpression ce = new CipherExpression();
+ String exp = expr[i];
+ if(exp.charAt(0) == '!')
+ {
+ ce.not = true;
+ if(exp.length() > 1)
+ {
+ exp = exp.substring(1);
+ }
+ else
+ {
+ Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
+ ex.reason = "IceSSL: invalid cipher expression `" + exp + "'";
+ throw ex;
+ }
+ }
+
+ if(exp.charAt(0) == '(')
+ {
+ if(!exp.endsWith(")"))
+ {
+ Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
+ ex.reason = "IceSSL: invalid cipher expression `" + exp + "'";
+ throw ex;
+ }
+
+ try
+ {
+ ce.re = java.util.regex.Pattern.compile(exp.substring(1, exp.length() - 2));
+ }
+ catch(java.util.regex.PatternSyntaxException ex)
+ {
+ Ice.PluginInitializationException e = new Ice.PluginInitializationException();
+ e.reason = "IceSSL: invalid cipher expression `" + exp + "'";
+ e.initCause(ex);
+ throw e;
+ }
+ }
+ else
+ {
+ ce.cipher = exp;
+ }
+
+ cipherList.add(ce);
+ }
+ }
+ _ciphers = new CipherExpression[cipherList.size()];
+ cipherList.toArray(_ciphers);
+ }
+
+ private boolean
+ checkPath(Ice.StringHolder path, boolean dir)
+ {
+ //
+ // Check if file exists. If not, try prepending the default
+ // directory and check again. If the file is found, the
+ // string argument is modified and true is returned. Otherwise
+ // false is returned.
+ //
+ java.io.File f = new java.io.File(path.value);
+ if(f.exists())
+ {
+ return dir ? f.isDirectory() : f.isFile();
+ }
+
+ if(_defaultDir.length() > 0)
+ {
+ String s = _defaultDir + java.io.File.separator + path.value;
+ f = new java.io.File(s);
+ if(f.exists() && ((!dir && f.isFile()) || (dir && f.isDirectory())))
+ {
+ path.value = s;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static class CipherExpression
+ {
+ boolean not;
+ String cipher;
+ java.util.regex.Pattern re;
+ }
+
+ private Ice.Logger _logger;
+ private IceInternal.ProtocolPluginFacade _facade;
+ private int _securityTraceLevel;
+ private String _securityTraceCategory;
+ private boolean _initialized;
+ private javax.net.ssl.SSLContext _context;
+ private String _defaultDir;
+ private CipherExpression[] _ciphers;
+ private boolean _allCiphers;
+ private boolean _noCiphers;
+ private String[] _protocols;
+ private boolean _checkCertName;
+ private int _verifyDepthMax;
+ private CertificateVerifier _verifier;
+ private PasswordCallback _passwordCallback;
+ private TrustManager _trustManager;
+}
diff --git a/java/src/IceSSL/PasswordCallback.java b/java/src/IceSSL/PasswordCallback.java
new file mode 100644
index 00000000000..55aa3144ba5
--- /dev/null
+++ b/java/src/IceSSL/PasswordCallback.java
@@ -0,0 +1,36 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+//
+// A password callback is an alternate way of supplying the plugin with
+// passwords that avoids using plaintext configuration properties.
+//
+public interface PasswordCallback
+{
+ //
+ // Obtain the password for the key. If an alias was selected using
+ // the IceSSL.Alias property, the value of the property is provided.
+ // The return value must not be null.
+ //
+ char[] getPassword(String alias);
+
+ //
+ // Obtain the password for validating the truststore. Return null
+ // to skip truststore validation.
+ //
+ char[] getTruststorePassword();
+
+ //
+ // Obtain the password for validating the keystore. Return null
+ // to skip keystore validation.
+ //
+ char[] getKeystorePassword();
+}
diff --git a/java/src/IceSSL/Plugin.java b/java/src/IceSSL/Plugin.java
new file mode 100644
index 00000000000..15aeb7692cc
--- /dev/null
+++ b/java/src/IceSSL/Plugin.java
@@ -0,0 +1,54 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+public interface Plugin extends Ice.Plugin
+{
+ //
+ // Establish the SSL context. This must be done before the
+ // plugin is initialized, therefore the application must define
+ // the property Ice.InitPlugins=0, set the context, and finally
+ // invoke initializePlugins on the PluginManager.
+ //
+ // When the application supplies its own SSL context, the
+ // plugin skips its normal property-based configuration.
+ //
+ void setContext(javax.net.ssl.SSLContext context);
+
+ //
+ // Obtain the SSL context. Use caution when modifying this value.
+ // Changes made to this value have no effect on existing connections.
+ //
+ javax.net.ssl.SSLContext getContext();
+
+ //
+ // Establish the certificate verifier object. This should be
+ // done before any connections are established.
+ //
+ void setCertificateVerifier(CertificateVerifier verifier);
+
+ //
+ // Obtain the certificate verifier object. Returns null if no
+ // verifier is set.
+ //
+ CertificateVerifier getCertificateVerifier();
+
+ //
+ // Establish the password callback object. This should be
+ // done before the plugin is initialized.
+ //
+ void setPasswordCallback(PasswordCallback callback);
+
+ //
+ // Obtain the password callback object. Returns null if no
+ // callback is set.
+ //
+ PasswordCallback getPasswordCallback();
+}
diff --git a/java/src/IceSSL/PluginFactory.java b/java/src/IceSSL/PluginFactory.java
new file mode 100644
index 00000000000..70064d8a1b0
--- /dev/null
+++ b/java/src/IceSSL/PluginFactory.java
@@ -0,0 +1,19 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+public class PluginFactory implements Ice.PluginFactory
+{
+ public Ice.Plugin
+ create(Ice.Communicator communicator, String name, String[] args)
+ {
+ return new PluginI(communicator);
+ }
+}
diff --git a/java/src/IceSSL/PluginI.java b/java/src/IceSSL/PluginI.java
new file mode 100644
index 00000000000..2021b7ae9f2
--- /dev/null
+++ b/java/src/IceSSL/PluginI.java
@@ -0,0 +1,68 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+class PluginI implements Plugin
+{
+ public
+ PluginI(Ice.Communicator communicator)
+ {
+ _instance = new Instance(communicator);
+ }
+
+ public void
+ initialize()
+ {
+ _instance.initialize();
+ }
+
+ public void
+ destroy()
+ {
+ }
+
+ public void
+ setContext(javax.net.ssl.SSLContext context)
+ {
+ _instance.context(context);
+ }
+
+ public javax.net.ssl.SSLContext
+ getContext()
+ {
+ return _instance.context();
+ }
+
+ public void
+ setCertificateVerifier(CertificateVerifier verifier)
+ {
+ _instance.setCertificateVerifier(verifier);
+ }
+
+ public CertificateVerifier
+ getCertificateVerifier()
+ {
+ return _instance.getCertificateVerifier();
+ }
+
+ public void
+ setPasswordCallback(PasswordCallback callback)
+ {
+ _instance.setPasswordCallback(callback);
+ }
+
+ public PasswordCallback
+ getPasswordCallback()
+ {
+ return _instance.getPasswordCallback();
+ }
+
+ private Instance _instance;
+}
diff --git a/java/src/IceSSL/RFC2253.java b/java/src/IceSSL/RFC2253.java
new file mode 100644
index 00000000000..601f8622aa4
--- /dev/null
+++ b/java/src/IceSSL/RFC2253.java
@@ -0,0 +1,417 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+//
+// See RFC 2253 and RFC 1779.
+//
+class RFC2253
+{
+ static class ParseException extends Ice.LocalException
+ {
+ public ParseException()
+ {
+ }
+
+ public ParseException(String reason)
+ {
+ this.reason = reason;
+ }
+
+ public String
+ ice_name()
+ {
+ return "RFC2253::ParseException";
+ }
+
+ public String reason;
+ }
+
+ static class RDNPair
+ {
+ String key;
+ String value;
+ }
+
+ static private class ParseState
+ {
+ String data;
+ int pos;
+ }
+
+ public static java.util.List
+ parse(String data)
+ throws ParseException
+ {
+ java.util.List results = new java.util.LinkedList();
+ java.util.List current = new java.util.LinkedList();
+ ParseState state = new ParseState();
+ state.data = data;
+ state.pos = 0;
+ while(state.pos < state.data.length())
+ {
+ current.add(parseNameComponent(state));
+ eatWhite(state);
+ if(state.pos < state.data.length() && state.data.charAt(state.pos) == ',')
+ {
+ ++state.pos;
+ }
+ else if(state.pos < state.data.length() && state.data.charAt(state.pos) == ';')
+ {
+ ++state.pos;
+ results.add(current);
+ current = new java.util.LinkedList();
+ }
+ else if(state.pos < state.data.length())
+ {
+ throw new ParseException("expected ',' or ';' at `" + state.data.substring(state.pos) + "'");
+ }
+ }
+ if(!current.isEmpty())
+ {
+ results.add(current);
+ }
+
+ return results;
+ }
+
+ public static java.util.List
+ parseStrict(String data)
+ throws ParseException
+ {
+ java.util.List results = new java.util.LinkedList();
+ ParseState state = new ParseState();
+ state.data = data;
+ state.pos = 0;
+ while(state.pos < state.data.length())
+ {
+ results.add(parseNameComponent(state));
+ eatWhite(state);
+ if(state.pos < state.data.length() &&
+ (state.data.charAt(state.pos) == ',' || state.data.charAt(state.pos) == ';'))
+ {
+ ++state.pos;
+ }
+ else if(state.pos < state.data.length())
+ {
+ throw new ParseException("expected ',' or ';' at `" + state.data.substring(state.pos) + "'");
+ }
+ }
+ return results;
+ }
+
+ private static RDNPair
+ parseNameComponent(ParseState state)
+ throws ParseException
+ {
+ RDNPair result = parseAttributeTypeAndValue(state);
+ while(state.pos < state.data.length())
+ {
+ eatWhite(state);
+ if(state.pos < state.data.length() && state.data.charAt(state.pos) == '+')
+ {
+ ++state.pos;
+ }
+ else
+ {
+ break;
+ }
+ RDNPair p = parseAttributeTypeAndValue(state);
+ result.value += "+";
+ result.value += p.key;
+ result.value += '=';
+ result.value += p.value;
+ }
+ return result;
+ }
+
+ private static RDNPair
+ parseAttributeTypeAndValue(ParseState state)
+ throws ParseException
+ {
+ RDNPair p = new RDNPair();
+ p.key = parseAttributeType(state);
+ eatWhite(state);
+ if(state.pos >= state.data.length())
+ {
+ throw new ParseException("invalid attribute type/value pair (unexpected end of state.data)");
+ }
+ if(state.data.charAt(state.pos) != '=')
+ {
+ throw new ParseException("invalid attribute type/value pair (missing =)");
+ }
+ ++state.pos;
+ p.value = parseAttributeValue(state);
+ return p;
+ }
+
+ private static String
+ parseAttributeType(ParseState state)
+ throws ParseException
+ {
+ eatWhite(state);
+ if(state.pos >= state.data.length())
+ {
+ throw new ParseException("invalid attribute type (expected end of state.data)");
+ }
+
+ String result = new String();
+
+ //
+ // RFC 1779.
+ // <key> ::= 1*( <keychar> ) | "OID." <oid> | "oid." <oid>
+ // <oid> ::= <digitString> | <digitstring> "." <oid>
+ // RFC 2253:
+ // attributeType = (ALPHA 1*keychar) | oid
+ // keychar = ALPHA | DIGIT | "-"
+ // oid = 1*DIGIT *("." 1*DIGIT)
+ //
+ // In section 4 of RFC 2253 the document says:
+ // Implementations MUST allow an oid in the attribute type to be
+ // prefixed by one of the character Strings "oid." or "OID.".
+ //
+ // Here we must also check for "oid." and "OID." before parsing
+ // according to the ALPHA KEYCHAR* rule.
+ //
+ // First the OID case.
+ //
+ if(Character.isDigit(state.data.charAt(state.pos)) ||
+ (state.data.length() - state.pos >= 4 && (state.data.substring(state.pos, state.pos + 4) == "oid." ||
+ state.data.substring(state.pos, state.pos + 4) == "OID.")))
+ {
+ if(!Character.isDigit(state.data.charAt(state.pos)))
+ {
+ result += state.data.substring(state.pos, state.pos + 4);
+ state.pos += 4;
+ }
+
+ while(true)
+ {
+ // 1*DIGIT
+ while(state.pos < state.data.length() && Character.isDigit(state.data.charAt(state.pos)))
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ }
+ // "." 1*DIGIT
+ if(state.pos < state.data.length() && state.data.charAt(state.pos) == '.')
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ // 1*DIGIT must follow "."
+ if(state.pos < state.data.length() && !Character.isDigit(state.data.charAt(state.pos)))
+ {
+ throw new ParseException("invalid attribute type (expected end of state.data)");
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ else if(Character.isUpperCase(state.data.charAt(state.pos)) ||
+ Character.isLowerCase(state.data.charAt(state.pos)))
+ {
+ //
+ // The grammar is wrong in this case. It should be ALPHA
+ // KEYCHAR* otherwise it will not accept "O" as a valid
+ // attribute type.
+ //
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ // 1* KEYCHAR
+ while(state.pos < state.data.length() &&
+ (Character.isDigit(state.data.charAt(state.pos)) ||
+ Character.isUpperCase(state.data.charAt(state.pos)) ||
+ Character.isLowerCase(state.data.charAt(state.pos)) ||
+ state.data.charAt(state.pos) == '-'))
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ }
+ }
+ else
+ {
+ throw new ParseException("invalid attribute type");
+ }
+ return result;
+ }
+
+ private static String
+ parseAttributeValue(ParseState state)
+ throws ParseException
+ {
+ eatWhite(state);
+ String result = new String();
+ if(state.pos >= state.data.length())
+ {
+ return result;
+ }
+
+ //
+ // RFC 2253
+ // # hexString
+ //
+ if(state.data.charAt(state.pos) == '#')
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ while(true)
+ {
+ String h = parseHexPair(state, true);
+ if(h.length() == 0)
+ {
+ break;
+ }
+ result += h;
+ }
+ }
+ //
+ // RFC 2253
+ // QUOTATION *( quotechar | pair ) QUOTATION ; only from v2
+ // quotechar = <any character except "\" or QUOTATION >
+ //
+ else if(state.data.charAt(state.pos) == '"')
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ while(true)
+ {
+ if(state.pos >= state.data.length())
+ {
+ throw new ParseException("invalid attribute value (unexpected end of state.data)");
+ }
+ // final terminating "
+ if(state.data.charAt(state.pos) == '"')
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ break;
+ }
+ // any character except '\'
+ else if(state.data.charAt(state.pos) != '\\')
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ }
+ // pair '\'
+ else
+ {
+ result += parsePair(state);
+ }
+ }
+ }
+ //
+ // RFC 2253
+ // * (Stringchar | pair)
+ // Stringchar = <any character except one of special, "\" or QUOTATION >
+ //
+ else
+ {
+ while(state.pos < state.data.length())
+ {
+ if(state.data.charAt(state.pos) == '\\')
+ {
+ result += parsePair(state);
+ }
+ else if(special.indexOf(state.data.charAt(state.pos)) == -1 && state.data.charAt(state.pos) != '"')
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ //
+ // RFC2253:
+ // pair = "\" ( special | "\" | QUOTATION | hexpair )
+ //
+ private static String
+ parsePair(ParseState state)
+ throws ParseException
+ {
+ String result = new String();
+
+ assert(state.data.charAt(state.pos) == '\\');
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+
+ if(state.pos >= state.data.length())
+ {
+ throw new ParseException("invalid escape format (unexpected end of state.data)");
+ }
+
+ if(special.indexOf(state.data.charAt(state.pos)) != -1 || state.data.charAt(state.pos) != '\\' ||
+ state.data.charAt(state.pos) != '"')
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ return result;
+ }
+ return parseHexPair(state, false);
+ }
+
+ //
+ // RFC 2253
+ // hexpair = hexchar hexchar
+ //
+ private static String
+ parseHexPair(ParseState state, boolean allowEmpty)
+ throws ParseException
+ {
+ String result = new String();
+ if(state.pos < state.data.length() && hexvalid.indexOf(state.data.charAt(state.pos)) != -1)
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ }
+ if(state.pos < state.data.length() && hexvalid.indexOf(state.data.charAt(state.pos)) != -1)
+ {
+ result += state.data.charAt(state.pos);
+ ++state.pos;
+ }
+ if(result.length() != 2)
+ {
+ if(allowEmpty && result.length() == 0)
+ {
+ return result;
+ }
+ throw new ParseException("invalid hex format");
+ }
+ return result;
+ }
+
+ //
+ // RFC 2253:
+ //
+ // Implementations MUST allow for space (' ' ASCII 32) characters to be
+ // present between name-component and ',', between attributeTypeAndValue
+ // and '+', between attributeType and '=', and between '=' and
+ // attributeValue. These space characters are ignored when parsing.
+ //
+ private static void
+ eatWhite(ParseState state)
+ {
+ while(state.pos < state.data.length() && state.data.charAt(state.pos) == ' ')
+ {
+ ++state.pos;
+ }
+ }
+
+ private final static String special = ",=+<>#;";
+ private final static String hexvalid = "0123456789abcdefABCDEF";
+}
diff --git a/java/src/IceSSL/TransceiverI.java b/java/src/IceSSL/TransceiverI.java
new file mode 100644
index 00000000000..b43a340c703
--- /dev/null
+++ b/java/src/IceSSL/TransceiverI.java
@@ -0,0 +1,854 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+import java.nio.*;
+import javax.net.ssl.*;
+import javax.net.ssl.SSLEngineResult.*;
+
+final class TransceiverI implements IceInternal.Transceiver
+{
+ public java.nio.channels.SelectableChannel
+ fd()
+ {
+ assert(_fd != null);
+ return _fd;
+ }
+
+ public void
+ close()
+ {
+ if(_instance.networkTraceLevel() >= 1)
+ {
+ String s = "closing ssl connection\n" + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ assert(_fd != null);
+
+ if(_readSelector != null)
+ {
+ try
+ {
+ _readSelector.close();
+ }
+ catch(java.io.IOException ex)
+ {
+ // Ignore.
+ }
+ _readSelector = null;
+ }
+
+ if(_writeSelector != null)
+ {
+ try
+ {
+ _writeSelector.close();
+ }
+ catch(java.io.IOException ex)
+ {
+ // Ignore.
+ }
+ _writeSelector = null;
+ }
+
+ try
+ {
+ _engine.closeInbound();
+ }
+ catch(SSLException ex)
+ {
+ //
+ // SSLEngine always raises an exception with this message:
+ //
+ // Inbound closed before receiving peer's close_notify: possible truncation attack?
+ //
+ // We would probably need to wait for a response in shutdown() to avoid this.
+ // For now, we'll ignore this exception.
+ //
+ //_logger.error("IceSSL: error during close\n" + ex.getMessage());
+ }
+
+ try
+ {
+ _fd.close();
+ }
+ catch(java.io.IOException ex)
+ {
+ Ice.SocketException se = new Ice.SocketException();
+ se.initCause(ex);
+ throw se;
+ }
+ finally
+ {
+ _fd = null;
+ }
+ }
+
+ //
+ // All methods that can write to the socket are synchronized.
+ //
+ public synchronized void
+ shutdownWrite()
+ {
+ if(_instance.networkTraceLevel() >= 2)
+ {
+ String s = "shutting down ssl connection for writing\n" + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ shutdown();
+
+ assert(_fd != null);
+ java.net.Socket socket = _fd.socket();
+ try
+ {
+ socket.shutdownOutput(); // Shutdown socket for writing.
+ }
+ catch(java.net.SocketException ex)
+ {
+ //
+ // Ignore. We can't reliably figure out if the socket
+ // exception is because the socket is not connected.
+ //
+ // if(!IceInternal.Network.notConnected(ex))
+ // {
+ // Ice.SocketException se = new Ice.SocketException();
+ // se.initCause(ex);
+ // throw se;
+ // }
+ }
+ catch(java.io.IOException ex)
+ {
+ Ice.SocketException se = new Ice.SocketException();
+ se.initCause(ex);
+ throw se;
+ }
+ }
+
+ //
+ // All methods that can write to the socket are synchronized.
+ //
+ public synchronized void
+ shutdownReadWrite()
+ {
+ if(_instance.networkTraceLevel() >= 2)
+ {
+ String s = "shutting down ssl connection for reading and writing\n" + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ shutdown();
+
+ assert(_fd != null);
+ java.net.Socket socket = _fd.socket();
+ try
+ {
+ socket.shutdownInput(); // Shutdown socket for reading
+ socket.shutdownOutput(); // Shutdown socket for writing
+ }
+ catch(java.net.SocketException ex)
+ {
+ //
+ // Ignore. We can't reliably figure out if the socket
+ // exception is because the socket is not connected.
+ //
+ // if(!IceInternal.Network.notConnected(ex))
+ // {
+ // Ice.SocketException se = new Ice.SocketException();
+ // se.initCause(ex);
+ // throw se;
+ // }
+ }
+ catch(java.io.IOException ex)
+ {
+ Ice.SocketException se = new Ice.SocketException();
+ se.initCause(ex);
+ throw se;
+ }
+ }
+
+ //
+ // All methods that can write to the socket are synchronized.
+ //
+ public synchronized void
+ write(IceInternal.BasicStream stream, int timeout)
+ throws IceInternal.LocalExceptionWrapper
+ {
+ //
+ // Complete handshaking first if necessary.
+ //
+ if(!_handshakeComplete)
+ {
+ handshake(timeout);
+ }
+
+ ByteBuffer buf = stream.prepareWrite();
+
+ //
+ // Write any pending data to the socket.
+ //
+ flush(timeout);
+
+ try
+ {
+ while(buf.hasRemaining())
+ {
+ final int rem = buf.remaining();
+
+ //
+ // Encrypt the buffer.
+ //
+ SSLEngineResult result = _engine.wrap(buf, _netOutput);
+ switch(result.getStatus())
+ {
+ case BUFFER_OVERFLOW:
+ assert(false);
+ break;
+ case BUFFER_UNDERFLOW:
+ assert(false);
+ break;
+ case CLOSED:
+ throw new Ice.ConnectionLostException();
+ case OK:
+ break;
+ }
+
+ //
+ // Write the encrypted data to the socket.
+ //
+ if(result.bytesProduced() > 0)
+ {
+ flush(timeout);
+
+ if(_instance.networkTraceLevel() >= 3)
+ {
+ String s = "sent " + result.bytesConsumed() + " of " + rem + " bytes via ssl\n" + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ if(_stats != null)
+ {
+ _stats.bytesSent(type(), result.bytesConsumed());
+ }
+ }
+ }
+ }
+ catch(SSLException ex)
+ {
+ Ice.SecurityException e = new Ice.SecurityException();
+ e.reason = "IceSSL: error while encoding message";
+ e.initCause(ex);
+ throw e;
+ }
+ }
+
+ public boolean
+ read(IceInternal.BasicStream stream, int timeout)
+ {
+ ByteBuffer buf = stream.prepareRead();
+
+ int rem = 0;
+ if(_instance.networkTraceLevel() >= 3)
+ {
+ rem = buf.remaining();
+ }
+
+ //
+ // Complete handshaking first if necessary.
+ //
+ if(!_handshakeComplete)
+ {
+ handshake(timeout);
+ }
+
+ //
+ // Try to satisfy the request from data we've already decrypted.
+ //
+ int pos = buf.position();
+ fill(buf);
+
+ if(_instance.networkTraceLevel() >= 3 && buf.position() > pos)
+ {
+ String s = "received " + (buf.position() - pos) + " of " + rem + " bytes via ssl\n" + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ if(_stats != null && buf.position() > pos)
+ {
+ _stats.bytesReceived(type(), buf.position() - pos);
+ }
+
+ //
+ // Read and decrypt more data if necessary. Note that we might read
+ // more data from the socket than is actually necessary to fill the
+ // caller's stream.
+ //
+ try
+ {
+ while(buf.hasRemaining())
+ {
+ _netInput.flip();
+ SSLEngineResult result = _engine.unwrap(_netInput, _appInput);
+ _netInput.compact();
+ switch(result.getStatus())
+ {
+ case BUFFER_OVERFLOW:
+ assert(false);
+ break;
+ case BUFFER_UNDERFLOW:
+ read(timeout);
+ continue;
+ case CLOSED:
+ throw new Ice.ConnectionLostException();
+ case OK:
+ break;
+ }
+
+ pos = buf.position();
+ fill(buf);
+
+ if(_instance.networkTraceLevel() >= 3 && buf.position() > pos)
+ {
+ String s = "received " + (buf.position() - pos) + " of " + rem + " bytes via ssl\n" + toString();
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ if(_stats != null && buf.position() > pos)
+ {
+ _stats.bytesReceived(type(), buf.position() - pos);
+ }
+ }
+ }
+ catch(SSLException ex)
+ {
+ Ice.SecurityException e = new Ice.SecurityException();
+ e.reason = "IceSSL: error during read";
+ e.initCause(ex);
+ throw e;
+ }
+
+ //
+ // Return a boolean to indicate whether more data is available.
+ //
+ return _netInput.position() > 0;
+ }
+
+ public String
+ type()
+ {
+ return "ssl";
+ }
+
+ public String
+ toString()
+ {
+ return _desc;
+ }
+
+ public void
+ checkSendSize(IceInternal.BasicStream stream, int messageSizeMax)
+ {
+ if(stream.size() > messageSizeMax)
+ {
+ throw new Ice.MemoryLimitException();
+ }
+ }
+
+ ConnectionInfo
+ getConnectionInfo()
+ {
+ //
+ // This can only be called on an open transceiver.
+ //
+ assert(_fd != null);
+ return _info;
+ }
+
+ //
+ // Only for use by ConnectorI, AcceptorI.
+ //
+ TransceiverI(Instance instance, javax.net.ssl.SSLEngine engine, java.nio.channels.SocketChannel fd,
+ String host, boolean incoming, String adapterName)
+ {
+ _instance = instance;
+ _engine = engine;
+ _fd = fd;
+ _host = host;
+ _adapterName = adapterName;
+ _incoming = incoming;
+ _logger = instance.communicator().getLogger();
+ try
+ {
+ _stats = instance.communicator().getStats();
+ }
+ catch(Ice.CommunicatorDestroyedException ex)
+ {
+ // Ignore.
+ }
+ _desc = IceInternal.Network.fdToString(_fd);
+ _maxPacketSize = 0;
+ if(System.getProperty("os.name").startsWith("Windows"))
+ {
+ //
+ // On Windows, limiting the buffer size is important to prevent
+ // poor throughput performances when transfering large amount of
+ // data. See Microsoft KB article KB823764.
+ //
+ _maxPacketSize = IceInternal.Network.getSendBufferSize(_fd) / 2;
+ if(_maxPacketSize < 512)
+ {
+ _maxPacketSize = 0;
+ }
+ }
+
+ // TODO: Buffer cache?
+ _appInput = ByteBuffer.allocateDirect(engine.getSession().getApplicationBufferSize() * 2);
+ _netInput = ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize() * 2);
+ _netOutput = ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize() * 2);
+ _handshakeComplete = false;
+ }
+
+ protected void
+ finalize()
+ throws Throwable
+ {
+ IceUtil.Assert.FinalizerAssert(_fd == null);
+
+ super.finalize();
+ }
+
+ private void
+ flush(int timeout)
+ {
+ _netOutput.flip();
+
+ int size = _netOutput.limit();
+ int packetSize = 0;
+ if(_maxPacketSize > 0 && size > _maxPacketSize)
+ {
+ packetSize = _maxPacketSize;
+ _netOutput.limit(_netOutput.position() + packetSize);
+ }
+
+ while(_netOutput.hasRemaining())
+ {
+ try
+ {
+ assert(_fd != null);
+ int ret = _fd.write(_netOutput);
+
+ if(ret == -1)
+ {
+ throw new Ice.ConnectionLostException();
+ }
+
+ if(ret == 0)
+ {
+ if(timeout == 0)
+ {
+ throw new Ice.TimeoutException();
+ }
+
+ if(_writeSelector == null)
+ {
+ _writeSelector = java.nio.channels.Selector.open();
+ _fd.register(_writeSelector, java.nio.channels.SelectionKey.OP_WRITE, null);
+ }
+
+ try
+ {
+ if(timeout > 0)
+ {
+ long start = System.currentTimeMillis();
+ int n = _writeSelector.select(timeout);
+ if(n == 0 && System.currentTimeMillis() >= start + timeout)
+ {
+ throw new Ice.TimeoutException();
+ }
+ }
+ else
+ {
+ _writeSelector.select();
+ }
+ }
+ catch(java.io.InterruptedIOException ex)
+ {
+ // Ignore.
+ }
+
+ continue;
+ }
+ }
+ catch(java.io.InterruptedIOException ex)
+ {
+ continue;
+ }
+ catch(java.io.IOException ex)
+ {
+ if(IceInternal.Network.connectionLost(ex))
+ {
+ Ice.ConnectionLostException se = new Ice.ConnectionLostException();
+ se.initCause(ex);
+ throw se;
+ }
+
+ Ice.SocketException se = new Ice.SocketException();
+ se.initCause(ex);
+ throw se;
+ }
+
+ if(packetSize > 0)
+ {
+ assert(_netOutput.position() == _netOutput.limit());
+ int position = _netOutput.position();
+ if(size - position > packetSize)
+ {
+ _netOutput.limit(position + packetSize);
+ }
+ else
+ {
+ packetSize = 0;
+ _netOutput.limit(size);
+ }
+ }
+ }
+ _netOutput.clear();
+ }
+
+ private void
+ read(int timeout)
+ {
+ while(true)
+ {
+ try
+ {
+ assert(_fd != null);
+ int ret = _fd.read(_netInput);
+
+ if(ret == -1)
+ {
+ throw new Ice.ConnectionLostException();
+ }
+
+ if(ret == 0)
+ {
+ if(timeout == 0)
+ {
+ throw new Ice.TimeoutException();
+ }
+
+ if(_readSelector == null)
+ {
+ _readSelector = java.nio.channels.Selector.open();
+ _fd.register(_readSelector, java.nio.channels.SelectionKey.OP_READ, null);
+ }
+
+ try
+ {
+ if(timeout > 0)
+ {
+ long start = System.currentTimeMillis();
+ int n = _readSelector.select(timeout);
+ if(n == 0 && System.currentTimeMillis() >= start + timeout)
+ {
+ throw new Ice.TimeoutException();
+ }
+ }
+ else
+ {
+ _readSelector.select();
+ }
+ }
+ catch(java.io.InterruptedIOException ex)
+ {
+ // Ignore.
+ }
+
+ continue;
+ }
+
+ break;
+ }
+ catch(java.io.InterruptedIOException ex)
+ {
+ continue;
+ }
+ catch(java.io.IOException ex)
+ {
+ if(IceInternal.Network.connectionLost(ex))
+ {
+ Ice.ConnectionLostException se = new Ice.ConnectionLostException();
+ se.initCause(ex);
+ throw se;
+ }
+
+ Ice.SocketException se = new Ice.SocketException();
+ se.initCause(ex);
+ throw se;
+ }
+ }
+ }
+
+ private void
+ handshake(int timeout)
+ {
+ try
+ {
+ HandshakeStatus status = _engine.getHandshakeStatus();
+ while(!_handshakeComplete)
+ {
+ SSLEngineResult result = null;
+ switch(status)
+ {
+ case FINISHED:
+ handshakeCompleted();
+ break;
+ case NEED_TASK:
+ {
+ // TODO: Use separate threads & join with timeout?
+ Runnable task;
+ while((task = _engine.getDelegatedTask()) != null)
+ {
+ task.run();
+ }
+ status = _engine.getHandshakeStatus();
+ break;
+ }
+ case NEED_UNWRAP:
+ {
+ //
+ // The engine needs more data. We might already have enough data in
+ // the _netInput buffer to satisfy the engine. If not, the engine
+ // responds with BUFFER_UNDERFLOW and we'll read from the socket.
+ //
+ _netInput.flip();
+ result = _engine.unwrap(_netInput, _appInput);
+ _netInput.compact();
+ //
+ // FINISHED is only returned from wrap or unwrap, not from engine.getHandshakeResult().
+ //
+ status = result.getHandshakeStatus();
+ switch(result.getStatus())
+ {
+ case BUFFER_OVERFLOW:
+ assert(false);
+ break;
+ case BUFFER_UNDERFLOW:
+ assert(status == javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP);
+ read(timeout);
+ break;
+ case CLOSED:
+ throw new Ice.ConnectionLostException();
+ case OK:
+ break;
+ }
+ break;
+ }
+ case NEED_WRAP:
+ {
+ //
+ // The engine needs to send a message.
+ //
+ result = _engine.wrap(_emptyBuffer, _netOutput);
+ if(result.bytesProduced() > 0)
+ {
+ flush(timeout);
+ }
+ //
+ // FINISHED is only returned from wrap or unwrap, not from engine.getHandshakeResult().
+ //
+ status = result.getHandshakeStatus();
+ break;
+ }
+ case NOT_HANDSHAKING:
+ assert(false);
+ break;
+ }
+
+ if(result != null)
+ {
+ switch(result.getStatus())
+ {
+ case BUFFER_OVERFLOW:
+ assert(false);
+ break;
+ case BUFFER_UNDERFLOW:
+ // Need to read again.
+ assert(status == HandshakeStatus.NEED_UNWRAP);
+ break;
+ case CLOSED:
+ throw new Ice.ConnectionLostException();
+ case OK:
+ break;
+ }
+ }
+ }
+ }
+ catch(SSLException ex)
+ {
+ Ice.SecurityException e = new Ice.SecurityException();
+ e.reason = "IceSSL: handshake error";
+ e.initCause(ex);
+ throw e;
+ }
+ }
+
+ private void
+ fill(ByteBuffer buf)
+ {
+ _appInput.flip();
+ if(_appInput.hasRemaining())
+ {
+ int bytesAvailable = _appInput.remaining();
+ int bytesNeeded = buf.remaining();
+ if(bytesAvailable > bytesNeeded)
+ {
+ bytesAvailable = bytesNeeded;
+ }
+ if(buf.hasArray())
+ {
+ //
+ // Copy directly into the destination buffer's backing array.
+ //
+ byte[] arr = buf.array();
+ int offset = buf.arrayOffset() + buf.position();
+ _appInput.get(arr, offset, bytesAvailable);
+ buf.position(offset + bytesAvailable);
+ }
+ else if(_appInput.hasArray())
+ {
+ //
+ // Copy directly from the source buffer's backing array.
+ //
+ byte[] arr = _appInput.array();
+ int offset = _appInput.arrayOffset() + _appInput.position();
+ buf.put(arr, offset, bytesAvailable);
+ _appInput.position(offset + bytesAvailable);
+ }
+ else
+ {
+ //
+ // Copy using a temporary array.
+ //
+ byte[] arr = new byte[bytesAvailable];
+ _appInput.get(arr);
+ buf.put(arr);
+ }
+ }
+ _appInput.compact();
+ }
+
+ private void
+ shutdown()
+ {
+ //
+ // Send the close_notify message.
+ //
+ _engine.closeOutbound();
+ try
+ {
+ _netOutput.clear();
+ while(!_engine.isOutboundDone())
+ {
+ _engine.wrap(_emptyBuffer, _netOutput);
+ try
+ {
+ flush(-1);
+ }
+ catch(Ice.ConnectionLostException ex)
+ {
+ // Ignore.
+ }
+ }
+ }
+ catch(SSLException ex)
+ {
+ Ice.SecurityException se = new Ice.SecurityException();
+ se.reason = "IceSSL: SSL failure while shutting down socket";
+ se.initCause(ex);
+ throw se;
+ }
+ }
+
+ private void
+ handshakeCompleted()
+ {
+ _handshakeComplete = true;
+
+ //
+ // IceSSL.VerifyPeer is translated into the proper SSLEngine configuration
+ // for a server, but we have to do it ourselves for a client.
+ //
+ if(!_incoming)
+ {
+ int verifyPeer =
+ _instance.communicator().getProperties().getPropertyAsIntWithDefault("IceSSL.VerifyPeer", 2);
+ if(verifyPeer > 0)
+ {
+ try
+ {
+ _engine.getSession().getPeerCertificates();
+ }
+ catch(javax.net.ssl.SSLPeerUnverifiedException ex)
+ {
+ Ice.SecurityException e = new Ice.SecurityException();
+ e.reason = "IceSSL: server did not supply a certificate";
+ e.initCause(ex);
+ throw e;
+ }
+ }
+ }
+
+ //
+ // Additional verification.
+ //
+ _info = Util.populateConnectionInfo(_engine.getSession(), _fd.socket(), _adapterName, _incoming);
+ _instance.verifyPeer(_info, _fd, _host, _incoming);
+
+ if(_instance.networkTraceLevel() >= 1)
+ {
+ String s;
+ if(_incoming)
+ {
+ s = "accepted ssl connection\n" + IceInternal.Network.fdToString(_fd);
+ }
+ else
+ {
+ s = "ssl connection established\n" + IceInternal.Network.fdToString(_fd);
+ }
+ _logger.trace(_instance.networkTraceCategory(), s);
+ }
+
+ if(_instance.securityTraceLevel() >= 1)
+ {
+ _instance.traceConnection(_fd, _engine, _incoming);
+ }
+ }
+
+ private Instance _instance;
+ private java.nio.channels.SocketChannel _fd;
+ private javax.net.ssl.SSLEngine _engine;
+ private String _host;
+ private String _adapterName;
+ private boolean _incoming;
+ private Ice.Logger _logger;
+ private Ice.Stats _stats;
+ private String _desc;
+ private int _maxPacketSize;
+ private ByteBuffer _appInput; // Holds clear-text data to be read by the application.
+ private ByteBuffer _netInput; // Holds encrypted data read from the socket.
+ private ByteBuffer _netOutput; // Holds encrypted data to be written to the socket.
+ private static ByteBuffer _emptyBuffer = ByteBuffer.allocate(0); // Used during handshaking.
+ private boolean _handshakeComplete;
+ private java.nio.channels.Selector _readSelector;
+ private java.nio.channels.Selector _writeSelector;
+ private ConnectionInfo _info;
+}
diff --git a/java/src/IceSSL/TrustManager.java b/java/src/IceSSL/TrustManager.java
new file mode 100644
index 00000000000..8598c1ac7e6
--- /dev/null
+++ b/java/src/IceSSL/TrustManager.java
@@ -0,0 +1,295 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+class TrustManager
+{
+ TrustManager(Ice.Communicator communicator)
+ {
+ assert communicator != null;
+ _communicator = communicator;
+ Ice.Properties properties = communicator.getProperties();
+ _traceLevel = properties.getPropertyAsInt("IceSSL.Trace.Security");
+ String key = null;
+ try
+ {
+ key = "IceSSL.TrustOnly";
+ _all = parse(properties.getProperty(key));
+ key = "IceSSL.TrustOnly.Client";
+ _client = parse(properties.getProperty(key));
+ key = "IceSSL.TrustOnly.Server";
+ _allServer = parse(properties.getProperty(key));
+ java.util.Map dict = properties.getPropertiesForPrefix("IceSSL.TrustOnly.Server.");
+ java.util.Iterator p = dict.entrySet().iterator();
+ while(p.hasNext())
+ {
+ java.util.Map.Entry entry = (java.util.Map.Entry)p.next();
+ key = (String)entry.getKey();
+ String name = key.substring("IceSSL.TrustOnly.Server.".length());
+ _server.put(name, parse((String)entry.getValue()));
+ }
+ }
+ catch(RFC2253.ParseException e)
+ {
+ Ice.PluginInitializationException ex = new Ice.PluginInitializationException();
+ ex.reason = "IceSSL: invalid property " + key + ":\n" + e.reason;
+ throw ex;
+ }
+ }
+
+ boolean
+ verify(ConnectionInfo info)
+ {
+ java.util.List trustset = new java.util.LinkedList();
+ if(!_all.isEmpty())
+ {
+ trustset.add(_all);
+ }
+
+ if(info.incoming)
+ {
+ if(!_allServer.isEmpty())
+ {
+ trustset.add(_allServer);
+ }
+ if(info.adapterName.length() > 0)
+ {
+ java.util.List p = (java.util.List)_server.get(info.adapterName);
+ if(p != null)
+ {
+ trustset.add(p);
+ }
+ }
+ }
+ else
+ {
+ if(!_client.isEmpty())
+ {
+ trustset.add(_client);
+ }
+ }
+
+ //
+ // If there is nothing to match against, then we accept the cert.
+ //
+ if(trustset.isEmpty())
+ {
+ return true;
+ }
+
+ //
+ // If there is no certificate then we match false.
+ //
+ if(info.certs != null && info.certs.length > 0)
+ {
+ javax.security.auth.x500.X500Principal subjectDN = (javax.security.auth.x500.X500Principal)
+ ((java.security.cert.X509Certificate)info.certs[0]).getSubjectX500Principal();
+ String subjectName = subjectDN.getName(javax.security.auth.x500.X500Principal.RFC2253);
+ assert subjectName != null;
+ try
+ {
+ //
+ // Decompose the subject DN into the RDNs.
+ //
+ if(_traceLevel > 0)
+ {
+ if(info.incoming)
+ {
+ _communicator.getLogger().trace("Security", "trust manager evaluating client:\n" +
+ "subject = " + subjectName + "\n" +
+ "adapter = " + info.adapterName + "\n" +
+ "local addr = " + IceInternal.Network.addrToString(info.localAddr) + "\n" +
+ "remote addr = " + IceInternal.Network.addrToString(info.remoteAddr));
+ }
+ else
+ {
+ _communicator.getLogger().trace("Security", "trust manager evaluating server:\n" +
+ "subject = " + subjectName + "\n" +
+ "local addr = " + IceInternal.Network.addrToString(info.localAddr) + "\n" +
+ "remote addr = " + IceInternal.Network.addrToString(info.remoteAddr));
+ }
+ }
+ java.util.List dn = RFC2253.parseStrict(subjectName);
+
+ //
+ // Try matching against everything in the trust set.
+ //
+ java.util.Iterator p = trustset.iterator();
+ while(p.hasNext())
+ {
+ java.util.List matchSet = (java.util.List)p.next();
+ if(_traceLevel > 1)
+ {
+ String s = "trust manager matching PDNs:\n";
+ java.util.Iterator q = matchSet.iterator();
+ boolean addSemi = false;
+ while(q.hasNext())
+ {
+ if(addSemi)
+ {
+ s += ';';
+ }
+ addSemi = true;
+ java.util.List rdnSet = (java.util.List)q.next();
+ java.util.Iterator r = rdnSet.iterator();
+ boolean addComma = false;
+ while(r.hasNext())
+ {
+ if(addComma)
+ {
+ s += ',';
+ }
+ addComma = true;
+ RFC2253.RDNPair rdn = (RFC2253.RDNPair)r.next();
+ s += rdn.key;
+ s += '=';
+ s += rdn.value;
+ }
+ }
+ _communicator.getLogger().trace("Security", s);
+ }
+ if(match(matchSet, dn))
+ {
+ return true;
+ }
+ }
+ }
+ catch(RFC2253.ParseException e)
+ {
+ _communicator.getLogger().warning(
+ "IceSSL: unable to parse certificate DN `" + subjectName + "'\nreason: " + e.reason);
+ }
+ }
+
+ return false;
+ }
+
+ private boolean
+ match(java.util.List matchSet, java.util.List subject)
+ {
+ java.util.Iterator r = matchSet.iterator();
+ while(r.hasNext())
+ {
+ if(matchRDNs((java.util.List)r.next(), subject))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean
+ matchRDNs(java.util.List match, java.util.List subject)
+ {
+ java.util.Iterator p = match.iterator();
+ while(p.hasNext())
+ {
+ RFC2253.RDNPair matchRDN = (RFC2253.RDNPair)p.next();
+ boolean found = false;
+ java.util.Iterator q = subject.iterator();
+ while(q.hasNext())
+ {
+ RFC2253.RDNPair subjectRDN = (RFC2253.RDNPair)q.next();
+ if(matchRDN.key.equals(subjectRDN.key))
+ {
+ found = true;
+ if(!matchRDN.value.equals(subjectRDN.value))
+ {
+ return false;
+ }
+ }
+ }
+ if(!found)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ java.util.List
+ parse(String value)
+ throws RFC2253.ParseException
+ {
+ //
+ // Java X500Principal.getName says:
+ //
+ // If "RFC2253" is specified as the format, this method emits
+ // the attribute type keywords defined in RFC 2253 (CN, L, ST,
+ // O, OU, C, STREET, DC, UID). Any other attribute type is
+ // emitted as an OID. Under a strict reading, RFC 2253 only
+ // specifies a UTF-8 string representation. The String
+ // returned by this method is the Unicode string achieved by
+ // decoding this UTF-8 representation.
+ //
+ // This means that things like emailAddress and such will be turned into
+ // something like:
+ //
+ // 1.2.840.113549.1.9.1=#160e696e666f407a65726f632e636f6d
+ //
+ // The left hand side is the OID (see
+ // http://www.columbia.edu/~ariel/ssleay/asn1-oids.html) for a
+ // list. The right hand side is a BER encoding of the value.
+ //
+ // This means that the user input, unless it uses the
+ // unfriendly OID format, will not directly match the
+ // principal.
+ //
+ // Two possible solutions:
+ //
+ // Have the RFC2253 parser convert anything that is not CN, L,
+ // ST, O, OU, C, STREET, DC, UID into OID format, and have it
+ // convert the values into a BER encoding.
+ //
+ // Send the user data through X500Principal to string form and
+ // then through the RFC2253 encoder. This uses the
+ // X500Principal to do the encoding for us.
+ //
+ // The latter is much simpler, however, it means we need to
+ // send the data through the parser twice because we split the
+ // DNs on ';' which cannot be blindly split because of quotes,
+ // \ and such.
+ //
+ java.util.List l = RFC2253.parse(value);
+ java.util.List result = new java.util.LinkedList();
+ java.util.Iterator p = l.iterator();
+ while(p.hasNext())
+ {
+ java.util.List dn = (java.util.List)p.next();
+ String v = new String();
+ boolean first = true;
+ java.util.Iterator q = dn.iterator();
+ while(q.hasNext())
+ {
+ if(!first)
+ {
+ v += ",";
+ }
+ first = false;
+ RFC2253.RDNPair pair = (RFC2253.RDNPair)q.next();
+ v += pair.key;
+ v += "=";
+ v += pair.value;
+ }
+ javax.security.auth.x500.X500Principal princ = new javax.security.auth.x500.X500Principal(v);
+ String subjectName = princ.getName(javax.security.auth.x500.X500Principal.RFC2253);
+ result.add(RFC2253.parseStrict(subjectName));
+ }
+ return result;
+ }
+
+ private Ice.Communicator _communicator;
+ private int _traceLevel;
+
+ private java.util.List _all;
+ private java.util.List _client;
+ private java.util.List _allServer;
+ private java.util.Map _server = new java.util.HashMap();
+}
diff --git a/java/src/IceSSL/Util.java b/java/src/IceSSL/Util.java
new file mode 100644
index 00000000000..fcde66a5db4
--- /dev/null
+++ b/java/src/IceSSL/Util.java
@@ -0,0 +1,127 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+public final class Util
+{
+ public static ConnectionInfo
+ getConnectionInfo(Ice.Connection connection)
+ {
+ Ice.ConnectionI con = (Ice.ConnectionI)connection;
+ assert(con != null);
+
+ //
+ // Lock the connection directly. This is done because the only
+ // thing that prevents the transceiver from being closed during
+ // the duration of the invocation is the connection.
+ //
+ synchronized(con)
+ {
+ IceInternal.Transceiver transceiver = con.getTransceiver();
+ if(transceiver == null)
+ {
+ ConnectionInvalidException ex = new ConnectionInvalidException();
+ ex.reason = "connection closed";
+ throw ex;
+ }
+
+ try
+ {
+ TransceiverI sslTransceiver = (TransceiverI)transceiver;
+ return sslTransceiver.getConnectionInfo();
+ }
+ catch(ClassCastException ex)
+ {
+ ConnectionInvalidException e = new ConnectionInvalidException();
+ e.reason = "not ssl connection";
+ throw e;
+ }
+ }
+ }
+
+ //
+ // Create a certificate from a PEM-encoded string.
+ //
+ public static java.security.cert.X509Certificate
+ createCertificate(String certPEM)
+ throws java.security.cert.CertificateException
+ {
+ final String header = "-----BEGIN CERTIFICATE-----";
+ final String footer = "-----END CERTIFICATE-----";
+
+ //
+ // The generateCertificate method requires that its input begin
+ // with the PEM header.
+ //
+ int pos = certPEM.indexOf(header);
+ if(pos == -1)
+ {
+ certPEM = header + "\n" + certPEM;
+ }
+ else if(pos > 0)
+ {
+ certPEM = certPEM.substring(pos);
+ }
+
+ //
+ // Add the footer if necessary.
+ //
+ if(certPEM.indexOf(footer) == -1)
+ {
+ certPEM = certPEM + footer;
+ }
+
+ byte[] bytes = null;
+ try
+ {
+ bytes = certPEM.getBytes("UTF8");
+ }
+ catch(java.io.UnsupportedEncodingException ex)
+ {
+ assert(false);
+ return null;
+ }
+
+ java.io.ByteArrayInputStream in = new java.io.ByteArrayInputStream(bytes);
+ java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
+ return (java.security.cert.X509Certificate)cf.generateCertificate(in);
+ }
+
+ static ConnectionInfo
+ populateConnectionInfo(javax.net.ssl.SSLSession session, java.net.Socket fd, String adapterName, boolean incoming)
+ {
+ ConnectionInfo info = new ConnectionInfo();
+ try
+ {
+ info.certs = session.getPeerCertificates();
+ }
+ catch(javax.net.ssl.SSLPeerUnverifiedException ex)
+ {
+ // No peer certificates.
+ }
+ info.cipher = session.getCipherSuite();
+ info.localAddr = (java.net.InetSocketAddress)fd.getLocalSocketAddress();
+ info.remoteAddr = (java.net.InetSocketAddress)fd.getRemoteSocketAddress();
+ info.adapterName = adapterName;
+ info.incoming = incoming;
+ return info;
+ }
+
+ public final static String jdkTarget = "1.5";
+
+ //
+ // Needed by the test scripts to determine the JDK target of the SSL plug-in.
+ //
+ public static void
+ main(String[] args)
+ {
+ System.out.println(jdkTarget);
+ }
+}
diff --git a/java/src/IceSSL/X509KeyManagerI.java b/java/src/IceSSL/X509KeyManagerI.java
new file mode 100644
index 00000000000..8b209ee3978
--- /dev/null
+++ b/java/src/IceSSL/X509KeyManagerI.java
@@ -0,0 +1,70 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+final class X509KeyManagerI extends javax.net.ssl.X509ExtendedKeyManager
+{
+ X509KeyManagerI(javax.net.ssl.X509KeyManager del, String alias)
+ {
+ _delegate = del;
+ _alias = alias;
+ }
+
+ public String
+ chooseClientAlias(String[] keyType, java.security.Principal[] issuers, java.net.Socket socket)
+ {
+ return _alias;
+ }
+
+ public String
+ chooseEngineClientAlias(String[] keyType, java.security.Principal[] issuers, javax.net.ssl.SSLEngine engine)
+ {
+ return _alias;
+ }
+
+ public String
+ chooseServerAlias(String keyType, java.security.Principal[] issuers, java.net.Socket socket)
+ {
+ return _alias;
+ }
+
+ public String
+ chooseEngineServerAlias(String[] keyType, java.security.Principal[] issuers, javax.net.ssl.SSLEngine engine)
+ {
+ return _alias;
+ }
+
+ public java.security.cert.X509Certificate[]
+ getCertificateChain(String alias)
+ {
+ return _delegate.getCertificateChain(alias);
+ }
+
+ public String[]
+ getClientAliases(String keyType, java.security.Principal[] issuers)
+ {
+ return _delegate.getClientAliases(keyType, issuers);
+ }
+
+ public String[]
+ getServerAliases(String keyType, java.security.Principal[] issuers)
+ {
+ return _delegate.getServerAliases(keyType, issuers);
+ }
+
+ public java.security.PrivateKey
+ getPrivateKey(String alias)
+ {
+ return _delegate.getPrivateKey(alias);
+ }
+
+ private javax.net.ssl.X509KeyManager _delegate;
+ private String _alias;
+}
diff --git a/java/src/IceSSL/X509TrustManagerI.java b/java/src/IceSSL/X509TrustManagerI.java
new file mode 100644
index 00000000000..325287daec1
--- /dev/null
+++ b/java/src/IceSSL/X509TrustManagerI.java
@@ -0,0 +1,50 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceSSL;
+
+final class X509TrustManagerI implements javax.net.ssl.X509TrustManager
+{
+ X509TrustManagerI(javax.net.ssl.X509TrustManager delegate)
+ {
+ _delegate = delegate;
+ }
+
+ public void
+ checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
+ throws java.security.cert.CertificateException
+ {
+ if(!authType.equals("DH_anon") && _delegate != null)
+ {
+ _delegate.checkClientTrusted(chain, authType);
+ }
+ }
+
+ public void
+ checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
+ throws java.security.cert.CertificateException
+ {
+ if(!authType.equals("DH_anon") && _delegate != null)
+ {
+ _delegate.checkServerTrusted(chain, authType);
+ }
+ }
+
+ public java.security.cert.X509Certificate[]
+ getAcceptedIssuers()
+ {
+ if(_delegate != null)
+ {
+ return _delegate.getAcceptedIssuers();
+ }
+ return null;
+ }
+
+ private javax.net.ssl.X509TrustManager _delegate;
+}