Choosing SSL client certificate in Java
Our system communicates with several web services providers. They are all invoked from a single Java client application. All the web services up until now have been over SSL, but none use client certificates. Well, a new partner is changing that.
Making the application use a certificate for the invocation is easy; setting javax.net.ssl.keyStore
and javax.net.ssl.keyStorePassword
will do it. However, the problem is now how to make it so that it only uses the certificate when invoking that particular web service. I guess more generally speaking, we'd like to be able to choose the client certificate to be used, if any.
One quick solution could be setting the system properties, invoking the methods, and then unsetting them. The only problem with that is that we're dealing with a multi-threaded application, so now we would need to deal with synchronization or locks or what have you.
Each service client is supposed to be completely independent from each other, and they're individually packaged in separate JARs. Thus, one option that has occurred to me (although we haven't properly analyzed it) is to somehow isolate each JAR, maybe load each one under a different VM with different parameters. That's merely an idea that I don't know how to implement (or if it's even possible, for that matter.)
This post suggests that it is possible to select an individual certificate from a key store, but how to attach it to the request seems to be a different issue altogether.
We're using Java 1.5, Axis2, and client classes generated with either wsimport
or wsdl2java
.
Solution 1:
The configuration is done via an SSLContext
, which is effectively a factory for the SSLSocketFactory
(or SSLEngine
). By default, this will be configured from the javax.net.ssl.*
properties. In addition, when a server requests a certificate, it sends a TLS/SSL CertificateRequest
message that contains a list of CA's distinguished names that it's willing to accept. Although this list is strictly speaking only indicative (i.e. servers could accept certs from issuers not in the list or could refuse valid certs from CAs in the list), it usually works this way.
By default, the certificate chooser in the X509KeyManager
configured within the SSLContext
(again you normally don't have to worry about it), will pick one of the certificates that has been issued by one in the list (or can be chained to an issuer there).
That list is the issuers
parameter in X509KeyManager.chooseClientAlias
(the alias
is the alias name for the cert you want to picked, as referred to within the keystore). If you have multiple candidates, you can also use the socket
parameter, which will get you the peer's IP address if that helps making a choice.
If this helps, you may find using jSSLutils (and its wrapper) for the configuration of your SSLContext
(these are mainly helper classes to build SSLContext
s). (Note that this example is for choosing the server-side alias, but it can be adapted, the source code is available.)
Once you've done this, you should look for the documentation regarding the axis.socketSecureFactory
system property in Axis (and SecureSocketFactory
). If you look at the Axis source code, it shouldn't be too difficult to build a org.apache.axis.components.net.SunJSSESocketFactory
that's initialized from the SSLContext
of your choice (see this question).
Just realized you were talking about Axis2, where the SecureSocketFactory
seems to have disappeared. You might be able to find a workaround using the default SSLContext
, but this will affect your entire application (which isn't great). If you use a X509KeyManagerWrapper of jSSLutils, you might be able to use the default X509KeyManager
and treat only certain hosts as an exception. (This is not an ideal situation, I'm not sure how to use a custom SSLContext
/SSLSocketFactory
in Axis 2.)
Alternatively, according to this Axis 2 document, it looks like Axis 2 uses Apache HTTP Client 3.x:
If you want to perform SSL client authentication (2-way SSL), you may use the Protocol.registerProtocol feature of HttpClient. You can overwrite the "https" protocol, or use a different protocol for your SSL client authentication communications if you don't want to mess with regular https. Find more information at http://jakarta.apache.org/commons/httpclient/sslguide.html
In this case, the SslContextedSecureProtocolSocketFactory
should help you configure an SSLContext
.
Solution 2:
Java SSL clients will only send a certificate if requested by the server. A server can send an optional hint about what certificates it will accept; this will help a client choose a single certificate if it has multiple.
Normally, a new SSLContext
is created with a specific client certificate, and Socket
instances are created from a factory obtained from that context. Unfortunately, Axis2 doesn't appear to support the use of an SSLContext
or a custom SocketFactory
. Its client certificate settings are global.