Thursday, October 8, 2009

Axis SSL for a non-trusted Certificate - Dev, Testing, only.

The problem is the Entrust 2048 SSL certificates which were recently issued, every platform I use normally has no trouble accepting them (no imported certificates required!), but deploying on the IBM WebSphere 6.1 JDK/Server is nothing but pain.

My errors without an import - "No trusted certificate found":

00000026 WSX509TrustMa E   CWPKI0022E: SSL HANDSHAKE FAILURE:  
A signer with SubjectDN "blah" was sent from target host:port "blah:443".  
The signer may need to be added to local trust store
"../base_v61/profiles/was61profile1/config/cells/CellName/nodes/NodeName/trust.p12" located in 
SSL configuration alias "NodeDefaultSSLSettings". 
The extended error message from the SSL handshake exception is: 
"No trusted certificate found".


My errors after attempting to import to trust.p12 - "End user tried to act as a CA"

WSX509TrustMa E   CWPKI0022E: SSL HANDSHAKE FAILURE:  
A signer with SubjectDN "blah" was sent from target host:port "blah:443".  
The signer may need to be added to local trust store 
"../base_v61/profiles/was61profile1/config/cells/CellName/nodes/NodeName/trust.p12"
located in SSL configuration alias "NodeDefaultSSLSettings". 
The extended error message from the SSL handshake exception is: 
"End user tried to act as a CA".


No matter what I import or try, I keep seeing "End user tried to act as a CA". Well if that's the way it's going to be IBM-TrustManager, that's the way I'm going to play.

My solution, though designed and tested on IBM runtimes is JDK independent.

My first play was to switch to xfire, which is just about the coolest/simplest SOAP implementor out there - and I know it uses the commons httpclient package.

Use Easy SSL TrustManager implementation, so easy to override.

EasySSLTrustManager is designed to accept self-signed certs, not those that are completely borked. So for me, it was required that I copy the httpclient contrib source and add a quick try/catch around the checkClientTrust and checkServerTrusted. This way I still see errors, but the ssl communications proceeds unhindered.

Then I tried to do the same for an Axis client. There are a few misleading pages on the Axis wiki; which I completely disregarded in favour of control by AxisProperties.

I'm running on Axis 1.4, but my stubs were generated years ago and I don't want to bother regenerating.

Here's the great site that set me on the "Axis client even with self-signed SSL cert" path.

My implementation, however, is much simpler, and reuses the EasySSLTrustManager :)

We create a TrustAllSSLSocketFactory that implements Axis' SecureSocketFactory
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Hashtable;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.axis.components.net.JSSESocketFactory;
import org.apache.axis.components.net.SecureSocketFactory;
import org.apache.commons.httpclient.contrib.ssl.EasyX509TrustManager;

/**
* Custom SSL socket factory to ignore certificate errors.
* 
* Based loosely on org.apache.axis.components.net.SunJSSESocketFactory
* Praise to open source libraries.
*/
public class TrustAllSSLSocketFactory extends JSSESocketFactory implements
SecureSocketFactory {
/**
* Constructor TrustAllSSLSocketFactory
* @param attributes
*/
@SuppressWarnings("unchecked")
public TrustAllSSLSocketFactory(Hashtable attributes) {
super(attributes);
}

/**
* Init the SSL socket factory with our own Trust Manager.
* 
* This overrides the parent class to provide our SocketFactory
* implementation.
* 
* @throws IOException
*/
protected void initFactory() throws IOException {

try {
SSLContext context = getContext();
sslFactory = context.getSocketFactory();
} catch (Exception e) {
if (e instanceof IOException) {
throw (IOException) e;
}
throw new IOException(e.getMessage());
}
}

/**
* Gets a custom SSL Context with no keystore and a Trust All Manager.
* 
* @return SSLContext 
* @throws Exception
*/
protected SSLContext getContext() throws Exception {

try {
// congifure a local SSLContext to use created keystores
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { new EasyX509TrustManager(null) },
new SecureRandom());

return sslContext;
} catch (Exception e) {
throw new Exception("Error creating context for SSLSocket!", e);
}
}

}


Now we tell Axis about it's new SSL Socket Factory.

I chose to do so in the WSProxy class initializing method, but it can exist any where before the call.

public class SampleWSProxy implements SampleWS {
private String _endpoint = null;
private com.example.application.webservice.ISampleWS iSampleWS = null;

public ISampleWSProxy() {
_initISampleWSProxy();
}

private void _initISampleWSProxy() {
try {
// this Axis default class only works for self-signed certs,
//  or certs loaded by custom keystore. NB. Has version for Sun JDK also.
// AxisProperties.setProperty("axis.socketSecureFactory",
// "org.apache.axis.components.net.IBMJSSESocketFactory");

// this works for all certs - even when an exception is thrown
// connection ignores the invalid cert and moves on :)
AxisProperties.setProperty("axis.socketSecureFactory",
"full.package.to.TrustAllSSLSocketFactory");

String socketSecureFactory = AxisProperties
.getProperty("axis.socketSecureFactory");
System.out.println("axis.socketSecureFactory : " + socketSecureFactory);

iSampleWS = (new ISampleWSServiceLocator())
.getSampleWS();
if (iSampleWS != null) {
if (_endpoint != null) {
((javax.xml.rpc.Stub) iSampleWS)._setProperty(
"javax.xml.rpc.service.endpoint.address", _endpoint);
}
else {
_endpoint = (String) ((javax.xml.rpc.Stub) iSampleWS)
._getProperty("javax.xml.rpc.service.endpoint.address");
}
// Staging has a test/generic provisioning web service
// with default username password as: admin
// callers may/must override! :)
((javax.xml.rpc.Stub) iSampleWS)._setProperty(
javax.xml.rpc.Stub.USERNAME_PROPERTY, "admin");
((javax.xml.rpc.Stub) iSampleWS)._setProperty(
javax.xml.rpc.Stub.PASSWORD_PROPERTY, "admin");

}

} catch (javax.xml.rpc.ServiceException serviceException) {
}
}
... end of _initISampleWSProxy .. followed by available web service methods ...
}

3 comments:

k said...

I'm having a similar problem. Can you give an example of the try/catch's you added to the trustmanager please? I'm not uber java and I cant get anything to take =(

October 29, 2009 12:37:00 PM EDT
syber said...

The EasyX509TrustManager changes were to swallow/ignore Throwables are wrapping the certificate validation in the methods:
checkClientTrusted and checkServerTrusted.

e.g.
----------------------------------------
public void checkClientTrusted(X509Certificate[] certificates, String authType)
throws CertificateException {
try {
standardTrustManager.checkClientTrusted(certificates, authType);
} catch (Throwable t) {
LOG.warn("Certificate not trusted " + t);
}
}

----------------------------------------

public void checkServerTrusted(X509Certificate[] certificates, String authType)
throws CertificateException {
try {
if ((certificates != null) && (certificates.length == 1)) {
certificates[0].checkValidity();
}
else {
standardTrustManager.checkServerTrusted(certificates, authType);
}
} catch (Throwable t) {
LOG.warn("Certificate not trusted " + t);
}
}

------------------------------------------

October 29, 2009 3:07:00 PM EDT
Anonymous said...

We had the same problem and this fixed it.

http://www.entrust.net/knowledge-base/technote.cfm?tn=7875

June 9, 2010 12:50:00 PM EDT