Wednesday, July 28, 2010

web service over https

A blog by Lukas Rossa
Home My How-To's Recently Read
RSS subscribe by rss
Calling a secure Axis web service via SSL, through a proxy
February 5th, 2008 by lukas

In a recent project one of the requirements was to implement an Axis client for a web service with the following additional configuration:

1. Web service traffic must go through a proxy
2. Basic authentication turned on – login & password required
3. Communication is over HTTPS with a self-signed SSL certificate

Although not overly difficult for somebody experienced in creating Apache Axis clients the implementation for the above is not trivial. This is mainly due to SSL’s complexity but also because the configurations to get the above working are required in slightly different places of the Axis client framework.

I hope the below may help someone through some of the steps required.

1. Routing web service calls through a proxy

We need to take care of this one first. If there is a proxy between the client and the server and the client doesn’t know to route the traffic through it the requests won’t even reach the server and will just time out. (If your web browser uses a proxy then your local web service will as well since we are using SOAP over HTTP. You can most likely use the same proxy for the settings for the web service).
Without a proxy configured the connection just times out:

AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
faultSubcode:
faultString: java.net.ConnectException: Connection timed out: connect
faultActor:
faultNode:
faultDetail:
{http://xml.apache.org/axis/}stackTrace:java.net.ConnectException: Connection timed out: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:305)
...

Telling Axis to use a proxy is easy. We use Axis properties to set both the proxy host and the port:

private void configureProxy()
{
//this setting (from a config file) controls whether the proxy is to be used or not
if (ApplicationProperties.useProxy())
{
AxisProperties.setProperty("https.proxyHost", "192.168.15.1");
AxisProperties.setProperty("https.proxyPort", "8080");
}
}

2. Basic Authentication

Once Axis knows to use a proxy we can actually reach the server. But calling a web service that requires HTTP basic-auth without the proper credentials will fail. Basic authentication includes a base64 encoded login and password set in the http header of the message. Without these details in the header we will get an error similar to this:

AxisFault
faultCode: {http://xml.apache.org/axis/}HTTP
faultSubcode:
faultString: (401)Authorization Required
faultActor:
faultNode:
faultDetail:
{}:return code: 401


401 Authorization Required

Authorization Required


This server could not verify that you
are authorized to access the document
requested. Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.




{http://xml.apache.org/axis/}HttpErrorCode:401

(401)Authorization Required
at org.apache.axis.transport.http.HTTPSender.readFromSocket(HTTPSender.java:744)
at org.apache.axis.transport.http.HTTPSender.invoke(HTTPSender.java:144)
...

Taking care of this is also quite easy as the generated Axis client stub (if you’re using a standard JAX-RPC web service) provides methods to set the user login and password for basic authentication. (Naturally, you need to know what these are to be able to connect)

private void configureBasicAuth() throws ServiceException
{
//the port is the interface for the web service
this.configuredPort = ((MyWebServiceLocator)getService()).getMyWebServicePort();

MyWebServiceBindingStub myStub = (MyWebServiceBindingStub)configuredPort;
myStub.setUsername("userName");
myStub.setPassword("password");
}

3. Communicating over HTTPS with a self-signed SSL certificate

Now that we’re authenticated our web service request makes it through but the SSL connection cannot be established since we’re not providing the correct certificate. In my instance the server requires an unsigned (or self-signed) SSL certificate.

If we don’t provide one the request will bounce with the following error:

AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
faultSubcode:
faultString: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
faultActor:
faultNode:
faultDetail:
{http://xml.apache.org/axis/}stackTrace:javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SunJSSE_az.a(DashoA12275)
...

Implementing SSL using certificates is a bit more tricky. It requires creating a custom SSLSocketFactory which uses the correct SSL certificate and setting this factory as the one to be used by Axis. The certificate is packaged into a custom keystore which is packaged and released with the client application. The steps required to implement this are:

1. obtain the certificate to be used on the client-side
2. create a custom keystore to store the above certificate
3. create code for the custom ssl socket factory by extending org.apache.axis.components.net.JSSESocketFactory (reads the custom keystore)
4. configure the new custom factory to be used by Axis

To create the custom factory we first need to create a custom keystore which will hold our self-signed certificate. The “Using Self-Signed Certificates for Web Service Security” article explains nicely how this is done.

(The certificate you receive could be a .crt. The keytool utility may throw a tantrum at this as it expects a .cer file. To convert to this Base-64 encoded X.509 certificate, double-click your original .crt file -> Details -> Copy to File and follow the prompts. Save the resulting certificate as a file with a .cer extension and keytool should now be happy)

I used the following command to create my CustomKeystore.jks:

keytool -import -noprompt -trustcacerts -alias CustomKeystoreAlias -file CERTIFICATE.cer -keystore CustomKeystore.jks -storepass customKeystorePassword

(NOTE: for some reason I could not copy the command into the CommandPrompt window and had to type it out)

Once the keystore is created, we include it in the client application – somewhere on the classpath. The next step is to code the custom SSLSocketFactory:

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Hashtable;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import org.apache.axis.components.net.JSSESocketFactory;
import org.apache.axis.components.net.SecureSocketFactory;
import org.apache.commons.lang.StringUtils;

/**
* Custom SSL socket factory to use our integrated keystore.
*
* Based loosely on org.apache.axis.components.net.SunJSSESocketFactory
*/
public class MyCustomSSLSocketFactory extends JSSESocketFactory implements SecureSocketFactory {

/* local keystore password */
private static String MY_KEYSTORE_PASSWORD = "customKeystorePassword";

/* local keystore file (contains the self-signed certificate from the server */
private static String RESOURCE_PATH_TO_KEYSTORE = "CustomKeystore.jks";

/**
* Constructor MyCustomSSLSocketFactory
*
* @param attributes
*/
public MyCustomSSLSocketFactory(Hashtable attributes) {
super(attributes);
}

/**
* Read the keystore, init the SSL socket factory
*
* 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.
* This is the main working of this class. The following are the steps that make up our
* custom configuration:
*
* 1. Open our keystore file using the password provided
* 2. Create a KeyManagerFactory and TrustManagerFactory using this file
* 3. Initialise a SSLContext using these factories
*
* @return SSLContext
* @throws WebServiceClientConfigException
* @throws Exception
*/
protected SSLContext getContext() throws WebServiceClientConfigException {

char[] keystorepass = MY_KEYSTORE_PASSWORD.toCharArray();

if (StringUtils.isBlank(new String(keystorepass)))
throw new WebServiceClientConfigException("Could not read password for configured keystore!");

InputStream keystoreFile = this.getClass().getResourceAsStream(RESOURCE_PATH_TO_KEYSTORE);

if (keystoreFile == null)
throw new WebServiceClientConfigException("Could not read the configured keystore file at "+RESOURCE_PATH_TO_KEYSTORE);

try
{
// create required keystores and their corresponding manager objects
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());

keyStore.load(keystoreFile, keystorepass);

KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keystorepass);

TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);

// congifure a local SSLContext to use created keystores
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());

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

The last step required is to tell Axis to use the above MyCustomSSLSocketFactory as the default ssl socket factory. This is again achieved by setting AxisProperties:

private void configureSSL()
{
//this is configurable so that it can be switched off in development mode
if (Configuration.useSSL())
{
//use our custom SSLSocketFactory
AxisProperties.setProperty("axis.socketSecureFactory","com.company.application.util.MyCustomSSLSocketFactory");
}
else
{
// The fake factory must not be used in production environment because it ignores any certificates but
// it is convenient to have for testing purposes.
AxisProperties.setProperty("axis.socketSecureFactory","org.apache.axis.components.net.SunFakeTrustSocketFactory");
log.debug("WARNING: SSL CERTIFICATE CONFIGURATION IS TURNED OFF!");
}
}

In the code above we have the option of either using our new fully configured MyCustomSSLSocketFactory or the provided SunFakeTrustSocketFactory. The latter is very useful for testing and will accept any ssl certificate. It is mentioned on the Axis wiki here.

This should be it. Communication should now flow freely between client and server in a reasonably secure manner.
Feel free to post comments.

Posted in programming | 26 Comments »
26 Responses

1. Abhay Singh Says:
February 9th, 2008 at 4:43 am

Hi there,

The article by you was excellent.

I was struggling to configure my axis client for https, but with your article i could successfully do that.

Thanks a ton.

Cheers
Abhay
2. dheer Says:
February 19th, 2008 at 7:31 pm

Thank you it helped me a lot to get an idea. Now I still get this exception

javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?

Could you please tell what else config i am missing Thank You
3. lukas Says:
February 20th, 2008 at 6:18 am

dheer:
Make sure that you are actually contacting the web service via https. Your web service url must start with “https://” and if you’re using a proxy then make sure that you’re setting the https.proxyHost and https.proxyPort. Also, double check that the server you’re trying to connect to has actually https setup.
4. marcel Says:
April 9th, 2008 at 8:59 pm

thanks a lot for this brilliant article. you helped me a great deal to finish my stuff. a lot of black magic is going on with all this SSL stuff :-)

great
marcel
5. vin Says:
May 13th, 2008 at 5:41 am

Great article. wonderful to find all the three niggling issues covered in the same article!
6. Julie Says:
July 31st, 2008 at 10:17 am

At what point do you call configureSSL()? Is there a workflow to this? Do I have to set the AxisProperties prior to obtaining a connection to the web service?
7. lukas Says:
August 1st, 2008 at 5:19 am

Julie: The above methods are all static methods that can be called once you have a client proxy. Each of the config steps must be executed BEFORE you establish the connection as once you do that you cannot easily dynamically change connection properties.
8. Julie Says:
August 1st, 2008 at 6:19 am

I don’t think this works with Axis2. Just setting the AxisProperties did nothint – i.e. at no point was my custom SSLSocketFactory getting instantiated. Axis2 uses apache.commons.HttpClient at its http transport layer, so this way is looking more promising… http://hc.apache.org/httpclient-3.x/sslguide.html
(although, I haven’t gotten it to work yet, either).
9. SivaKannan Says:
October 14th, 2008 at 11:29 am

This actual is a great post!! I was able to talk to a WS expecting Client Auth.

I have a question. I want to pass the cert/password location to the MyCustomSSLSocketFactory(in your case). How can I do that?
10. Vince Says:
March 6th, 2009 at 3:55 am

Hi,

i have a Web Application that calls two Web Services.

The two Web Services can be reach via two different proxies. Meaning WS #1 can be reached via proxy #1 (and only this one) and WS #2 can be reached via proxy #2 (and only this one).

The two calls are not synchronised and can be done at the same time. So in my case, i would like a thread safe solution.

my question is : do you have any idea how can I specify two different proxies for calling WS with axis 1.1 ?

Thanks.
11. lukas Says:
March 17th, 2009 at 9:38 am

Vince: this is an interesting problem that may not be easily solved with Axis 1.1. Without knowing the details I can suggest maybe to create 2 separate client projects (separately deployed jar files) each with its own Axis client implementation. You’d dispatch messages to either one or the other depending on service call required. This is more in the realm of enterprise messaging now and would potentially benefit from a lightweight JMS or even ESB implementation.
12. Sam Says:
March 17th, 2009 at 10:38 pm

Hello.
Your article is very interesting, still i have a few question to set.

My webservices are in PHP, and the client is in Java(using Axis).

I configured Apache(server) to use SSL using a self generated certificate, then imported the certificate to create the CustomKeystore just like you adviced.

When i tested the client i got:
“javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target”.

So, after a bit of reading, i imported the certificate into the cacerts file used by the JRE in use.

Now the client can connect and it responds, even if i provide a bad MY_KEYSTORE_PASSWORD or PATH to the keystore in the MyCustomSSLSocketFactory.

Does the client still look to the MyCustomSSLSocketFactory anymore ?
Is there anyway to overpass the problems caused by the self generated certificate ?

Thank you.
13. Juanqui Says:
March 18th, 2009 at 12:38 am

Hi Lukas, a great article !
I have to connect an axis client web service with SSL but with mutual authentication, I have the the certificate from a URL in a cacerts and everything works fine, but when I need to send my credentials I don’t know how to do it.
I need to send my certificate that is in the cacerts too but I donĂ‚´t find how to choose my alias and how to connect, and then call a method. Any ideas ?
14. Suresh Says:
March 19th, 2009 at 7:30 am

Thanks a lot Lukas for your article.

Great work dude!!
15. Juanqui Says:
March 27th, 2009 at 6:23 am

I could do the mutual authentication, but now I have the following problem. I have some axis clients that use one way authentication and others use two ways authentication.
For work with two ways authentication I need to set the axis properties:
AxisProperties.setProperty(“axis.socketSecureFactory”,”com.mycompany.MySSLSocketFactory”);
Setting this property produce that the two way authentication works fine but the one way authentication don’t work.
Any ideas ? Thanks, Juan.
16. Juanqui Says:
March 31st, 2009 at 8:32 am

If you want to work with “3. Communicating over HTTPS with a self-signed SSL certificate”, one way or two ways, don’t create the class that inherit from JSSESocketCustomFactory, I repeat you don`t need to do “3. create code for the custom ssl socket factory by extending org.apache.axis.components.net.JSSESocketFactory (reads the custom keystore)” and “4 configure the new custom factory to be used by Axis”, you only need to set this six properties:

javax.net.ssl.trustStore="your path to cacerts"
javax.net.ssl.trustStorePassword=yourpassword
javax.net.ssl.trustStoreType=JKS or the type you use in your certs
javax.net.ssl.keyStore="your path to cacerts"
javax.net.ssl.keyStorePassword=yourpassword
javax.net.ssl.keyStoreType=JKS or the type you use in your certs

Another things is that all your certs in cacerts should have the same password for work with axis 1.4
17. Juanqui Says:
March 31st, 2009 at 8:36 am

And you don’t should set the axis properties:
AxisProperties.setProperty(“axis.socketSecureFactory”,”com.mycompany.MySSLSocketFactory”);
and everything works fine.
The solution for Lucas work great if you want to work with one or two ways but not both at the same time.
18. Anatoliy Says:
April 29th, 2009 at 7:02 am

Using multiple configuration at the same time should be possible. You can define a property file that maps the destination host and port to your specific SSL attributes and initialise those up front. Here is a detailed example:

package com.my.pack;

import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Hashtable;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.servlet.ServletContextEvent;

import org.apache.axis.AxisProperties;
import org.apache.axis.components.net.JSSESocketFactory;
import org.apache.axis.components.net.SecureSocketFactory;
import org.apache.axis.components.net.BooleanHolder;

/**
* This class provides a workaround for Axis 1.x limitation of using custom socket factories dynamically.
* It caches a custom created Map of SSLSocketFactory objects on startup and later uses this cache rather
* then the default Axis factory.
*/
public class MyCustomSSLSocketFactory extends JSSESocketFactory implements SecureSocketFactory, javax.servlet.ServletContextListener
{
public static final String DEFAULT_SSL_CONTEXT_TYPE = "SSLv3";

private static HashMap factories = null;

/**
* Initialize your custom factory here. Make sure that is defined in right web.xml,
* so it picked up by the right classloader.
*/
public void contextInitialized(ServletContextEvent sce)
{
AxisProperties.setProperty("axis.socketSecureFactory", "com.my.pack.MyCustomSSLSocketFactory");
loadFactories();
}

public void contextDestroyed(ServletContextEvent sce) {}

public MyCustomSSLSocketFactory(Hashtable attributes)
{
super(attributes);
}

/**
* A dummy init to mask the JSSESocketFactory.initFactory();
*/
protected void initFactory() throws IOException
{
}

/**
* This method loads the Map of custom SSL Socket Factories.
*/
public synchronized void loadFactories()
{
if(factories==null)
{
while(//reading thru your property file that maps "host:port"
//to SSL attributes like trust/key store, etc.
)
{
factories.put(host+port, getSocketFactory(trust_store,
trust_store_type,
trust_store_password,
ident_store,
ident_store_type,
ident_store_password,
ident_private_key_password)
)
);
}
}
}

/**
* This method uses a custom SSLSocketFactory, before letting the super.create(...) to continue.
* The "sslFactory" is an instance variablee of the JSSESocketFactory, and is initialized in its create(...) method
* unless already beed existent. Here it will always be overriden every time new socket is created.
*/
public Socket create(String host, int port, StringBuffer otherHeaders, BooleanHolder useFullURL) throws Exception
{
if(port 0)
{
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(identStoreType);
ks.load(getFileStream(identStore), identStorePassword.toCharArray());
kmf.init(ks, identPrivateKeyPassword.toCharArray());
kmArr = kmf.getKeyManagers();
}

ctx.init(kmArr, tmArr, new SecureRandom());
factory = ctx.getSocketFactory();

return factory;
}

/**
* Read resource file
*/
private static InputStream getFileStream(String file)
{
InputStream fis = null;
try
{
try{fis = new FileInputStream(file);}catch(Exception e){;}
if(fis==null)
{
System.out.println("File ["+file+"] not found. Will attempt load from classpath...");
URL url = ClassLoader.getSystemClassLoader().getResource(file);
if (url == null)
url = MyCustomSSLSocketFactory.class.getResource("/" + file);

fis = url.openStream();
}
}
catch(IOException e)
{
System.out.println("Unable to load file ["+file+"]");
e.printStackTrace();
}
return fis;
}
}

19. Anatoliy Says:
April 29th, 2009 at 7:13 am

That didn’t post very well. I need to find some other place to put the code.
20. Peter Says:
June 15th, 2009 at 12:00 pm

I find HTTP4E very useful for making AXIS Service calls. It is an awesome Eclipse plugin. It has tabs, syntax coloring, auto suggest, code generation, REST HTTP call replay, etc.. It does a great job of HTTP debugging, HTTP tampering, hacking.

http://http4e.roussev.org/
21. buddha Says:
July 16th, 2009 at 4:23 am

Can you please change the webpage color style . Its hard to read the code , with these colors.
22. venky Says:
September 25th, 2009 at 4:24 pm

i have 3 services runs on the same instance. two runs in the same LAN and one is external. so how can i control switching ? i am getting issues if one works other fails. i am setting as System.setproperty for proxy one.can u help me regarding this ?
SoapBindingStub stub = new SoapBindingStub(new URL(service.getSoapAddress()), service);
System.setProperty(“http.proxyHost”, “hostname”);
System.setProperty(“http.proxyPort”, “3128″);
23. wolf Says:
December 10th, 2009 at 3:28 am

I really like this solution. As I understand it, it allows to provide special key stores for many axis clients in one installation.

Unfortunately it is build on axis1.4. I use axis2 1.5.x and the complete package org.apache.axis.components.net does not exist.

Has anyone ported this to axis2?

Thanks a lot
Wolf
24. Guest Says:
May 10th, 2010 at 3:16 pm

I am getting ” faultString: (401)Authorization Required” exception when i am making the JAR.
If i run application without making the JAR, then this exception is not thrown …
25. Guest Says:
May 10th, 2010 at 5:32 pm

I am getting ” faultString: (401)Authorization Required” exception when i am making the JAR.
If i run application without making the JAR, then this exception is not thrown
26. BrendaHerring21 Says:
July 25th, 2010 at 4:10 am