A project for work required me to program in that most rigid and litigious of languages, Java. I used Apache Axis to make SOAP calls over SSL, thus making the project buzzword compliant. This post is about how, with the help of John Cho at VMware, I was able to coax Java in talking with SSL hosts whose X509 certificates have no recognized identity by the client. Typically, this means the hosts are self-signed and are not know to trusted root certificate servers. Without this hack, Java will refuse to use the SSL connection until the user imported the client certificate from the self-signed server, using the keytool utility from the JRE.

NOTE: Disabling authenticating of SSL certs is not compatible with security-sensative projects. Please understand that by using this hack, you are reducing the effectiveness of SSL security (although the actual network traffic through the SSL tunnel is still encrypted). The decision to use this hack is typical of the design choices made on the security-versus-convenience axis.

Although this code is specific to Axis, the careful reader may find this code applicable to their next Java project.

Apache Axis 1.4 (and earlier) uses the standard java.net.ssl package that is distributed with Sun’s JDK 1.5 (it’s in older JDKs too). The Axis class that creates the SSL socket factory that contacts the class that actually does the cert authenication is:

org.apache.axis.components.net.JSSESocketFactory.java

What we need to do create a special X509TrustManager that accepts all certs and install it as part of the SSL Factory. There’s a lot of abstraction and action at a distance code here, so let’s start with how to implement an all-trusting X509Certificate class.

class TrustyTrustManager implements javax.net.ssl.X509TrustManager {

  public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
    return null; 
  }

  public void checkClientTrusted(
         java.security.cert.X509Certificate[] c, 
     String authType) throws CertificateException {
    // do nothing, accept by default
  }

  public void checkServerTrusted(
         java.security.cert.X509Certificate[] c, 
         String authType) throws CertificateException {
    // do nothing, accept by default
  }
}

Recall that X509TrustManager is just an abstract class. It requires three methods to be implemented. Fortunately, these methods are easy to deal with, if you don’t care about authenication. The first, getAcceptedIssuers(), is supposed to return an array of certs for trusted authority servers. Since we aren’t going to consult any, we can return null there. The last two methods throw an exception if the described certificate cannot be authenticated by building a “trust path” from the root authorities to the given one. Since not throwing an exception means that the trust path was succesfully constructed, we do absolutely nothing in these methods. If only all code were so easy to write!

The only catch here is that we do need to import java.security.cert.CertificateException to handle the exceptions that we’ll never throw, otherwise we get a compiler error. Thanks for the extra hoop, Java.

I added this class as an inner, private class to the JSSESocketFactory.java file, but you may want to isolate this code into its own file for use in other projects.

Now we need to install this X509Certificate class. This can be done by change the code in initFactory() to something like the following:

    protected void initFactory() throws IOException {
    // Inspired by John Cho
    try {
        javax.net.ssl.TrustManager[] trusty = 
        new javax.net.ssl.TrustManager[] {
            new TrustyTrustManager() 
        };

        javax.net.ssl.SSLContext sc = 
                  javax.net.ssl.SSLContext.getInstance("SSL");

        sc.init(null, trusty, new java.security.SecureRandom());
        sslFactory = (SSLSocketFactory) sc.getSocketFactory();
    } catch (Exception e) {
        throw(new IOException("SSLFactory: " + e.getMessage()));
    }
    }

Let me step through the code backwards, since it will help to know where we’re going with this method. The whole point of initFactory() is to initialize the protected class member sslFactory with a valid object. SSLSocketFactory objects are returned by javax.net.ssl.SSLContext objects, which the documentation describes like this:

«Instances of this class represent a secure socket protocol implementation which acts as a factory for secure socket factories. This class is initialized with an optional set of key and trust managers and source of secure random bytes.»

I emphasized “trust manager” here, since that’s what we implemented with the TrustyTrustManager class. Now, we just need to create an SSLContext object for the SSL protocol (not sure why there are other options here), and shove in our TrustManager.

Well, not quite. It turns out that SSLContext expects to get an array of TrustManagers.

init(KeyManager[] km, TrustManager[] tm, SecureRandom random)

We don’t need KeyManagers at all and the random seed can be generated from a method in java.security. What we do need is an array of TrustManagers with one TrustyTrustManager object in it. Once this is assembled, init() can be called correctly and the SSLSocketFactory obtained.

There you are! It’s as simple as rocket science, but not as hard as brain surgery.

Some advanced Java coders will probably decry this code as cheating. It’s not very OO to manhandle an existing class like this. Surely, there must be a way to subclass JSSESocketFactory and override the behavior of initFactory() more cleanly. The answer is “yes,” but that means finding all those places that instantiate JSSESocketFactory objects and subclassing those classes. And, of course, you can see how this subclassing will quickly ripple through the entire object heirarchy of Axis to destroy all my free time. In Perl, I might have simply poked through the package namespace at some point to overwrite initFactory(). I’m not aware how to do that in Java effectively, but I welcome your suggestions.

UPDATE: For additional information on doing this with Apache’s XML-RPC lib for Java, check this out. I think this technique may work with axis too, since it is built of the standard Sun SSL stuff. But, I haven’t tried.