At my current project, some of the webservices we need to connect to are running in a non-firewalled environment. In order to prevent unauthorized clients from connecting to these services, the administrators decided that is was a good plan to use mutual authentication using SSL. Since these are all connections running within the intranet environment of my customer, the certificates are not signed by a globally trusted certification authority (like VeriSign) but by an internal certificate authority which is not trusted by default.
Globally, the picture looks like this:
WAS = Websphere Application Server
IHS = IBM HTTP Sever (Apache with extra's)
We have webservice clients running in WAS which use Apache CXF, an open source services framework, to communicate with webservice providers. These providers are running on another WAS instance. The HTTPS connection terminates at the IHS which runs on the same machine as the WAS instance to connect to. The WAS instance then is configured to allow only connections from localhost.
Scope of this post
To get this working, we had to do all sorts of stuff:
- Generate certification requests for all certificates
- Get the certificates signed by the internal authority
- Correctly configure these signed certificates in WAS and IHS (client & server)
- Get both the server and client to trust these internally signed certificates
This post focusses on how to get client authentication to work in a webapplication that calls web services. Common stuff like getting certificates signed and such is well documented on the Internet, and I won't go into this. So the rest of this post assumes you have valid, signed certificates.
Our client applications have to set up the HTTPS connection to the servers and all configuration for this had to reside in the WAS (a maintenance requirement). So the CXF clients we have should not have to be configured to know where the keystores are, what the passwords are, and so on. They should "Just Work".
You can probably configure this in WAS in a million ways, but the way we did it was to create something called a "Dynamic outbound SSL configuration". It's mechanism in Websphere for defining SSL configuration in scope of a certain protocol, host and/or port. For example, it is possible to define a specific SSL configuration for a HTTPS connection to my.hostname.intranet on 443, and yet another for a HTTPS connection for my.other-hostname.intranet. In our case, we have a certificate that identifies the machine and not so much the application, so for us the setting was simply "*,*,443": any outgoing protocol to any host on port 443 would be set up using the configured SSL configuration.
So, what's exactly set in the SSL configuration? In our case, we created a new SSL configuration with a new keystore (containing the private key and the signed personal certificate) and a truststore (containing the trusted internal authority certificates). We also set the default client certificate alias, so we don't have to set this programmatically. IBM has a guide on setting it up that maybe is a bit complicated, but does provide good intel on what all possibilities are.
The last step was getting our client code to somehow use this dynamic outbound configuration. Through some digging on the Internet, we came across a solution where you can add an outgoing interceptor to the chain that passes a certain set of properties (like: the host, port and so on) to an IBM API, which retrieves a SslSocketFactory with the correct SSL configuration applied to it for use in the application code. All it requires is include the interceptor is the outgoing chain, which in practice is a matter of just a couple of lines of XML in the Spring configuration:
<cxf:bus> <cxf:outInterceptors> <bean class="com.xebia.opensource.cxf.extensions.WebsphereSslOutInterceptor" /> </cxf:outInterceptors> </cxf:bus>
This overrides CXF's default SSL handling and retrieves the Websphere SSLSocketFactory based on the hostname and port the message is being sent to. The application doesn't require any further configuration or knowledge of certificates. Also, the out interceptor does not cause any trouble when running in another container (such as Jetty) or when connecting to a webservice over plain HTTP (in a development environment). In these cases it simply does nothing.
I created an open source maven project for it, see the classes and the README in the websphere-cxf-extensions github project for more information.
The server-side certificates terminates at the IHS, so the webservice application itself serving the response doesn't have to have anything specific.
Instead, all is configured in IHS. I assume you already have IHS running on an SSL port and just have to make sure only authorized clients can connect to it. To require client authentication, the httpd.conf can be configured like this:
This means that clients must authenticate themselves, and only valid certificates with certain common names (which should match the hostname of the client) are granted access.
Also, the certification authority public certificates had to be added to the IHS truststore in order for IHS to accept the client certificates. This was already set up on the servers, so I don't exactly know how to do this...
It's hard to do this right on the first go, and if something is wrong, it's hard to debug what the exact problem is. If you're trying to get this working, doing the following things helped us:
- Set logging on FINEST on the com.ibm.websphere.ssl.* and com.ibm.ws.ssl.* packages. In the trace.log you now have a lot of information on what goes on in IBM internals.
- Try the client certificate working by first importing the private key and signed personal certificate into your browser and trying to set up the connection. This very much helps finding out if the problem is on the client or server end.