lundi, 2 août 2010

Java and certificate's mess : blind SSL factory for commons httpClient

There are parts of Java which are not very "quick development" oriented. The side I want to illustrate here is dealing with SSL.

When the application is in early development stage, we don't want to bother with stuff like valid certificate. We don't want to get stuck for days because we haven't received valid development SSL certificate from Verisign or other "more trusted than me" monopoles. We just want to activate the "--no-check-certificate" à la wget, or "CURLOPT_SSL_VERIFYPEER" à la PHP.

Adding a new certificate in the cacert file is not so complicated, but it is not always possible on every computers (should developers have administrators rights on their computers ?), or break the debian pakage integrity and risk to be overridden at the next JRE update.

No, for development phase, a blind SSL factory is a good point for productivity. Simply DO NOT FORGET TO REMOVE IT in the integration or pre-production phase. Having valid and trusted SSL certificate in production is NOT an optional thing.

What is a blind SSL factory

A blind SSL factory is simply an SSL factory which will validate any certificate. Expirated, selfsigned, but also man-in-the-middled or forged certificates will be trusted.

Mike McKinney already explained how to create a blind SSL factory for LDAP connexions.

I will leverage his explanations to enable blind SSL factory for commons httpClient.

The start point is to create a TrustManager which will trust any certificate, and then give this TrustManager to the SSL factory. This will result in a blind SSL factory :)

Anonymous class that implements X509TrustManager :

class BlindX509TrustManager implements X509TrustManager
{
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
public void checkClientTrusted(final X509Certificate[] c, final String a)
{
}
public void checkServerTrusted(final X509Certificate[] c, final String a)
{
}
}
Initializing the SSLContext to retrieve a SocketFactory :
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new BlindX509TrustManager() }, new java.security.SecureRandom());
SocketFactory blindFactory = sc.getSocketFactory();
And finally the BlindSSLProtocolSocketFactory which implements the needed ProtocolSocketFactory of httpClient :
public class BlindSSLProtocolSocketFactory implements SecureProtocolSocketFactory
{
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException,
UnknownHostException
{
return blindFactory.createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params)
throws IOException, UnknownHostException, ConnectTimeoutException
{
if (params == null)
{
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
if (timeout == 0)
{
return createSocket(host, port, localAddress, localPort);
}
else
{
return ControllerThreadSocketFactory.createSocket(this, host, port, localAddress, localPort, timeout);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
return blindFactory.createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException
{
return ((SSLSocketFactory) blindFactory).createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj)
{
return obj != null && obj.getClass().equals(getClass());
}
public int hashCode()
{
return getClass().hashCode();
}
}
Everything tied up together, put into the classpath of the application, will override the https protocol with the BlindSSLProtocolSocketFactory and thus allow your code to connect any certificate !
public class BlindSSLProtocolSocketFactory implements SecureProtocolSocketFactory
{
private static final Log LOG = LogFactory.getLog(BlindSSLProtocolSocketFactory.class);
public BlindSSLProtocolSocketFactory()
{
blindFactory = createBlindSocketFactory();
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException,
UnknownHostException
{
return blindFactory.createSocket(host, port, clientHost, clientPort);
}

public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params)
throws IOException, UnknownHostException, ConnectTimeoutException
{
if (params == null)
{
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
if (timeout == 0)
{
return createSocket(host, port, localAddress, localPort);
}
else
{
return ControllerThreadSocketFactory.createSocket(this, host, port, localAddress, localPort, timeout);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
return blindFactory.createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException
{
return ((SSLSocketFactory) blindFactory).createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj)
{
return obj != null && obj.getClass().equals(getClass());
}
public int hashCode()
{
return getClass().hashCode();
}
private SocketFactory blindFactory = null;
private static SocketFactory createBlindSocketFactory()
{
try
{
SSLContext context = SSLContext.getInstance("SSL");
context.init(null, new TrustManager[] { new BlindX509TrustManager() }, null);
return context.getSocketFactory();
}
catch (Exception e)
{
LOG.error(e.getMessage(), e);
throw new HttpClientError(e.toString());
}
}
}
And to regirster the new protocol as the default https protocol :
Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) new BlindSSLProtocolSocketFactory(), 443));

Please note that if you want only access a selfsigned certificate you have better to use EasySSLProtocolSocketFactory from httpClient contibs. It still validate the selfsigned certificate, so expired or bad certificate will be denied.

2 commentaires:

Cédric Luthi a dit…

[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[myWebserverURL host]];

I love Cocoa! ;-)

KillerWhile a dit…

I know your Mac OS biased mind, but I need to confess it sounds really good :)