/*
* Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
* See LICENCE file for licensing information.
*/
package eu.emi.security.authn.x509.impl;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import eu.emi.security.authn.x509.X509CertChainValidator;
import eu.emi.security.authn.x509.X509Credential;
import eu.emi.security.authn.x509.helpers.ssl.HostnameToCertificateChecker;
import eu.emi.security.authn.x509.helpers.ssl.SSLTrustManager;
/**
* Simple utility allowing programmers to quickly create SSL socket factories
* using {@link X509CertChainValidator}.
*
* @author K. Benedyczak
*/
public class SocketFactoryCreator
{
static
{
CertificateUtils.configureSecProvider();
}
/**
* Creates a SSL trustmanager which uses the provided validator.
* @param v validator to use for certificates validation
* @return ready to use TrustManager
*/
public static X509TrustManager getSSLTrustManager(X509CertChainValidator v)
{
return new SSLTrustManager(v);
}
/**
* Low level interface. It can be used to get {@link SSLContext} object initialized with the
* provided credential and validator.
* @param c credential to use for the created sockets. If null, then anonymous socket will be created,
* what is useful only for client side.
* @param v validator to use for certificates validation
* @param r implementation providing random numbers
* @return initialized {@link SSLContext} object
*/
public static SSLContext getSSLContext(X509Credential c,
X509CertChainValidator v, SecureRandom r)
{
KeyManager[] kms = c == null ? null : new KeyManager[] {c.getKeyManager()};
SSLTrustManager tm = new SSLTrustManager(v);
SSLContext sslCtx;
try
{
sslCtx = SSLContext.getInstance("TLS");
} catch (NoSuchAlgorithmException e)
{
throw new RuntimeException("The TLS protocol is unsupported by the JDK, " +
"a serious installation problem?", e);
}
try
{
sslCtx.init(kms, new TrustManager[] {tm}, r);
} catch (KeyManagementException e)
{
throw new RuntimeException("Shouldn't happen - SSLContext can't be initiated?", e);
}
return sslCtx;
}
/**
* Returns an {@link SSLServerSocketFactory} configured to check
* client certificates with a provided validator. Server socket will use
* the provided credentials.
* @param c credential to use for the server socket
* @param v validator to use for client's validation
* @param r implementation providing random numbers
* @return configured {@link SSLServerSocketFactory}
*/
public static SSLServerSocketFactory getServerSocketFactory(X509Credential c,
X509CertChainValidator v, SecureRandom r)
{
return getSSLContext(c, v, r).getServerSocketFactory();
}
/**
* Same as {@link #getServerSocketFactory(X509Credential, X509CertChainValidator, SecureRandom)}
* using {@link SecureRandom} implementation as the last argument. Note that this
* method might block if the machine has not enough system entropy. It is not suggested to use
* this method for setting up automatic test environments, however it is suitable for production setups.
* @param c credential to use for the server socket
* @param v validator to use for client's validation
* @return configured {@link SSLServerSocketFactory}
*/
public static SSLServerSocketFactory getServerSocketFactory(X509Credential c,
X509CertChainValidator v)
{
return getServerSocketFactory(c, v, new SecureRandom());
}
/**
* Returns an {@link SSLSocketFactory} configured to check
* servers' certificates with a provided validator. Client socket will use
* the provided credentials.
* @param c credential to use for the client socket
* @param v validator to use for server's validation
* @param r implementation providing random numbers
* @return configured {@link SSLSocketFactory}
*/
public static SSLSocketFactory getSocketFactory(X509Credential c, X509CertChainValidator v, SecureRandom r)
{
return getSSLContext(c, v, r).getSocketFactory();
}
/**
* Same as {@link #getSocketFactory(X509Credential, X509CertChainValidator, SecureRandom)}
* using {@link SecureRandom} implementation as the last argument. Note that this
* method might block if the machine has not enough system entropy. It is not suggested to use
* this method for setting up automatic test environments, however it is suitable for production setups.
* @param c credential to use for the client socket
* @param v validator to use for server's validation
* @return configured {@link SSLSocketFactory}
*/
public static SSLSocketFactory getSocketFactory(X509Credential c, X509CertChainValidator v)
{
return getSocketFactory(c, v, new SecureRandom());
}
/**
* This method, invoked on an initialized SSL socket will perform the initial handshake (if necessary)
* and then check if the peer's hostname is matching its certificate. The reaction to a mismatch
* must be handled by the provided callback.
*
* @param socket socket to be checked
* @param callback used when there is mismatch.
* @throws SSLPeerUnverifiedException if the peer was not verified
*/
public static void connectWithHostnameChecking(SSLSocket socket, HostnameMismatchCallback callback)
throws SSLPeerUnverifiedException
{
HostnameToCertificateChecker checker = new HostnameToCertificateChecker();
SSLSession session = socket.getSession();
X509Certificate cert;
Certificate[] serverChain = session.getPeerCertificates();
if (serverChain == null || serverChain.length == 0)
throw new IllegalStateException("JDK BUG? Got null or empty peer certificate array");
if (!(serverChain[0] instanceof X509Certificate))
throw new ClassCastException("Peer certificate should be " +
"an X.509 certificate, but is " + serverChain[0].getClass().getName());
cert = (X509Certificate) serverChain[0];
String hostname = socket.getInetAddress().getHostName();
try
{
if (!checker.checkMatching(hostname, cert))
callback.nameMismatch(socket, cert, hostname);
} catch (Exception e)
{
throw new IllegalStateException("Can't check peer's address against its certificate", e);
}
}
}