/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.remoting.security;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.InvalidParameterException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
/**
* This builds SSL server socket factories and SSL socket factories.<p/>
* The main methods are createSSLServerSocketFactory() and createSSLSocketFactory().
* By default, these methods will use SSLServerSocketFactory.getDefault() and
* SSLSocketFactory.getDefault() and will require the proper system properties to
* be set. To use a custom configuration, will need to set either the useSSLServerSocketFactory or useSSLSocketFactory
* properties to be false.<p/>
* Some common errors seen are:<p/>
* <p/>
* 1. javax.net.ssl.SSLException: No available certificate corresponds to the SSL cipher suites which are enabled <p/>
* <p/>
* The 'javax.net.ssl.keyStore' system property has not been set and are using the default SSLServerSocketFactory.<p/>
* <p/>
* 2. java.net.SocketException: Default SSL context init failed: Cannot recover key <p/>
* <p/>
* The 'javax.net.ssl.keyStorePassword' system property has not been set and are using the default SSLServerSocketFactory. <p/>
* <p/>
* 3. java.io.IOException: Can not create SSL Server Socket Factory due to the url to the key store not being set. <p/>
* <p/>
* The default SSLServerSocketFactory is NOT being used (so custom configuration for the server socket factory) and the key store url has not been set. <p/>
* <p/>
* 4. java.lang.IllegalArgumentException: password can't be null <p/>
* <p/>
* The default SSLServerSocketFactory is NOT being used (so custom configuration for the server socket factory) and the key store password has not been set. <p/>
*
* @author <a href="mailto:tom.elrod@jboss.com">Tom Elrod</a>
*/
public class SSLSocketBuilder implements SSLSocketBuilderMBean
{
/**
* Value is TLS (Transport Layer Security).
*/
public static final String DEFAULT_SECURE_SOCKET_PROTOCOL = "TLS";
/**
* Value is SunX509.
*/
public static final String DEFAULT_KEY_MANAGEMENT_ALGORITHM = "SunX509";
/**
* Value is JKS
*/
public static final String DEFAULT_KEY_STORE_TYPE = "JKS";
/**
* Defaults to DEFAULT_SECURE_SOCKET_PROTOCOL. Some acceptable values are TLS, SSL, and SSLv3.
*/
private String secureSocketProtocol = DEFAULT_SECURE_SOCKET_PROTOCOL;
/**
* Defaults to DEFAULT_KEY_MANAGEMENT_ALGORITHM.
*/
private String keyManagementAlgorithm = DEFAULT_KEY_MANAGEMENT_ALGORITHM;
/**
* Defaults to DEFAULT_KEY_STORE_TYPE. Some acceptable values are JKS (Java Keystore - Sun's keystore format),
* JCEKS (Java Cryptography Extension keystore - More secure version of JKS), and
* PKCS12 (Public-Key Cryptography Standards #12 keystore - RSA's Personal Information Exchange Syntax Standard).
* These are not case sensitive.
*/
private String keyStoreType = DEFAULT_KEY_STORE_TYPE;
private boolean useSSLServerSocketFactory = true;
private boolean useSSLSocketFactory = true;
private char[] keyStorePassword = null;
private char[] keyPassword = null;
private URL keyStoreURL = null;
private URL trustStoreURL = null;
private boolean useClientMode;
public SSLSocketBuilder()
{
}
/**
* Will indicate if should use the SSLServerSocketFactory.getDefault() for getting the ServerSocketFactory
* to use (when calling createSSLServerSocketFactory()).
* If true, will allow for setting key store location (via javax.net.ssl.keyStore system property) and
* setting of the key store password (via javax.net.ssl.keyStorePassword system property) and no other
* configuration is needed (none of the other setters will need to be called and are in fact ignored). If set to
* false, will allow the custom setting of secure socket protocol, key management algorithm, key store type,
* key store url, key store password, and key password.<p/>
* The default value is true.<p/>
* <b>NOTE: If this is not explicitly set to false, no customizations can be made and the default implementation
* provided by the JVM vendor being used will be executed.</b>
*
* @param shouldUse
*/
public void setUseSSLServerSocketFactory(boolean shouldUse)
{
this.useSSLServerSocketFactory = shouldUse;
}
/**
* Return whether SSLServerSocketFactory.getDefault() will be used or not. See setUseSSLServerSocketFactory() for more
* information on what this means.
*
* @return
*/
public boolean getUseSSLServerSocketFactory()
{
return useSSLServerSocketFactory;
}
/**
* Will indicate if should use the SSLSocketFactory.getDefault() for getting the SocketFactory
* to use (when calling createSSLSocketFactory()).
* If true, will allow for setting trust store location (via Djavax.net.ssl.trustStore system property) and no other
* configuration is needed (none of the other setters will need to be called and are in fact ignored). If set to
* false, will allow the custom setting of secure socket protocol, key management algorithm, key store type,
* ant trust store url.<p/>
* The default value is true.<p/>
* <b>NOTE: If this is not explicitly set to false, no customizations can be made and the default implementation
* provided by the JVM vendor being used will be executed.</b>
*
* @param shouldUse
*/
public void setUseSSLSocketFactory(boolean shouldUse)
{
this.useSSLSocketFactory = shouldUse;
}
/**
* Return whether SSLSocketFactory.getDefault() will be used or not. See setUseSSLSocketFactory() for more
* information on what this means.
*
* @return
*/
public boolean getUseSSLSocketFactory()
{
return useSSLSocketFactory;
}
/**
* The protocol for the SSLContext. Some acceptable values are TLS, SSL, and SSLv3.
* Defaults to DEFAULT_SECURE_SOCKET_PROTOCOL.
*/
public String getSecureSocketProtocol()
{
return secureSocketProtocol;
}
/**
* The protocol for the SSLContext. Some acceptable values are TLS, SSL, and SSLv3.
* Defaults to DEFAULT_SECURE_SOCKET_PROTOCOL.
*/
public void setSecureSocketProtocol(String secureSocketProtocol)
{
this.secureSocketProtocol = secureSocketProtocol;
}
/**
* The algorithm for the key manager factory.
* Defaults to DEFAULT_KEY_MANAGEMENT_ALGORITHM.
*/
public String getKeyManagementAlgorithm()
{
return keyManagementAlgorithm;
}
/**
* The algorithm for the key manager factory.
* Defaults to DEFAULT_KEY_MANAGEMENT_ALGORITHM.
*/
public void setKeyManagementAlgorithm(String keyManagementAlgorithm)
{
this.keyManagementAlgorithm = keyManagementAlgorithm;
}
/**
* The type to be used for the key store.
* Defaults to DEFAULT_KEY_STORE_TYPE. Some acceptable values are JKS (Java Keystore - Sun's keystore format),
* JCEKS (Java Cryptography Extension keystore - More secure version of JKS), and
* PKCS12 (Public-Key Cryptography Standards #12 keystore - RSA's Personal Information Exchange Syntax Standard).
* These are not case sensitive.
*/
public String getKeyStoreType()
{
return keyStoreType;
}
/**
* The type to be used for the key store.
* Defaults to DEFAULT_KEY_STORE_TYPE. Some acceptable values are JKS (Java Keystore - Sun's keystore format),
* JCEKS (Java Cryptography Extension keystore - More secure version of JKS), and
* PKCS12 (Public-Key Cryptography Standards #12 keystore - RSA's Personal Information Exchange Syntax Standard).
* These are not case sensitive.
*/
public void setKeyStoreType(String keyStoreType)
{
this.keyStoreType = keyStoreType;
}
/**
* Sets the password to use for the key store. This only needs to be set if setUseSSLServerSocketFactory() is set
* to false (otherwise will be ignored). The value passed will also be used for the key password if it is not
* explicitly set.
*
* @param passphrase
*/
public void setKeyStorePassword(String passphrase)
{
if(passphrase != null && passphrase.length() > 0)
{
keyStorePassword = passphrase.toCharArray();
}
else
{
throw new InvalidParameterException("Must enter a non null key store passphrase with at least one character.");
}
}
/**
* Sets the password to use for the keys within the key store. This only needs to be set if setUseSSLServerSocketFactory()
* is set to false (otherwise will be ignored). If this value is not set, but the key store password is, it will use
* that value for the key password.
*
* @param passphrase
*/
public void setKeyPassword(String passphrase)
{
if(passphrase != null && passphrase.length() > 0)
{
keyPassword = passphrase.toCharArray();
}
else
{
throw new InvalidParameterException("Must enter a non null key passphrase with at least one character.");
}
}
/**
* Determines whether factories returned by SSLSocketBuilder create Sockets/ServerSockets in client or server mode.
*
* @param useClientMode
*/
public void setUseClientMode(boolean useClientMode)
{
this.useClientMode = useClientMode;
}
/**
* Will create a SSLServerSocketFactory. If the useSSLServerSocketFactory property is set to true (which is the default),
* it will use SSLServerSocketFactory.getDefault() to get the server socket factory. Otherwise, if property is false,
* will use all the other custom properties that have been set to create a custom server socket factory.
*
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws CertificateException
* @throws UnrecoverableKeyException
* @throws KeyManagementException
*/
public ServerSocketFactory createSSLServerSocketFactory()
throws IOException, NoSuchAlgorithmException, KeyStoreException,
CertificateException, UnrecoverableKeyException, KeyManagementException
{
ServerSocketFactory ssf = null;
if(useSSLServerSocketFactory)
{
ssf = SSLServerSocketFactory.getDefault();
}
else
{
ssf = createCustomServerSocketFactory();
}
return ssf;
}
/**
* This is the full on custom ssl server socket configuration. The only thing not allowed at this point
* is the changing of providers. Everything else, including the protocol, algorithm, and key store type
* can be customized.
*
* @return
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws IOException
* @throws CertificateException
* @throws UnrecoverableKeyException
* @throws KeyManagementException
*/
private ServerSocketFactory createCustomServerSocketFactory()
throws NoSuchAlgorithmException, KeyStoreException, IOException,
CertificateException, UnrecoverableKeyException, KeyManagementException
{
ServerSocketFactory ssf = null;
SSLContext sslContext = SSLContext.getInstance(secureSocketProtocol);
KeyManagerFactory keyMgrFactory = getKeyManagerFactory();
KeyManager[] keyManagers = keyMgrFactory.getKeyManagers();
sslContext.init(keyManagers, null, null);
ssf = sslContext.getServerSocketFactory();
if (useClientMode)
return new UserModeSSLServerSocketFactory((SSLServerSocketFactory) ssf);
return ssf;
}
/**
* Will create a SSLSocketFactory. If the useSSLSocketFactory property is set to true (which is the default),
* it will use SSLSocketFactory.getDefault() to get the socket factory. Otherwise, if property is false,
* will use all the other custom properties that have been set to create a custom server socket factory.
*
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws CertificateException
* @throws KeyManagementException
*/
public SocketFactory createSSLSocketFactory()
throws IOException, NoSuchAlgorithmException, KeyStoreException,
CertificateException, KeyManagementException
{
SocketFactory sf = null;
if(useSSLSocketFactory)
{
sf = SSLSocketFactory.getDefault();
}
else
{
sf = createCustomSocketFactory();
}
return sf;
}
private SocketFactory createCustomSocketFactory()
throws NoSuchAlgorithmException, IOException, CertificateException, KeyStoreException, KeyManagementException
{
SocketFactory sf = null;
SSLContext sslContext = SSLContext.getInstance(secureSocketProtocol);
TrustManagerFactory trustMgrFactory = getTrustManagerFactory();
TrustManager[] trustManagers = trustMgrFactory.getTrustManagers();
sslContext.init(null, trustManagers, null);
sf = sslContext.getSocketFactory();
return sf;
}
private TrustManagerFactory getTrustManagerFactory()
throws NoSuchAlgorithmException, IOException, CertificateException, KeyStoreException
{
TrustManagerFactory truestMgrFactory = null;
truestMgrFactory = TrustManagerFactory.getInstance(keyManagementAlgorithm);
KeyStore keyStore = getKeyStore(trustStoreURL);
truestMgrFactory.init(keyStore);
return truestMgrFactory;
}
private KeyManagerFactory getKeyManagerFactory()
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException
{
KeyManagerFactory keyMgrFactory = null;
keyMgrFactory = KeyManagerFactory.getInstance(keyManagementAlgorithm);
KeyStore keyStore = getKeyStore(keyStoreURL);
keyMgrFactory.init(keyStore, keyPassword);
return keyMgrFactory;
}
private KeyStore getKeyStore(URL storeURL) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException
{
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
if(storeURL == null)
{
throw new IOException("Can not create SSL Server Socket Factory due to the url to the key store not being set.");
}
InputStream is = storeURL.openStream();
keyStore.load(is, keyStorePassword);
// if key password not set, just try the key store password
if(keyPassword == null || keyPassword.length > 0)
{
keyPassword = keyStorePassword;
}
return keyStore;
}
/**
* This is the url string to the key store to use. If UseSSLServerSocketFactory is true, this will be ignored
* and will use the value set by the javax.net.ssl.keyStore system property. Otherwise, if UseSSLServerSocketFactory
* is false, this must be set.
*
* @param storeURL
* @throws IOException
*/
public void setKeyStoreURL(String storeURL) throws IOException
{
this.keyStoreURL = this.validateStoreURL(storeURL);
}
/**
* This is the url string to the trust store to use. If UseSSLSocketFactory is true, this will be ignored
* and will use the value set by the javax.net.ssl.trustStore system property. Otherwise, if UseSSLSocketFactory
* is false, this must be set.
*
* @param storeURL
* @throws IOException
*/
public void setTrustStoreURL(String storeURL) throws IOException
{
this.trustStoreURL = this.validateStoreURL(storeURL);
}
private URL validateStoreURL(String storeURL) throws IOException
{
URL url = null;
// First see if this is a URL
try
{
url = new URL(storeURL);
}
catch(MalformedURLException e)
{
// Not a URL or a protocol without a handler
}
// Next try to locate this as file path
if(url == null)
{
File tst = new File(storeURL);
if(tst.exists() == true)
{
url = tst.toURL();
}
}
// Last try to locate this as a classpath resource
if(url == null)
{
ClassLoader loader = getContextClassLoader();
url = loader.getResource(storeURL);
}
// Fail if no valid key store was located
if(url == null)
{
String msg = "Failed to find url=" + storeURL + " as a URL, file or resource";
throw new MalformedURLException(msg);
}
return url;
}
/****************************************************************************************************************
* The following are just needed in order to make it a service mbean. They are just NOOPs (no implementation). *
****************************************************************************************************************/
/**
* create the service, do expensive operations etc
*/
public void create() throws Exception
{
//NOOP - nothing to do here. Just needed for mbean service api
}
/**
* start the service, create is already called
*/
public void start() throws Exception
{
//NOOP - nothing to do here. Just needed for mbean service api
}
/**
* stop the service
*/
public void stop()
{
//NOOP - nothing to do here. Just needed for mbean service api
}
/**
* destroy the service, tear down
*/
public void destroy()
{
//NOOP - nothing to do here. Just needed for mbean service api
}
// TODO: -TME getContextClassLoader() and GetTCLAction were both taken from org.jboss.security.plugins package
static ClassLoader getContextClassLoader()
{
ClassLoader loader = (ClassLoader) AccessController.doPrivileged(GetTCLAction.ACTION);
return loader;
}
private static class GetTCLAction implements PrivilegedAction
{
static PrivilegedAction ACTION = new GetTCLAction();
public Object run()
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return loader;
}
}
private static class UserModeSSLServerSocketFactory extends ServerSocketFactory
{
SSLServerSocketFactory serverSocketFactory;
public UserModeSSLServerSocketFactory(SSLServerSocketFactory serverSocketFactory)
{
this.serverSocketFactory = serverSocketFactory;
}
public ServerSocket createServerSocket() throws IOException
{
SSLServerSocket ss = (SSLServerSocket) serverSocketFactory.createServerSocket();
ss.setUseClientMode(true);
return ss;
}
public ServerSocket createServerSocket(int arg0) throws IOException
{
SSLServerSocket ss = (SSLServerSocket) serverSocketFactory.createServerSocket(arg0);
ss.setUseClientMode(true);
return ss;
}
public ServerSocket createServerSocket(int arg0, int arg1) throws IOException
{
SSLServerSocket ss = (SSLServerSocket) serverSocketFactory.createServerSocket(arg0, arg1);
ss.setUseClientMode(true);
return ss;
}
public ServerSocket createServerSocket(int arg0, int arg1, InetAddress arg2) throws IOException
{
SSLServerSocket ss = (SSLServerSocket) serverSocketFactory.createServerSocket(arg0, arg1, arg2);
ss.setUseClientMode(true);
return ss;
}
public boolean equals(Object obj)
{
return serverSocketFactory.equals(obj);
}
public String[] getDefaultCipherSuites()
{
return serverSocketFactory.getDefaultCipherSuites();
}
public String[] getSupportedCipherSuites()
{
return serverSocketFactory.getSupportedCipherSuites();
}
public int hashCode()
{
return serverSocketFactory.hashCode();
}
public String toString()
{
return serverSocketFactory.toString();
}
}
}