Skip to main content

JAX-WS: Can I choose from multiple client certificates on the fly?

9 replies [Last post]
wgkorb
Offline
Joined: 2004-11-09
Points: 0

I have a webapp that is calling a web service supplied by a vendor. The vendor requires the use of client certificates for authentication, and I have successfully called their service using the PKCS#12 keystore they gave us with JAX-WS 2.2 using code like this:

    System.setProperty("javax.net.ssl.keyStore", "myKeyStore.p12");<br />
    System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");<br />
    System.setProperty("javax.net.ssl.keyStorePassword", "password");

The problem is, my webapp will be supporting multiple business units, and the vendor differentiates between our business units by issuing separate certificates for each. So I'm in a quandary: I have four PKCS#12 files, one per business unit, and my webapp will need to decide which one to use at runtime. Moreover, this webapp could be heavily used by many simultaneous users, and thus more than one of the certs may need to be used at the same time.

So it seems to me that I will need to include all four of my certs in the same keystore, but as they are supplied by the vendor, each cert/key pair comes in a separate .p12 file. Is it possible to combine all four p12 files into a single keystore using keytool?

Once I resolve that, how do I tell JAX-WS which certificate to present each time it contacts the vendor's web service?

Any insights would be very much appreciated.

Thanks,
Bill

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
wgkorb
Offline
Joined: 2004-11-09
Points: 0

I've discovered the solution for issue number 1 (how to combine multiple PCKS#12/p12 files into a single keystore). With the advent of the keytool included with JDK 1.6, the "-importkeystore" option can be used to import the cert/key pair from one p12 file into another.

For example:

[code]keytool -importkeystore -v \
-srckeystore key1.p12 \
-destkeystore combinedKeystore.p12 \
-srcstoretype pkcs12 \
-deststoretype pkcs12 \
-srcstorepass key1Pass \
-deststorepass combinedPass \
-srcalias key1alias \
-destalias key1alias
keytool -importkeystore -v \
-srckeystore key2.p12 \
-destkeystore combinedKeystore.p12 \
-srcstoretype pkcs12 \
-deststoretype pkcs12 \
-srcstorepass key2Pass \
-deststorepass combinedPass \
-srcalias key2alias \
-destalias key2alias[/code]
After running these two commands, I have a new PKCS#12 keystore named [i]combinedKeystore.p12[/i] and [b]keytool -list[/b] confirms that both keys are present.

OK, now how to select from among those multiple keys?

Thanks,
Bill

wgkorb
Offline
Joined: 2004-11-09
Points: 0

Ugh. I spoke to soon. When I tried to use the new .p12 file with both keys included, it hits the fan:
[code]...
Caused by: java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded
at com.sun.net.ssl.internal.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:270)
at java.security.KeyStore.getKey(KeyStore.java:763)
at com.sun.net.ssl.internal.ssl.SunX509KeyManagerImpl.(SunX509KeyManagerImpl.java:113)
at com.sun.net.ssl.internal.ssl.KeyManagerFactoryImpl$SunX509.engineInit(KeyManagerFactoryImpl.java:48)
at javax.net.ssl.KeyManagerFactory.init(KeyManagerFactory.java:239)
at com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl.getDefaultKeyManager(DefaultSSLContextImpl.java:170)
at com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl.(DefaultSSLContextImpl.java:40)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at java.lang.Class.newInstance0(Class.java:355)
at java.lang.Class.newInstance(Class.java:308)
at java.security.Provider$Service.newInstance(Provider.java:1221)
... 27 more
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.PKCS12PBECipherCore.b(DashoA13*..)
at com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndDESede.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at com.sun.net.ssl.internal.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:252)
... 40 more[/code]
*sigh* Back to the drawing board.

Bill

wgkorb
Offline
Joined: 2004-11-09
Points: 0

OK, solution is to import both PKCS#12 certs into a JKS keystore. Once I do that, then I can see and use both keys.
So back to question #2: how do I select from these multiple keys are runtime when connecting to the consuming service?
Anyone?
Thanks,
Bill

Mhui
Offline
Joined: 2011-03-08
Points: 0

I'm not sure if I replied already, when i reply by email it doens't get reposted...
Search for aliasSelector in the following article.
http://xwss.java.net/articles/security_config.html

Oh yeah, the resason why ou get the Unrecoverable Key , is because all the keys in the glassfish's keystore.jks must either have no password or the password must be the same as the keystore's password. ie. "changeit".

wgkorb
Offline
Joined: 2004-11-09
Points: 0

I found a blog post that claims to supercede the URL you pointed me at:

http://weblogs.java.net/blog/2009/06/01/security-token-configuration-metro

I was able to create a class that implements the com.sun.xml.wss.AliasSelector interface as that was trivial.

What I am struggling with, however, is where to specify that configuration in my client. I am using Netbeans (I've tried both 6.9 and 7) but cannot seem to find where to specify the WS-Security settings. The WSDL I am using is in a local file and doesn't include the URL (I set that manually using the BindingProvider.ENDPOINT_ADDRESS_PROPERTY) and it doesn't indicate anywhere in the WSDL that either CERTIFICATE or BASIC authentication are required (and in fact, both are). Perhaps that is why the "Web Service Properties" wizard doesn't include any of the choices for security that I've seen on the NB tutorials.

Do I just need to include the sc:Keystore configuration tag in the wsit-client.xml that NB generated for me? If so, it doesn't even include a declaration of the sc namespace - do I just need to add that? Perhaps something like this?

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; 
&lt;definitions
xmlns=&quot;http://schemas.xmlsoap.org/wsdl/&quot;
xmlns:wsdl=&quot;http://schemas.xmlsoap.org/wsdl/&quot;
xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot;
xmlns:soap=&quot;http://schemas.xmlsoap.org/wsdl/soap/&quot; name=&quot;mainclientconfig&quot;
xmlns:sc=&quot;http://schemas.sun.com/2006/03/wss/server&quot;
&gt;
  &lt;import location=&quot;source-ws-api.xml&quot; namespace=&quot;http://svc.domain.com/wsapi&quot;/&gt;
  &lt;sc:Keystore
    aliasSelector=&quot;com.domain.MyAliasSelector&quot;
    callbackHandler=&quot;com.domain.MyKeyStoreCallbackHandler&quot;
  /&gt;
  &lt;sc:CallbackHandlerConfiguration&gt;
    &lt;sc:CallbackHandler name=&quot;usernameHandler&quot; classname=&quot;com.domain.MyUsernameCallbackHandler&quot;/&gt;
    &lt;sc:CallbackHandler name=&quot;passwordHandler&quot; classname=&quot;com.domain.MyPasswordCallbackHandler&quot; /&gt;
  &lt;/sc:CallbackHandlerConfiguration&gt;
&lt;/definitions&gt;

Oh, and if it matters, I am deploying this in a non-109 container (Sun Web Server 7/Oracle iPlanet Web Server 7). As such, I don't have any of the security features that are built-in to GlassFish. I assume that I can just include the Metro 2.1 JARs in my WEB-INF/lib to get this to work.

Any help would be very much appreciated.

Thanks,
Bill

kumarjayanti
Offline
Joined: 2003-12-10
Points: 0

 The AliasSelector is not something that can help a WebService Client to send different Client-Certs in a SSL HandShake.  The AliasSelector is primarily for a case where the Client Certificate is passed in the SOAP Message (WS-Security Header) as a BinarySecurityToken, in this case the AliasSelector can help select the appropriate certificate for each of your BU's as you state.

SSL is handled at a lower layer in the Stack underneath the WebServices layer. So if you need to multiplex Client-Certs from a keystore depending on the BU, then you will need to write a Custom X509KeyManager (javax.net.ssl.X509KeyManager) and initialize an SSLContext using your custom KeyManager and then set SSLSockeFactory obtained form the Context as the VM's default SSLSocketFactory on the client side.

Is your Client a Standalone Java Client or is it a WebClient running inside GlassFIsh again ?.

For WebClients running on GlassFish we have a System Property (jvm-option) that can be set for the https outbout alias 

-Dcom.sun.enterprise.security.httpsOutboundKeyAlias=YOUR_ALIAS

But this would allow for a single alias to be chosen from a keystore containing multiple Key's.

 

wgkorb
Offline
Joined: 2004-11-09
Points: 0

I found another blog entry that sheds some light on this:

http://blogs.oracle.com/harsha/entry/selecting_certificates_programmatically_in_wsit

This shows that I do indeed need to embed my configuration into the wsit-client.xml, albeit a bit different than I listed above. I decided to do a quick and dirty prototype to try to get this working. I created a service that simply returns the passed string, and put it inside a security-constraint in my web.xml it with transport-guarantee CONFIDENTIAL and login-config auth-method CLIENT-CERT.

If I run my sample client against this service without an AliasSelector, it simply grabs the first certificate in my keystore that the server will accept. I then tweaked it as follows:

  MyService	service = new MyService();
  My port = service.getMyPort();
  ((BindingProvider) port).getRequestContext().put(&quot;USER&quot;, name);

I then created an implementation of AliasSelector that will look for the USER property in the Map that is passed to the select() method, and use that as the alias to look for:

public class MyAliasSelector implements AliasSelector {
  @Override
  public String select(Map map) {
    if ( (map == null) || map.isEmpty() ) {
      return null;
    }
    String username = (String) map.get(&quot;USER&quot;);
    if ( username == null ) {
      throw new IllegalArgumentException(
                &quot;Passed map must include the \&quot;USER\&quot; property to select appropriate cert.&quot;);
    }
    return username;
  }
}

Lastly, I tweaked my wsit-client.xml to specify my aliasSelector:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; 
&lt;definitions
xmlns=&quot;http://schemas.xmlsoap.org/wsdl/&quot;
xmlns:wsdl=&quot;http://schemas.xmlsoap.org/wsdl/&quot;
xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot;
xmlns:soap=&quot;http://schemas.xmlsoap.org/wsdl/soap/&quot;
xmlns:wsp=&quot;http://www.w3.org/ns/ws-policy&quot;
xmlns:sc1=&quot;http://schemas.sun.com/2006/03/wss/client&quot;
name=&quot;mainclientconfig&quot;
&gt;
  &lt;import location=&quot;MyService.xml&quot; namespace=&quot;http://my.svc.domain.com/&quot;/&gt;
  &lt;wsp:Policy&gt;
    &lt;wsp:ExactlyOne&gt;
      &lt;wsp:All&gt;
        &lt;sc1:KeyStore visibility=&quot;private&quot; storepass=&quot;password&quot; type=&quot;JKS&quot;
          location=&quot;/tmp/keystore.jks&quot;
          aliasselector=&quot;com.domain.MyAliasSelector&quot; /&gt;
      &lt;/wsp:All&gt;
    &lt;/wsp:ExactlyOne&gt;
  &lt;/wsp:Policy&gt;
&lt;/definitions&gt;

I know that my modified wsit-client.xml is indeed being loaded by Metro when I run the client because when I made a typo, it threw an exception and printed a stack trace indicating my error. However, when I run my code in my debugger and put a break point on the first line of the MyAliasSelector.select() method, I find that it is never called.

Am I missing something obvious here?

Thanks,
Bill

wgkorb
Offline
Joined: 2004-11-09
Points: 0

One more data point: I changed the value of my aliasSelector attribute to a non-existent class, and this caused no issue. This leads me to believe that Metro isn't even validating the wsit-client.xml config file.

wgkorb
Offline
Joined: 2004-11-09
Points: 0

Just to close the loop on this (and for the next person trying to figure out how to do it), I was able to extend X509KeyManager as described in Alexandre Saudate's blog. I was then able to set the com.sun.xml.ws.developer.JAXWSProperties.SSL_SOCKET_FACTORY on my JAX-WS request context to use my custom SSLSocketFactory, and it works like a charm!

Thanks,
Bill