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.