HttpClient to Self Signed Certificates

March 30, 2011

I needed to do a https call using HttpClient where the SSL was implemented using a self signed certificate. This is possible by setting up a new protocol handler for https.

This shouldn't be used in production, unless you understand the risks of accepting self signed certificates.

Here's the HTTPClient code to use, the following two classes are also required.

// Allow access even though certificate is self signed
Protocol lEasyHttps = new Protocol("https", new EasySslProtocolSocketFactory(), 443);
Protocol.registerProtocol("https", lEasyHttps);

// Make https call
HttpClient lClient = new HttpClient();
HttpMethod lMethod = new GetMethod(lHttpUrl);
lMethod.setQueryString(lParams);
int lCode = lClient.executeMethod(lMethod);
String lResponse = lMethod.getResponseBodyAsString();

EasySslProtocolSocketFactory

package com.company.ssl;

import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.HttpClientError;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.ControllerThreadSocketFactory;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import com.sun.net.ssl.SSLContext;
import com.sun.net.ssl.TrustManager;

/**
 * <p>
 * EasySSLProtocolSocketFactory can be used to create SSL {@link Socket}s  that accept self-signed certificates.
 * </p>
 * 
 * <p>
 * This socket factory SHOULD NOT be used for productive systems  due to security reasons, unless it is a conscious
 * decision and  you are perfectly aware of security implications of accepting  self-signed certificates
 * </p>
 * 
 * <p>
 * Example of using custom protocol socket factory for a specific host:
 * <pre>
 *     Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
 * 
 *     HttpClient client = new HttpClient();
 *     client.getHostConfiguration().setHost("localhost", 443, easyhttps);
 *     // use relative url only
 *     GetMethod httpget = new GetMethod("/");
 *     client.executeMethod(httpget);
 *     </pre>
 * </p>
 * 
 * <p>
 * Example of using custom protocol socket factory per default instead of the standard one:
 * <pre>
 *     Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
 *     Protocol.registerProtocol("https", easyhttps);
 * 
 *     HttpClient client = new HttpClient();
 *     GetMethod httpget = new GetMethod("https://localhost/");
 *     client.executeMethod(httpget);
 *     </pre>
 * </p>
 *
 * @author <a href="mailto:oleg -at- ural.ru">Oleg Kalnichevski</a><p>
 */
public class EasySslProtocolSocketFactory implements SecureProtocolSocketFactory
{    
  /** Log object for this class. */
  private static final Log LOG = LogFactory.getLog(EasySslProtocolSocketFactory.class);
  private SSLContext sslcontext = null;

  /**
   * Constructor for EasySSLProtocolSocketFactory.
   */
  public EasySslProtocolSocketFactory()
  {
    super();
  }

  /**
   * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
   */
  public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort)
    throws IOException, UnknownHostException
  {
    return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
  }

  /**
   * Attempts to get a new socket connection to the given host within the given time limit.
   * 
   * <p>
   * To circumvent the limitations of older JREs that do not support connect timeout a  controller thread is executed.
   * The controller thread attempts to create a new socket  within the given limit of time. If socket constructor does
   * not return until the  timeout expires, the controller terminates and throws an {@link ConnectTimeoutException}
   * </p>
   *
   * @param host the host name/IP
   * @param port the port on the host
   * @param localAddress the local host name/IP to bind the socket to
   * @param localPort the port on the local machine
   * @param params {@link HttpConnectionParams Http connection parameters}
   *
   * @return Socket a new socket
   *
   * @throws IOException if an I/O error occurs while creating the socket
   * @throws UnknownHostException if the IP address of the host cannot be determined
   * @throws ConnectTimeoutException DOCUMENT ME!
   * @throws IllegalArgumentException DOCUMENT ME!
   */
  public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort,
    final 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
    {
      // To be eventually deprecated when migrated to Java 1.4 or above
      return ControllerThreadSocketFactory.createSocket(this, host, port, localAddress, localPort, timeout);
    }
  }

  /**
   * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
   */
  public Socket createSocket(String host, int port) throws IOException, UnknownHostException
  {
    return getSSLContext().getSocketFactory().createSocket(host, port);
  }

  /**
   * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
   */
  public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
    throws IOException, UnknownHostException
  {
    return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
  }

  public boolean equals(Object obj)
  {
    return ((obj != null) && obj.getClass().equals(EasySslProtocolSocketFactory.class));
  }

  public int hashCode()
  {
    return EasySslProtocolSocketFactory.class.hashCode();
  }

  private static SSLContext createEasySSLContext()
  {
    try
    {
      SSLContext context = SSLContext.getInstance("SSL");
      context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null);

      return context;
    }
    catch (Exception e)
    {
      LOG.error(e.getMessage(), e);
      throw new HttpClientError(e.toString());
    }
  }

  private SSLContext getSSLContext()
  {
    if (this.sslcontext == null)
    {
      this.sslcontext = createEasySSLContext();
    }

    return this.sslcontext;
  }
}

EasyX509TrustManager

package com.company.ssl;

import com.sun.net.ssl.TrustManager;
import com.sun.net.ssl.TrustManagerFactory;
import com.sun.net.ssl.X509TrustManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * <p>
 * EasyX509TrustManager unlike default {@link X509TrustManager} accepts  self-signed certificates.
 * </p>
 * 
 * <p>
 * This trust manager SHOULD NOT be used for productive systems  due to security reasons, unless it is a conscious
 * decision and  you are perfectly aware of security implications of accepting  self-signed certificates
 * </p>
 *
 * @author <a href="mailto:adrian.sutton@ephox.com">Adrian Sutton</a>
 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>  DISCLAIMER: HttpClient developers DO NOT actively
 *         support this component. The component is provided as a reference material, which may be inappropriate to be
 *         used without additional customization.
 */
public class EasyX509TrustManager implements X509TrustManager
{
  /** Log object for this class. */
  private static final Log LOG = LogFactory.getLog(EasyX509TrustManager.class);
  private X509TrustManager standardTrustManager = null;

  /**
   * Constructor for EasyX509TrustManager.
   *
   */
  public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException
  {
    super();

    TrustManagerFactory factory = TrustManagerFactory.getInstance("SunX509");
    factory.init(keystore);

    TrustManager[] trustmanagers = factory.getTrustManagers();

    if (trustmanagers.length == 0)
    {
      throw new NoSuchAlgorithmException("SunX509 trust manager not supported");
    }

    this.standardTrustManager = (X509TrustManager) trustmanagers[0];
  }

  /**
   * @see com.sun.net.ssl.X509TrustManager#getAcceptedIssuers()
   */
  public X509Certificate[] getAcceptedIssuers()
  {
    return this.standardTrustManager.getAcceptedIssuers();
  }

  /**
   * @see com.sun.net.ssl.X509TrustManager#isClientTrusted(X509Certificate[])
   */
  public boolean isClientTrusted(X509Certificate[] certificates)
  {
    return this.standardTrustManager.isClientTrusted(certificates);
  }

  /**
   * @see com.sun.net.ssl.X509TrustManager#isServerTrusted(X509Certificate[])
   */
  public boolean isServerTrusted(X509Certificate[] certificates)
  {
    if ((certificates != null) && LOG.isDebugEnabled())
    {
      LOG.debug("Server certificate chain:");

      for (int i = 0; i < certificates.length; i++)
      {
        if (LOG.isDebugEnabled())
        {
          LOG.debug("X509Certificate[" + i + "]=" + certificates[i]);
        }
      }
    }

    if ((certificates != null) && (certificates.length == 1))
    {
      X509Certificate certificate = certificates[0];

      try
      {
        certificate.checkValidity();
      }
      catch (CertificateException e)
      {
        LOG.error(e.toString());

        return false;
      }

      return true;
    }
    else
    {
      return this.standardTrustManager.isServerTrusted(certificates);
    }
  }
}

Tags: ssl httpclient https