diff options
author | Mark Spruiell <mes@zeroc.com> | 2006-04-13 21:20:25 +0000 |
---|---|---|
committer | Mark Spruiell <mes@zeroc.com> | 2006-04-13 21:20:25 +0000 |
commit | 074bf1d6b113fffa7cf9df6433b90311f2199ea1 (patch) | |
tree | aff0594b90bceb6f6786b81ebecbcb16e735bb5f /java/src | |
parent | SSL fix (diff) | |
download | ice-074bf1d6b113fffa7cf9df6433b90311f2199ea1.tar.bz2 ice-074bf1d6b113fffa7cf9df6433b90311f2199ea1.tar.xz ice-074bf1d6b113fffa7cf9df6433b90311f2199ea1.zip |
cleanup, refactoring, align with C++/C#
Diffstat (limited to 'java/src')
-rw-r--r-- | java/src/IceSSL/AcceptorI.java | 242 | ||||
-rw-r--r-- | java/src/IceSSL/CertificateVerifier.java | 23 | ||||
-rw-r--r-- | java/src/IceSSL/ConnectorI.java | 355 | ||||
-rw-r--r-- | java/src/IceSSL/Context.java | 453 | ||||
-rw-r--r-- | java/src/IceSSL/Instance.java | 107 | ||||
-rw-r--r-- | java/src/IceSSL/KeyManagerI.java | 74 | ||||
-rw-r--r-- | java/src/IceSSL/Plugin.java | 28 | ||||
-rw-r--r-- | java/src/IceSSL/PluginI.java | 14 | ||||
-rw-r--r-- | java/src/IceSSL/VerifyInfo.java | 45 |
9 files changed, 1042 insertions, 299 deletions
diff --git a/java/src/IceSSL/AcceptorI.java b/java/src/IceSSL/AcceptorI.java index 1c0b94a0d78..5229c896160 100644 --- a/java/src/IceSSL/AcceptorI.java +++ b/java/src/IceSSL/AcceptorI.java @@ -73,20 +73,150 @@ class AcceptorI implements IceInternal.Acceptor } _fd.setSoTimeout(timeout); fd = (javax.net.ssl.SSLSocket)_fd.accept(); + + // + // 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.getRemoteSocketAddress(); + synchronized(this) + { + if(remoteAddr.equals(_connectToSelfAddr)) + { + try + { + fd.close(); + } + catch(java.io.IOException e) + { + } + return null; + } + } + fd.setUseClientMode(false); + + // + // getSession blocks until the initial handshake completes. + // + if(timeout == 0) + { + fd.getSession(); + } + else + { + HandshakeThread ht = new HandshakeThread(fd); + ht.start(); + if(!ht.waitForHandshake(timeout)) + { + throw new Ice.TimeoutException(); + } + } + + if(!_ctx.verifyPeer(fd, "", true)) + { + try + { + fd.close(); + } + catch(java.io.IOException e) + { + } + return null; + } } catch(java.net.SocketTimeoutException ex) { + if(fd != null) + { + try + { + fd.close(); + } + catch(java.io.IOException e) + { + } + } Ice.TimeoutException e = new Ice.TimeoutException(); e.initCause(ex); throw e; } + catch(javax.net.ssl.SSLException ex) + { + if(fd != null) + { + try + { + fd.close(); + } + catch(java.io.IOException e) + { + } + } + + // + // Unfortunately, the situation where the cipher suite does not match + // the certificates is not detected until accept is called. If we were + // to throw a LocalException, the IncomingConnectionFactory would + // simply log it and call accept again, resulting in an infinite loop. + // To avoid this problem, we check for the special case and throw + // an exception that IncomingConnectionFactory doesn't trap. + // + if(ex.getMessage().toLowerCase().startsWith("no available certificate corresponds to the ssl cipher " + + "suites which are enabled")) + { + RuntimeException e = new RuntimeException(); + e.initCause(ex); + throw e; + } + + Ice.SecurityException e = new Ice.SecurityException(); + e.initCause(ex); + throw e; + } catch(java.io.IOException ex) { + if(fd != null) + { + try + { + fd.close(); + } + catch(java.io.IOException e) + { + } + } + + if(IceInternal.Network.connectionLost(ex)) + { + throw new Ice.ConnectionLostException(); + } + Ice.SocketException e = new Ice.SocketException(); e.initCause(ex); throw e; } + catch(RuntimeException ex) + { + if(fd != null) + { + try + { + fd.close(); + } + catch(java.io.IOException e) + { + } + } + throw ex; + } if(_instance.networkTraceLevel() >= 1) { @@ -94,6 +224,11 @@ class AcceptorI implements IceInternal.Acceptor _logger.trace(_instance.networkTraceCategory(), s); } + if(_instance.securityTraceLevel() > 0) + { + _ctx.traceConnection(fd, true); + } + return new TransceiverI(_instance, fd); } @@ -102,7 +237,16 @@ class AcceptorI implements IceInternal.Acceptor { java.nio.channels.SocketChannel fd = IceInternal.Network.createTcpSocket(); IceInternal.Network.setBlock(fd, false); - IceInternal.Network.doConnect(fd, _addr, -1); + 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); } @@ -175,6 +319,17 @@ class AcceptorI implements IceInternal.Acceptor } String[] cipherSuites = _ctx.filterCiphers(_fd.getSupportedCipherSuites(), _fd.getEnabledCipherSuites()); + try + { + _fd.setEnabledCipherSuites(cipherSuites); + } + catch(IllegalArgumentException ex) + { + Ice.SecurityException e = new Ice.SecurityException(); + e.reason = "invalid ciphersuite"; + e.initCause(ex); + throw e; + } if(_instance.securityTraceLevel() > 0) { StringBuffer s = new StringBuffer(); @@ -185,7 +340,22 @@ class AcceptorI implements IceInternal.Acceptor } _logger.trace(_instance.securityTraceCategory(), s.toString()); } - _fd.setEnabledCipherSuites(cipherSuites); + + String[] protocols = _ctx.getProtocols(); + if(protocols != null) + { + try + { + _fd.setEnabledProtocols(protocols); + } + catch(IllegalArgumentException ex) + { + Ice.SecurityException e = new Ice.SecurityException(); + e.reason = "invalid protocol"; + e.initCause(ex); + throw e; + } + } } catch(java.io.IOException ex) { @@ -215,10 +385,78 @@ class AcceptorI implements IceInternal.Acceptor super.finalize(); } + private static class HandshakeThread extends Thread + { + HandshakeThread(javax.net.ssl.SSLSocket fd) + { + _fd = fd; + _ok = false; + } + + public void + run() + { + try + { + _fd.getSession(); + synchronized(this) + { + _ok = true; + notifyAll(); + } + + } + catch(RuntimeException ex) + { + synchronized(this) + { + _ex = ex; + notifyAll(); + } + } + } + + boolean + waitForHandshake(int timeout) + { + boolean result = false; + + synchronized(this) + { + while(!_ok && _ex == null) + { + try + { + wait(timeout); + break; + } + catch(InterruptedException ex) + { + continue; + } + } + + if(_ex != null) + { + throw _ex; + } + + result = _ok; + } + + return result; + } + + private javax.net.ssl.SSLSocket _fd; + private boolean _ok; + private RuntimeException _ex; + } + private Instance _instance; private Context _ctx; private Ice.Logger _logger; private javax.net.ssl.SSLServerSocket _fd; private int _backlog; private java.net.InetSocketAddress _addr; + 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..485275fce98 --- /dev/null +++ b/java/src/IceSSL/CertificateVerifier.java @@ -0,0 +1,23 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2006 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 true to allow a connection using the provided certificate + // information, or false to reject the connection. + // + boolean verify(VerifyInfo info); +} diff --git a/java/src/IceSSL/ConnectorI.java b/java/src/IceSSL/ConnectorI.java index 52d1269059e..785eb02d359 100644 --- a/java/src/IceSSL/ConnectorI.java +++ b/java/src/IceSSL/ConnectorI.java @@ -11,148 +11,14 @@ package IceSSL; final class ConnectorI implements IceInternal.Connector { - private static class ConnectThread extends Thread - { - ConnectThread(javax.net.ssl.SSLContext ctx, java.net.InetSocketAddress addr) - { - _ctx = ctx; - _addr = addr; - } - - public void - run() - { - try - { - javax.net.SocketFactory factory = _ctx.getSocketFactory(); - javax.net.ssl.SSLSocket fd = - (javax.net.ssl.SSLSocket)factory.createSocket(_addr.getAddress(), _addr.getPort()); - synchronized(this) - { - _fd = fd; - notifyAll(); - } - } - catch(java.io.IOException ex) - { - synchronized(this) - { - _ex = ex; - notifyAll(); - } - } - } - - javax.net.ssl.SSLSocket - getFd(int timeout) - throws java.io.IOException - { - javax.net.ssl.SSLSocket fd = null; - - synchronized(this) - { - while(_fd == null && _ex == null) - { - try - { - wait(timeout); - break; - } - catch(InterruptedException ex) - { - continue; - } - } - - if(_ex != null) - { - throw _ex; - } - - fd = _fd; - _fd = null; - } - - return fd; - } - - private javax.net.ssl.SSLContext _ctx; - private java.net.InetSocketAddress _addr; - private javax.net.ssl.SSLSocket _fd; - private java.io.IOException _ex; - } - - private static class HandshakeThread extends Thread - { - HandshakeThread(javax.net.ssl.SSLSocket fd) - { - _fd = fd; - _ok = false; - } - - public void - run() - { - try - { - _fd.startHandshake(); - synchronized(this) - { - _ok = true; - notifyAll(); - } - - } - catch(java.io.IOException ex) - { - synchronized(this) - { - _ex = ex; - notifyAll(); - } - } - } - - boolean - waitForHandshake(int timeout) - throws java.io.IOException - { - boolean result = false; - - synchronized(this) - { - while(!_ok && _ex == null) - { - try - { - wait(timeout); - break; - } - catch(InterruptedException ex) - { - continue; - } - } - - if(_ex != null) - { - throw _ex; - } - - result = _ok; - } - - return result; - } - - private javax.net.ssl.SSLSocket _fd; - private boolean _ok; - private java.io.IOException _ex; - } - public IceInternal.Transceiver connect(int timeout) { + // + // The plugin may not be fully initialized. + // + Context ctx = _instance.clientContext(); + if(_instance.networkTraceLevel() >= 2) { String s = "trying to establish ssl connection to " + toString(); @@ -167,7 +33,7 @@ final class ConnectorI implements IceInternal.Connector // if(timeout >= 0) { - ConnectThread ct = new ConnectThread(_ctx.sslContext(), _addr); + ConnectThread ct = new ConnectThread(ctx.sslContext(), _addr); ct.start(); fd = ct.getFd(timeout == 0 ? 1 : timeout); if(fd == null) @@ -177,13 +43,24 @@ final class ConnectorI implements IceInternal.Connector } else { - javax.net.SocketFactory factory = _ctx.sslContext().getSocketFactory(); + javax.net.SocketFactory factory = ctx.sslContext().getSocketFactory(); fd = (javax.net.ssl.SSLSocket)factory.createSocket(_addr.getAddress(), _addr.getPort()); } fd.setUseClientMode(true); - String[] cipherSuites = _ctx.filterCiphers(fd.getSupportedCipherSuites(), fd.getEnabledCipherSuites()); + String[] cipherSuites = ctx.filterCiphers(fd.getSupportedCipherSuites(), fd.getEnabledCipherSuites()); + try + { + fd.setEnabledCipherSuites(cipherSuites); + } + catch(IllegalArgumentException ex) + { + Ice.SecurityException e = new Ice.SecurityException(); + e.reason = "invalid ciphersuite"; + e.initCause(ex); + throw e; + } if(_instance.securityTraceLevel() > 0) { StringBuffer s = new StringBuffer(); @@ -194,7 +71,22 @@ final class ConnectorI implements IceInternal.Connector } _logger.trace(_instance.securityTraceCategory(), s.toString()); } - fd.setEnabledCipherSuites(cipherSuites); + + String[] protocols = ctx.getProtocols(); + if(protocols != null) + { + try + { + fd.setEnabledProtocols(protocols); + } + catch(IllegalArgumentException ex) + { + Ice.SecurityException e = new Ice.SecurityException(); + e.reason = "invalid protocol"; + e.initCause(ex); + throw e; + } + } // // If a connect timeout is specified, do the SSL handshake in a separate thread. @@ -212,6 +104,13 @@ final class ConnectorI implements IceInternal.Connector { fd.startHandshake(); } + + if(!ctx.verifyPeer(fd, _host, false)) + { + Ice.SecurityException ex = new Ice.SecurityException(); + ex.reason = "outgoing connection rejected by certificate verifier"; + throw ex; + } } catch(java.net.ConnectException ex) { @@ -237,6 +136,22 @@ final class ConnectorI implements IceInternal.Connector se.initCause(ex); throw se; } + catch(javax.net.ssl.SSLException ex) + { + if(fd != null) + { + try + { + fd.close(); + } + catch(java.io.IOException e) + { + } + } + Ice.SecurityException e = new Ice.SecurityException(); + e.initCause(ex); + throw e; + } catch(java.io.IOException ex) { if(fd != null) @@ -249,6 +164,12 @@ final class ConnectorI implements IceInternal.Connector { } } + + if(IceInternal.Network.connectionLost(ex)) + { + throw new Ice.ConnectionLostException(); + } + Ice.SocketException e = new Ice.SocketException(); e.initCause(ex); throw e; @@ -274,6 +195,11 @@ final class ConnectorI implements IceInternal.Connector _logger.trace(_instance.networkTraceCategory(), s); } + if(_instance.securityTraceLevel() > 0) + { + ctx.traceConnection(fd, false); + } + return new TransceiverI(_instance, fd); } @@ -289,14 +215,153 @@ final class ConnectorI implements IceInternal.Connector ConnectorI(Instance instance, String host, int port) { _instance = instance; - _ctx = instance.clientContext(); _logger = instance.communicator().getLogger(); + _host = host; _addr = IceInternal.Network.getAddress(host, port); } + private static class ConnectThread extends Thread + { + ConnectThread(javax.net.ssl.SSLContext ctx, java.net.InetSocketAddress addr) + { + _ctx = ctx; + _addr = addr; + } + + public void + run() + { + try + { + javax.net.SocketFactory factory = _ctx.getSocketFactory(); + javax.net.ssl.SSLSocket fd = + (javax.net.ssl.SSLSocket)factory.createSocket(_addr.getAddress(), _addr.getPort()); + synchronized(this) + { + _fd = fd; + notifyAll(); + } + } + catch(java.io.IOException ex) + { + synchronized(this) + { + _ex = ex; + notifyAll(); + } + } + } + + javax.net.ssl.SSLSocket + getFd(int timeout) + throws java.io.IOException + { + javax.net.ssl.SSLSocket fd = null; + + synchronized(this) + { + while(_fd == null && _ex == null) + { + try + { + wait(timeout); + break; + } + catch(InterruptedException ex) + { + continue; + } + } + + if(_ex != null) + { + throw _ex; + } + + fd = _fd; + _fd = null; + } + + return fd; + } + + private javax.net.ssl.SSLContext _ctx; + private java.net.InetSocketAddress _addr; + private javax.net.ssl.SSLSocket _fd; + private java.io.IOException _ex; + } + + private static class HandshakeThread extends Thread + { + HandshakeThread(javax.net.ssl.SSLSocket fd) + { + _fd = fd; + _ok = false; + } + + public void + run() + { + try + { + _fd.startHandshake(); + synchronized(this) + { + _ok = true; + notifyAll(); + } + + } + catch(java.io.IOException ex) + { + synchronized(this) + { + _ex = ex; + notifyAll(); + } + } + } + + boolean + waitForHandshake(int timeout) + throws java.io.IOException + { + boolean result = false; + + synchronized(this) + { + while(!_ok && _ex == null) + { + try + { + wait(timeout); + break; + } + catch(InterruptedException ex) + { + continue; + } + } + + if(_ex != null) + { + throw _ex; + } + + result = _ok; + } + + return result; + } + + private javax.net.ssl.SSLSocket _fd; + private boolean _ok; + private java.io.IOException _ex; + } + private Instance _instance; - private Context _ctx; private Ice.Logger _logger; + private String _host; private java.net.InetSocketAddress _addr; } diff --git a/java/src/IceSSL/Context.java b/java/src/IceSSL/Context.java index 5c99702d5e9..7db3d6450eb 100644 --- a/java/src/IceSSL/Context.java +++ b/java/src/IceSSL/Context.java @@ -11,152 +11,212 @@ package IceSSL; class Context { - Context(String ciphers, String keystore, String password, String keystorePassword, String certs, - String certsPassword, java.security.SecureRandom rand) + Context(Instance instance, boolean client, javax.net.ssl.SSLContext context, java.security.SecureRandom rand) throws java.security.GeneralSecurityException { - java.util.ArrayList cipherList = new java.util.ArrayList(); + _instance = instance; + _logger = instance.communicator().getLogger(); + + final String prefix = client ? "IceSSL.Client." : "IceSSL.Server."; + Ice.Properties properties = instance.communicator().getProperties(); + String ciphers = properties.getProperty(prefix + "Ciphers"); + if(ciphers.length() > 0) { - String[] expr = ciphers.split("[ \t]+"); - for(int i = 0; i < expr.length; ++i) + parseCiphers(ciphers); + } + + // + // If the user doesn't supply an SSLContext, we need to create one based + // on property settings. + // + _ctx = context; + if(_ctx == null) + { + // + // Check for a default directory. We look in this directory for + // files mentioned in the configuration. + // + _defaultDir = properties.getProperty(prefix + "DefaultDir"); + + // + // The keystore holds private keys and associated certificates. + // + Ice.StringHolder keystorePath = new Ice.StringHolder(properties.getProperty(prefix + "Keystore")); + + // + // The password for the keys. + // + final String password = properties.getProperty(prefix + "Password"); + + // + // The password for the keystore. + // + final 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.getPropertyWithDefault(prefix + "Truststore", properties.getProperty(prefix + "Certs"))); + + // + // The password for the truststore. + // + final String truststorePassword = + properties.getPropertyWithDefault(prefix + "TruststorePassword", + properties.getProperty(prefix + "CertsPassword")); + + // + // The truststore type defaults to "JKS", but it can also be "PKCS12". + // + String truststoreType = + properties.getPropertyWithDefault(prefix + "TruststoreType", java.security.KeyStore.getDefaultType()); + + // + // Parse the enabled protocols. + // + String protocols = properties.getProperty(prefix + "Protocols"); + if(protocols.length() > 0) { - if(expr[i].equals("ALL")) + java.util.ArrayList l = new java.util.ArrayList(); + String[] arr = protocols.split("[ \t,]+"); + for(int i = 0; i < arr.length; ++i) { - if(i != 0) + String s = arr[i].toLowerCase(); + if(s.equals("ssl3") || s.equals("sslv3")) { - Ice.PluginInitializationException ex = new Ice.PluginInitializationException(); - ex.reason = "IceSSL: `ALL' must be first in cipher list `" + ciphers + "'"; - throw ex; + l.add("SSLv3"); } - _allCiphers = true; - } - else if(expr[i].equals("NONE")) - { - if(i != 0) + else if(s.equals("tls") || s.equals("tls1") || s.equals("tlsv1")) { - Ice.PluginInitializationException ex = new Ice.PluginInitializationException(); - ex.reason = "IceSSL: `NONE' must be first in cipher list `" + ciphers + "'"; - throw ex; + l.add("TLSv1"); + } + else + { + Ice.PluginInitializationException e = new Ice.PluginInitializationException(); + e.reason = "IceSSL: unrecognized protocol `" + arr[i] + "'"; + throw e; } - _noCiphers = true; } - else + _protocols = new String[l.size()]; + l.toArray(_protocols); + } + + // + // Collect the key managers. + // + javax.net.ssl.KeyManager[] keyManagers = null; + if(keystorePath.value.length() > 0) + { + if(!checkPath(keystorePath, false)) { - CipherExpression ce = new CipherExpression(); - String exp = expr[i]; - if(exp.charAt(0) == '!') + 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) { - 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; - } + passwordChars = keystorePassword.toCharArray(); } - if(exp.charAt(0) == '(') - { - if(!exp.endsWith(")")) - { - Ice.PluginInitializationException ex = new Ice.PluginInitializationException(); - ex.reason = "IceSSL: invalid cipher expression `" + exp + "'"; - throw ex; - } + java.io.BufferedInputStream bis = + new java.io.BufferedInputStream(new java.io.FileInputStream(keystorePath.value)); + keys.load(bis, passwordChars); + } + 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; + } - 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 + String algorithm = javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm(); + javax.net.ssl.KeyManagerFactory kmf = javax.net.ssl.KeyManagerFactory.getInstance(algorithm); + kmf.init(keys, password.toCharArray()); + 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)) { - ce.cipher = exp; + Ice.PluginInitializationException e = new Ice.PluginInitializationException(); + e.reason = "IceSSL: keystore does not contain an entry with alias `" + alias + "'"; + throw e; } - cipherList.add(ce); + for(int i = 0; i < keyManagers.length; ++i) + { + keyManagers[i] = new KeyManagerI((javax.net.ssl.X509KeyManager)keyManagers[i], alias, client); + } } } - _ciphers = new CipherExpression[cipherList.size()]; - cipherList.toArray(_ciphers); - } - - final String ksType = java.security.KeyStore.getDefaultType(); - javax.net.ssl.KeyManager[] keyManagers = null; - if(keystore != null && keystore.length() > 0) - { - _keys = java.security.KeyStore.getInstance(ksType); - try + // + // Collect the trust managers. + // + javax.net.ssl.TrustManager[] trustManagers = null; + if(truststorePath.value.length() > 0) { - char[] pass = null; - if(keystorePassword != null && keystorePassword.length() > 0) + if(!checkPath(truststorePath, false)) { - pass = keystorePassword.toCharArray(); + 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(); + } - java.io.BufferedInputStream bis = - new java.io.BufferedInputStream(new java.io.FileInputStream(keystore)); - _keys.load(bis, pass); - } - catch(java.io.IOException ex) - { - Ice.PluginInitializationException e = new Ice.PluginInitializationException(); - e.reason = "IceSSL: unable to load keystore from `" + keystore + "'"; - e.initCause(ex); - throw e; - } - - String algorithm = javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm(); - javax.net.ssl.KeyManagerFactory kmf = javax.net.ssl.KeyManagerFactory.getInstance(algorithm); - kmf.init(_keys, password.toCharArray()); - keyManagers = kmf.getKeyManagers(); - } - - javax.net.ssl.TrustManager[] trustManagers = null; - if(certs != null && certs.length() > 0) - { - _certs = java.security.KeyStore.getInstance(ksType); - try - { - char[] pass = null; - if(certsPassword != null && certsPassword.length() > 0) + java.io.BufferedInputStream bis = + new java.io.BufferedInputStream(new java.io.FileInputStream(truststorePath.value)); + ts.load(bis, passwordChars); + } + catch(java.io.IOException ex) { - pass = certsPassword.toCharArray(); + Ice.PluginInitializationException e = new Ice.PluginInitializationException(); + e.reason = "IceSSL: unable to load truststore:\n" + truststorePath.value; + e.initCause(ex); + throw e; } - java.io.BufferedInputStream bis = - new java.io.BufferedInputStream(new java.io.FileInputStream(certs)); - _certs.load(bis, pass); - } - catch(java.io.IOException ex) - { - Ice.PluginInitializationException e = new Ice.PluginInitializationException(); - e.reason = "IceSSL: unable to load keystore from `" + certs + "'"; - 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(); } - String algorithm = javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm(); - javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance(algorithm); - tmf.init(_certs); - trustManagers = tmf.getTrustManagers(); + // + // Initialize the SSL context. + // + _ctx = javax.net.ssl.SSLContext.getInstance("SSL"); + _ctx.init(keyManagers, trustManagers, rand); } - - _ctx = javax.net.ssl.SSLContext.getInstance("SSL"); - _ctx.init(keyManagers, trustManagers, rand); } javax.net.ssl.SSLContext @@ -240,6 +300,165 @@ class Context return arr; } + String[] + getProtocols() + { + return _protocols; + } + + void + traceConnection(javax.net.ssl.SSLSocket fd, boolean incoming) + { + javax.net.ssl.SSLSession session = fd.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(_instance.securityTraceCategory(), msg); + } + + boolean + verifyPeer(javax.net.ssl.SSLSocket fd, String host, boolean incoming) + { + CertificateVerifier verifier = _instance.certificateVerifier(); + if(verifier != null) + { + VerifyInfo info = new VerifyInfo(); + info.incoming = incoming; + try + { + info.certs = fd.getSession().getPeerCertificates(); + } + catch(javax.net.ssl.SSLPeerUnverifiedException ex) + { + // No peer certificates. + } + info.socket = fd; + info.address = host; + if(!verifier.verify(info)) + { + if(_instance.securityTraceLevel() > 0) + { + _logger.trace(_instance.securityTraceCategory(), + (incoming ? "incoming" : "outgoing") + + " connection rejected by certificate verifier\n" + + IceInternal.Network.fdToString(fd)); + } + return false; + } + } + + return true; + } + + private void + parseCiphers(String ciphers) + { + java.util.ArrayList cipherList = new java.util.ArrayList(); + 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; @@ -247,10 +466,12 @@ class Context java.util.regex.Pattern re; } + private Instance _instance; + private Ice.Logger _logger; + private String _defaultDir; private CipherExpression[] _ciphers; private boolean _allCiphers; private boolean _noCiphers; + private String[] _protocols; private javax.net.ssl.SSLContext _ctx; - private java.security.KeyStore _keys; - private java.security.KeyStore _certs; } diff --git a/java/src/IceSSL/Instance.java b/java/src/IceSSL/Instance.java index e1dda63e598..b6494d69099 100644 --- a/java/src/IceSSL/Instance.java +++ b/java/src/IceSSL/Instance.java @@ -17,33 +17,62 @@ class Instance _securityTraceLevel = communicator.getProperties().getPropertyAsIntWithDefault("IceSSL.Trace.Security", 0); _securityTraceCategory = "Security"; - java.security.SecureRandom rand; - try - { - // - // Create a SecureRandom object. We call nextInt() in order to - // force the object to perform any time-consuming initialization tasks now. - // - rand = java.security.SecureRandom.getInstance("SHA1PRNG"); - - // - // We call nextInt() in order to force the object to perform any time-consuming - // initialization tasks now. - // - rand.nextInt(); - + // + // Initialize the plugin, unless IceSSL.DelayInit=1. + // + if(communicator.getProperties().getPropertyAsInt("IceSSL.DelayInit") == 0) + { + initialize(null, null); } - catch(java.security.GeneralSecurityException ex) - { + + // + // Register the endpoint factory. + // + _facade.addEndpointFactory(new EndpointFactoryI(this)); + } + + void + initialize(javax.net.ssl.SSLContext clientContext, javax.net.ssl.SSLContext serverContext) + { + if(_clientContext != null) + { Ice.PluginInitializationException e = new Ice.PluginInitializationException(); - e.reason = "IceSSL: unable to initialize secure PRNG"; - e.initCause(ex); + e.reason = "plugin is already initialized"; throw e; } + // + // If we have to initialize an SSLContext, we'll need a SecureRandom object. + // + java.security.SecureRandom rand = null; + if(clientContext == null || serverContext == null) + { + try + { + rand = java.security.SecureRandom.getInstance("SHA1PRNG"); + + // + // We call nextInt() in order to force the object to perform any time-consuming + // initialization tasks now. + // + rand.nextInt(); + } + catch(java.security.GeneralSecurityException ex) + { + Ice.PluginInitializationException e = new Ice.PluginInitializationException(); + e.reason = "IceSSL: unable to initialize secure PRNG"; + e.initCause(ex); + throw e; + } + } + + // + // Create the client and server contexts. We always create both, even + // if only one is used. + // try { - _clientContext = createContext("Client", rand); + _clientContext = new Context(this, true, clientContext, rand); } catch(java.security.GeneralSecurityException ex) { @@ -55,7 +84,7 @@ class Instance try { - _serverContext = createContext("Server", rand); + _serverContext = new Context(this, false, serverContext, rand); } catch(java.security.GeneralSecurityException ex) { @@ -64,8 +93,12 @@ class Instance e.initCause(ex); throw e; } + } - _facade.addEndpointFactory(new EndpointFactoryI(this)); + void + setCertificateVerifier(CertificateVerifier verifier) + { + _verifier = verifier; } Ice.Communicator @@ -107,28 +140,31 @@ class Instance Context clientContext() { + if(_clientContext == null) + { + Ice.PluginInitializationException e = new Ice.PluginInitializationException(); + e.reason = "IceSSL: plugin is not fully initialized"; + throw e; + } return _clientContext; } Context serverContext() { + if(_serverContext == null) + { + Ice.PluginInitializationException e = new Ice.PluginInitializationException(); + e.reason = "IceSSL: plugin is not fully initialized"; + throw e; + } return _serverContext; } - private Context - createContext(String mode, java.security.SecureRandom rand) - throws java.security.GeneralSecurityException - { - final String prefix = "IceSSL." + mode + "."; - Ice.Properties properties = communicator().getProperties(); - String ciphers = properties.getProperty(prefix + "Ciphers"); - String keyStore = properties.getProperty(prefix + "Keystore"); - String password = properties.getProperty(prefix + "Password"); - String keyStorePassword = properties.getProperty(prefix + "KeystorePassword"); - String certs = properties.getProperty(prefix + "Certs"); - String certsPassword = properties.getProperty(prefix + "CertsPassword"); - return new Context(ciphers, keyStore, password, keyStorePassword, certs, certsPassword, rand); + CertificateVerifier + certificateVerifier() + { + return _verifier; } private IceInternal.ProtocolPluginFacade _facade; @@ -136,4 +172,5 @@ class Instance private String _securityTraceCategory; private Context _clientContext; private Context _serverContext; + private CertificateVerifier _verifier; } diff --git a/java/src/IceSSL/KeyManagerI.java b/java/src/IceSSL/KeyManagerI.java new file mode 100644 index 00000000000..4a6d76e33cc --- /dev/null +++ b/java/src/IceSSL/KeyManagerI.java @@ -0,0 +1,74 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2006 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 KeyManagerI implements javax.net.ssl.X509KeyManager +{ + KeyManagerI(javax.net.ssl.X509KeyManager del, String alias, boolean client) + { + _delegate = del; + _alias = alias; + _client = client; + } + + public String + chooseClientAlias(String[] keyType, java.security.Principal[] issuers, java.net.Socket socket) + { + if(_client) + { + return _alias; + } + else + { + return _delegate.chooseClientAlias(keyType, issuers, socket); + } + } + + public String + chooseServerAlias(String keyType, java.security.Principal[] issuers, java.net.Socket socket) + { + if(!_client) + { + return _alias; + } + else + { + return _delegate.chooseServerAlias(keyType, issuers, socket); + } + } + + 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; + private boolean _client; +} diff --git a/java/src/IceSSL/Plugin.java b/java/src/IceSSL/Plugin.java new file mode 100644 index 00000000000..a4316453f62 --- /dev/null +++ b/java/src/IceSSL/Plugin.java @@ -0,0 +1,28 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2006 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 +{ + // + // Manually initialize the plugin. The application must set the property + // IceSSL.DelayInit=1 in order to use this method. + // + // It is legal to pass null as either argument, in which case the plugin + // obtains its certificates as directed by configuration properties. + // + void initialize(javax.net.ssl.SSLContext clientContext, javax.net.ssl.SSLContext serverContext); + + // + // Establish the certificate verifier object. This should be + // done before any connections are established. + // + void setCertificateVerifier(CertificateVerifier verifier); +} diff --git a/java/src/IceSSL/PluginI.java b/java/src/IceSSL/PluginI.java index 431847271cd..bba197882e3 100644 --- a/java/src/IceSSL/PluginI.java +++ b/java/src/IceSSL/PluginI.java @@ -9,7 +9,7 @@ package IceSSL; -public class PluginI extends Ice.LocalObjectImpl implements Ice.Plugin +class PluginI extends Ice.LocalObjectImpl implements Plugin { public PluginI(Ice.Communicator communicator) @@ -22,5 +22,17 @@ public class PluginI extends Ice.LocalObjectImpl implements Ice.Plugin { } + public void + initialize(javax.net.ssl.SSLContext clientContext, javax.net.ssl.SSLContext serverContext) + { + _instance.initialize(clientContext, serverContext); + } + + public void + setCertificateVerifier(CertificateVerifier verifier) + { + _instance.setCertificateVerifier(verifier); + } + private Instance _instance; } diff --git a/java/src/IceSSL/VerifyInfo.java b/java/src/IceSSL/VerifyInfo.java new file mode 100644 index 00000000000..95e603ada3a --- /dev/null +++ b/java/src/IceSSL/VerifyInfo.java @@ -0,0 +1,45 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2006 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; + +// +// VerifyInfo contains information that may be of use to a +// CertificateVerifier implementation. +// +public class VerifyInfo +{ + // + // A value of true indicates an incoming (server) connection. + // + public boolean incoming; + + // + // The peer's certificate chain, which can be null if the peer + // is unverified. + // + public java.security.cert.Certificate[] certs; + + // + // The SSL socket that is being authenticated. + // + public javax.net.ssl.SSLSocket socket; + + // + // The address of the server as specified by the proxy's + // endpoint. For example, in the following proxy: + // + // identity:ssl -h www.server.com -p 10000 + // + // the value of address is "www.server.com". + // + // The value is an empty string for incoming connections. + // + public String address; +} |