In a Java 1.7 app running on Windows 7, I'm trying to do 2-way SSL with a server (a smartcard token is providing my client certs via openSC). The server's certificate is getting verified by the client just fine, but the client doesn't respond to the server's certificate request. I believe it's because the the client isn't able to make a chain from my certificate to one of the ones requested by the server (even though such a chain exists).
Here's the SSL debug of the server's Certificate Request and the clients empty response:
*** CertificateRequest
Cert Types: RSA, DSS, ECDSA
Cert Authorities:
<CN=c4isuite-SDNI-DC02-CA, DC=c4isuite, DC=local>
<CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US>
...
*** ServerHelloDone
*** Certificate chain
***
My client cert is as follows:
found key for : Certificate for PIV Authentication
chain [0] = [
[
Version: V3
Subject: CN=<...>, OU=CONTRACTOR, OU=PKI, OU=DoD, O=U.S. Government, C=US
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 2048 bits
Issuer: CN=DOD CA-30, OU=PKI, OU=DoD, O=U.S. Government, C=US
SerialNumber: [ 05bf13]
Via key-tool, I also installed in the truststore (java cacerts file), what should be the link between my cert's issuer, DOD CA-30, and what the server is requesting, DoD Root CA 2.
From SSL debug:
adding as trusted cert:
Subject: CN=DOD CA-30, OU=PKI, OU=DoD, O=U.S. Government, C=US
Issuer: CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US
Algorithm: RSA; Serial number: 0x1b5
Valid from Thu Sep 08 10:59:24 CDT 2011 until Fri Sep 08 10:59:24 CDT 2017
adding as trusted cert:
Subject: CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US
Issuer: CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US
Algorithm: RSA; Serial number: 0x5
Valid from Mon Dec 13 09:00:10 CST 2004 until Wed Dec 05 09:00:10 CST 2029
So the question is, why can't the client make the certificate chain for the response? Here's the relevant code:
// Create the keyStore from the SmartCard certs
Provider provider = new sun.security.pkcs11.SunPKCS11(configName);
Security.addProvider(provider);
keyStore = KeyStore.getInstance("PKCS11", "SunPKCS11-SCR3310test");
char[] pin = PIN.toCharArray();
keyStore.load(null, pin);
// Init the trustmanager
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
// Create the client key manager
LOG.info("Installing keystore with pin");
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(clientKeyStore, clientKeyPassword.toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), null);
// Init SSL context
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
URL url = new URL(urlString);
URLConnection connection = url.openConnection();
if (connection instanceof HttpsURLConnection) {
LOG.info("Connection is HTTPS");
((HttpsURLConnection) connection).setSSLSocketFactory(socketFactory);
}
// Send the request.
connection.connect();
InputStreamReader in = new InputStreamReader((InputStream) connection.getContent());
...
And the error I get back is that the server returns a 403. Most likely because the client didn't send it a client cert.
Even though it looks like you've only copied part of the CA list sent by the server into this question, I'll assume that
CN=DOD CA-30, OU=PKI, OU=DoD, O=U.S. Government, C=US
isn't in this list.What seems to be missing in the chain is this certificate (which you mention later on):
Importing certificates into your client's truststore has absolutely no effect on the certificate the client sends. The client-certificate (and its private key) needs to be set up in the client keystore. In addition, if you want to send a client-certificate chain (which will be required here, if the server doesn't offer this intermediate CA certificate in its list), you'll need to associate the full chain to that certificate entry. It's not just enough to put the other certificates into the keystore.
To fix this, you should configure your keystore entry with the client-certificate chain. This can be done as described in this answer. However, it's possible that the fact that this is a hardware token accessed via PKCS#11 might make this a bit more complicated (perhaps there's another certificate management tool provided with the card, possibly independent from Java).
Since I know which certificate I need to use for authentication to the server, I can force the client to send that specific certificate by extending X509ExtendedKeyManager, and Overriding the chooseClientAlias() method to always return the alias of that certificate. Code:
So as you can see, I take in a defaultKeyManager which I defer to for anything except what I want to override. Then, to use this in your sslContext, do the following: