I am calling a webservice that requires client certificate authentication.
If I specify a Java keystore containing a single certificate (the client certificate the servi
Short answer: it cannot be done with default Java ssl implementation.
Long answer: I looked on how the SSL handshake is implemented in the sun.security.ssl.ClientHandshaker
. In its method serverHelloDone
is called X509ExtendedKeyManager.chooseClientAlias
. Its implementations are really done in such a way that they return first alias, whose entry matches the given key algorithm and few other things. No way how to tweak the alias selection.
For those who can change the code, this looks like a promising workaround: http://www.44342.com/java-f392-t785-p1.htm
Heres a complete code snipped that works.
I use it to create an SSL connection on Android with a keystore from a smartcard that contains multiple certificates that match the standard filtering criteria.
Credits go to zarniwoop.
/**
* filters the SSLCertificate we want to use for SSL
* <code>
* KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
* kmf.init(keyStore, null);
* String SSLCertificateKeyStoreAlias = keyStore.getCertificateAlias(sslCertificate);
* KeyManager[] keyManagers = new KeyManager[] { new FilteredKeyManager((X509KeyManager)kmf.getKeyManagers()[0], sslCertificate, SSLCertificateKeyStoreAlias) };
* </code>
*/
private class FilteredKeyManager implements X509KeyManager {
private final X509KeyManager originatingKeyManager;
private final X509Certificate sslCertificate;
private final String SSLCertificateKeyStoreAlias;
/**
* @param originatingKeyManager, original X509KeyManager
* @param sslCertificate, X509Certificate to use
* @param SSLCertificateKeyStoreAlias, Alias of the certificate in the provided keystore
*/
public FilteredKeyManager(X509KeyManager originatingKeyManager, X509Certificate sslCertificate, String SSLCertificateKeyStoreAlias) {
this.originatingKeyManager = originatingKeyManager;
this.sslCertificate = sslCertificate;
this.SSLCertificateKeyStoreAlias = SSLCertificateKeyStoreAlias;
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return SSLCertificateKeyStoreAlias;
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return originatingKeyManager.chooseServerAlias(keyType, issuers, socket);
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return new X509Certificate[]{ sslCertificate };
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return originatingKeyManager.getClientAliases(keyType, issuers);
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return originatingKeyManager.getServerAliases(keyType, issuers);
}
@Override
public PrivateKey getPrivateKey(String alias) {
return originatingKeyManager.getPrivateKey(alias);
}
}
The links that Jakub provides in his response lead you to the answer, but I wanted to post a simpler response here, since we struggled with this problem for quite a while before finally getting something that worked.
We have the case where there are several certificates available to use, and we need to use the one that has a specific alias to perform our connection. We did this by creating our own KeyManager implementation which passes through most of its functionality to the default X509KeyManager but has functionality to select exactly the correct alias to use when the connection is performed.
First the key manager we created:
public class FilteredKeyManager implements X509KeyManager {
private final X509KeyManager originatingKeyManager;
private final X509Certificate[] x509Certificates;
public FilteredKeyManager(X509KeyManager originatingKeyManager, X509Certificate[] x509Certificates) {
this.originatingKeyManager = originatingKeyManager;
this.x509Certificates = x509Certificates;
}
public X509Certificate[] getCertificateChain(String alias) {
return x509Certificates;
}
public String[] getClientAliases(String keyType, Principal[] issuers) {
return new String[] {"DesiredClientCertAlias"};
}
All other methods required for implementation are passthroughs to originatingKeyManager
.
Then, when we actually set up the context:
SSLContext context = SSLContext.getInstance("TLSv1");
context.init(new KeyManager[] { new FilteredKeyManager((X509KeyManager)originalKeyManagers[0], desiredCertsForConnection) },
trustManagerFactory.getTrustManagers(), new SecureRandom());
Hope that makes it clear, and works for anyone else trying to solve this problem.
My impression of the KeyManager is that once it is initialized with the keystore, it uses the alias of the private key entry to find the associated certificate and certificate chain.
Otherwise,I think it chooses a chain based on the key types and certificate authorities recognized by the host.
So in your case, your description does not mention a private entry in the keystore, so I will guess that the keymanager chooses the most suitable certificate.
I am not aware at all of the system property you mention.
-Try to change the keystore to have a private key and the associated chain
-Or (not sure if this will work) change the alias of the certificate you want to send to the server to match the subject name of the certificate