/*
* $Id$
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.commons.connector.http.transport;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Security;
import java.util.logging.Logger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.jcoderz.commons.connector.InitializingSslFailedException;
import org.jcoderz.commons.util.Assert;
/**
* Factory used to create SSLSocket.
*/
public final class SslSocketFactory
implements SecureProtocolSocketFactory
{
/** The class name used for logging */
private static final String CLASSNAME = SslSocketFactory.class.getName();
/** The logger in use */
private static final Logger logger = Logger.getLogger(CLASSNAME);
/** Cache for SSLSocketFactories */
private static final ThreadLocal SSL_SOCKET_FACTORIES = new ThreadLocal();
private final String mKeyStoreLocation;
private final String mKeyStorePassword;
private final String mTrustStoreLocation;
private final String mTrustStorePassword;
private final String mKeyAlias;
private final String mKeyPassword;
private KeyStore mKeyStore = null;
private KeyStore mTrustStore = null;
/**
* Constructor.
* @param keyStoreLocation
* the location of the key store in use
* @param keyStorePassword
* the password of the key store in use
* @param trustStoreLocation
* the location of the trust store in use
* @param trustStorePassword
* the password of the trust store in use
* @param keyAlias
* the alias of the key in use
* @param keyPassword
* the password of the key in use
*/
public SslSocketFactory (
String keyStoreLocation,
String keyStorePassword,
String trustStoreLocation,
String trustStorePassword,
String keyAlias,
String keyPassword)
{
Assert.notNull(keyStoreLocation, "keyStoreLocation");
Assert.notNull(keyStorePassword, "keyStorePassword");
Assert.notNull(trustStoreLocation, "trustStoreLocation");
Assert.notNull(trustStorePassword, "trustStorePassword");
Assert.notNull(keyAlias, "keyAlias");
Assert.notNull(keyPassword, "keyPassword");
mKeyStoreLocation = keyStoreLocation;
mKeyStorePassword = keyStorePassword;
mTrustStoreLocation = trustStoreLocation;
mTrustStorePassword = trustStorePassword;
mKeyAlias = keyAlias;
mKeyPassword = keyPassword;
}
/**
* Constructor.
* @param keyStore the keystore to use
* @param trustStore the truststore in use
* @param keyAlias the alias of the key in use
* @param keyPassword the password of the key in use
*/
public SslSocketFactory (
KeyStore keyStore,
KeyStore trustStore,
String keyAlias,
String keyPassword)
{
Assert.notNull(keyStore, "keyStore");
Assert.notNull(keyAlias, "keyAlias");
Assert.notNull(keyPassword, "keyPassword");
mKeyStore = keyStore;
mTrustStore = trustStore;
mKeyAlias = keyAlias;
mKeyPassword = keyPassword;
mKeyStoreLocation = null;
mKeyStorePassword = null;
mTrustStoreLocation = null;
mTrustStorePassword = null;
}
/**
* Gets a CtsKeyManager as a specific X509KeyManager for the alias in use.
*
* @return KeyManager[] contains only one KeyManager for the alias in use
* @throws GeneralSecurityException
* in case of an keystore failure
*/
private KeyManager[] getKeyManagers ()
{
final KeyManager manager
= new HttpsKeyManager(null, mKeyStore, mKeyAlias, mKeyPassword);
final KeyManager[] managers = {manager};
return managers;
}
/**
* Gets the TrustManagers for the specified algorithm.
*
* @return TrustManager[] the TrustManger for the algorithm
* @throws GeneralSecurityException
* in case of an keystore failure
*/
private TrustManager[] getTrustManagers ()
throws GeneralSecurityException
{
final TrustManagerFactory tmf
= TrustManagerFactory.getInstance(
Security.getProperty("ssl.TrustManagerFactory.algorithm"));
tmf.init(mTrustStore);
return tmf.getTrustManagers();
}
private SSLSocketFactory getSslSocketFactory ()
throws IOException, FileNotFoundException
{
SSLSocketFactory result;
result = (SSLSocketFactory) SSL_SOCKET_FACTORIES.get();
if (result == null)
{
logger.fine("Creating new SSL_SOCKET_FACTORY for Thread.");
SSLContext ctx = null;
try
{
// loading keystore/truststore if necessary (test mode only!!)
if (mKeyStore == null)
{
logger.finest("Loading keystore from file system - "
+ mKeyStoreLocation);
final char[] passphraseKeyStore
= mKeyStorePassword.toCharArray();
mKeyStore = KeyStore.getInstance("JKS");
mKeyStore.load(new FileInputStream(
mKeyStoreLocation), passphraseKeyStore);
}
if (mTrustStore == null)
{
logger.finest("Loading truststore from file system - "
+ mTrustStoreLocation);
final char[] passphraseTrustStore
= mTrustStorePassword.toCharArray();
mTrustStore = KeyStore.getInstance("JKS");
mTrustStore.load(new FileInputStream(
mTrustStoreLocation), passphraseTrustStore);
}
if (!mKeyStore.containsAlias(mKeyAlias))
{
final String reason
= "Keystore does not contain key for alias "
+ "<" + mKeyAlias + ">";
final InitializingSslFailedException sse
= new InitializingSslFailedException(reason);
throw sse;
}
ctx = SSLContext.getInstance("TLS");
ctx.init(getKeyManagers(), getTrustManagers(), null);
}
catch (GeneralSecurityException gse)
{
final String reason = gse.getMessage();
final InitializingSslFailedException sse
= new InitializingSslFailedException(reason, gse);
throw sse;
}
result = ctx.getSocketFactory();
SSL_SOCKET_FACTORIES.set(result);
}
return result;
}
/** {@inheritDoc} */
public Socket createSocket (
String host, int port, InetAddress localAddress ,
int localPort, HttpConnectionParams params)
throws IOException, UnknownHostException, ConnectTimeoutException
{
// This is an IBM JSSE workaround: SSLSocket.connect() does not
// connect the socket in the IBM JSSE implementation, so we
// first connect a plain TCP socket and then layer it with an
// SSL Socket.
final Socket tcpSock = new Socket();
final SocketAddress endPoint = new InetSocketAddress(host, port);
tcpSock.connect(endPoint, params.getConnectionTimeout());
final Socket sock
= getSslSocketFactory().createSocket(tcpSock, host, port, true);
return sock;
}
/** {@inheritDoc} */
public Socket createSocket (String host, int port)
throws IOException, UnknownHostException
{
throw new UnsupportedOperationException("Method not supported");
}
/** {@inheritDoc} */
public Socket createSocket (
Socket socket, String host, int port, boolean autoClose)
{
throw new UnsupportedOperationException("Method not supported");
}
/** {@inheritDoc} */
public Socket createSocket (
String arg0, int arg1, InetAddress arg2, int arg3)
throws IOException, UnknownHostException
{
throw new UnsupportedOperationException("Method not supported");
}
}