Howardism Musings from my Awakening Dementia
My collected thoughts flamed by hubris
Home PageSend Comment

Accepting Self-Signed SSL Certificates in Java

Every now and then I need to have a Java client make an encrypted connection to an internal server which has a self-signed SSL certificate… and if you didn't know this before, if the SSL certificate is trusted (either by a 3rd party or by storing the certificate in your own trust store), all is well … but if you wanted your code to accept the certs regardless, you have to shoot some hoops … and I since I always forget what that requires, I thought I'd drop my notes here.

The first approach is simpler, but requires each socket creation request to go through a new factory. Since often we might be using a library or other code that we can't change and want that code to accept the self-signed certificates, the second approach puts our factory in the default provider system of the Java Security system.

Approach 1: Providing a new SocketFactory

As in most of these sorts of things, there are two classes … the first is a trust manager replacement, NaiveTrustManager and then there is the code to instantiate and use it.

Class 1: NaiveTrustManager

As you can see, this TrustManager works by not throwing an exception when a self-signed (or any) SSL certificate is handed to it.

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate ;
import javax.net.ssl.X509TrustManager;

/**
 * This Trust Manager is "naive" because it trusts everyone. 
 **/
public class NaiveTrustManager implements X509TrustManager
{
  /**
   * Doesn't throw an exception, so this is how it approves a certificate.
   * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], String)
   **/
  public void checkClientTrusted ( X509Certificate[] cert, String authType )
              throws CertificateException 
  {
  }

  /**
   * Doesn't throw an exception, so this is how it approves a certificate.
   * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], String)
   **/
  public void checkServerTrusted ( X509Certificate[] cert, String authType ) 
     throws CertificateException 
  {
  }

  /**
   * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
   **/
  public X509Certificate[] getAcceptedIssuers ()
  {
    return null;  // I've seen someone return new X509Certificate[ 0 ]; 
  }
}

Class 2: Replacement for SSLSocketFactory

Next, we create a SSLSocketFactory replacement method which we call in order to get sockets that will accept self-signed certificates.

private static SSLSocketFactory sslSocketFactory;

/**
 * Returns a SSL Factory instance that accepts all server certificates.
 * <pre>SSLSocket sock =
 *     (SSLSocket) getSocketFactory.createSocket ( host, 443 ); </pre>
 * @return  An SSL-specific socket factory. 
 **/
public static final SSLSocketFactory getSocketFactory()
{
  if ( sslSocketFactory == null ) {
    try {
      TrustManager[] tm = new TrustManager[] { new NaiveTrustManager() };
      SSLContext context = SSLContext.getInstance ("SSL");
      context.init( new KeyManager[0], tm, new SecureRandom( ) );

      sslSocketFactory = (SSLSocketFactory) context.getSocketFactory ();

    } catch (KeyManagementException e) {
      log.error ("No SSL algorithm support: " + e.getMessage(), e); 
    } catch (NoSuchAlgorithmException e) {
      log.error ("Exception when setting up the Naive key management.", e);
    }
  }
  return sslSocketFactory;
}

The biggest problem with this approach is that we need to call our version of the getSocketFactory() method. Yeah, it would be nice if you could just do something like this:

System.setProperty ( "javax.net.ssl.trustStore ", 
       "com.mycompany.mypackage.NaiveTrustManager");

The code to build this came from Sun's web tutorial.

Approach 2: Implement a new Security Provider

The next approach extends our previous work and replaces the default java.security.Provider with one that inserts our NaiveTrustManager we previously created.

NaiveTrustProvider Class and the Embedded NaiveTrustManagerFactory

Our NaiveTrustProvider extends the java.security.Provider class with a version that contains a new "algorithm" and injects it into the AccessController (see this article for details and another version).

import java.security.*;
import java.security.cert.*;
import javax.net.ssl.*;

/**
 * Provides all secure socket factories, with a socket that ignores
 * problems in the chain of certificate trust. This is good for embedded
 * applications that just want the encryption aspect of SSL communication,
 * without worrying too much about validating the identify of the server at the
 * other end of the connection. In other words, this may leave you vulnerable
 * to a man-in-the-middle attack.
 */

public final class NaiveTrustProvider extends Provider
{
  /** The name of our algorithm **/
  private static final String TRUST_PROVIDER_ALG = "NaiveTrustAlgorithm";

  /** Need to refer to ourselves somehow to know if we're already registered **/
  private static final String TRUST_PROVIDER_ID  = "NaiveTrustProvider";

  /**
   * Hook in at the provider level to handle libraries and 3rd party 
   * utilities that use their own factory. Requires permission to 
   * execute AccessController.doPrivileged,
   * so this probably won't work in applets or other high-security jvms
   **/

  public NaiveTrustProvider ()
  {
    super ( TRUST_PROVIDER_ID,
            (double) 0.1,
            "NaiveTrustProvider (provides all secure socket factories by ignoring problems in the chain of certificate trust)" );

    AccessController.doPrivileged ( new PrivilegedAction() {
      public Object run()
      {
        put ("TrustManagerFactory." + NaiveTrustManagerFactory.getAlgorithm(),
             NaiveTrustManagerFactory.class.getName() );
        return null;
      }
    });
  }

  /**
   * This is the only method the client code need to call. Yup, just put
   * NaiveTrustProvider.setAlwaysTrust() into your initialization code
   * and you're good to go
   * 
   * @param enableNaiveTrustProvider set to true to always trust (set to false
   *          it not yet implemented)
   **/

  public static void setAlwaysTrust (boolean enableNaiveTrustProvider)
  {
    if (enableNaiveTrustProvider) {
      Provider registered = Security.getProvider (TRUST_PROVIDER_ID);
      if (null == registered) {
        Security.insertProviderAt (new NaiveTrustProvider (), 1);
        Security.setProperty ("ssl.TrustManagerFactory.algorithm",
                              TRUST_PROVIDER_ALG);
      }
    } else {
      throw new UnsupportedOperationException (
          "Disable Naive trust provider not yet implemented");
    }
  }

  /**
   * The factory for the NaiveTrustProvider
   **/
  public final static class NaiveTrustManagerFactory 
         extends TrustManagerFactorySpi
  {
    public NaiveTrustManagerFactory () { }

    protected void engineInit (ManagerFactoryParameters mgrparams)
    {
    }

    protected void engineInit (KeyStore keystore)
    {
    }

    /**
     * Returns a collection of trust managers that are naive. 
     * This collection is just a single element array containing
     * our {@link NaiveTrustManager} class.
     **/
    protected TrustManager[] engineGetTrustManagers ()
    {
      // Returns a new array of just a single NaiveTrustManager.
      return new TrustManager[] { new NaiveTrustManager() } };
    }

    /**
     * Returns our "NaiveTrustAlgorithm" string.
     * @return The string, "NaiveTrustAlgorithm"
     */
    public static String getAlgorithm()
    {
      return TRUST_PROVIDER_ALG;
    }
  }
}

Why yes, you could inline things and combine the first class with the last (and of course, drop the code in the middle completely). See this article for continuing discussion on this subject.

Tell others about this article:
Click here to submit this page to Stumble It