[advertisement]

Interested in putting your ad here?

Blog Entry

29th July 2006
Send to twitter Send to Facebook

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.

Blah!
:: 31st Jul 2006 - 12:07
:: Mike
Work work work. Give me something I can enjoy. More stories about beer in airports!
hotttt
:: 2nd Aug 2006 - 14:08
:: sally
me likey the tech talk. me-ow.
Tnx
:: 9th Aug 2006 - 14:08
:: Andrea
It has been useful Tnx
worked for me
:: 23rd Aug 2006 - 12:08
:: Oliver
thanks.
Nicer way
:: 12th Sep 2006 - 10:09
:: Simon
Re: the last paragraph - the answer is "yes" and it's a bit nicer than you suggest.
  1. Create your subclass of JSSEFactory
  2. Create a file on the classpath called META-INF/services/org.apache.axis.components.net.SecureSocketFactory. Makes the contents simply the fully qualified name to your JSSEFactory subclass. ie my.company.axis.helpers.JSSEFactory
  3. Done!
As explained here: http://wiki.apache.org/ws/FrontPage/Axis/AxisClientConfiguration/Ssl. Hope that helps...
Another simple solution
:: 21st Nov 2006 - 14:11
:: Mike Kolcun

I was having a similar problem, wanting to ignore the ssl certs, after trying the above I ran across this.

All I did was

System.setProperty("axis.socketSecureFactory","org.apache.axis.components.net.SunFakeTrustSocketFactory");

And everyting worked. Found it here.

http://wiki.apache.org/ws/FrontPage/Axis/SslUnsignedCertificate

Thanks for the article

RE: Disabling X509 Cert authentication in Apache Axis
:: 28th Sep 2007 - 23:09
:: Agha
Thanks. You just saved me from a salmon day.
carlosbarber4@yahoo.com
:: 17th May 2010 - 15:05
Nice post. Easy as rocket science? Easier when somebody's teaching how. Thanks man!

Blog archives

+ About this blog