9
votes

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 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.

2

2 Answers

3
votes

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):

  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

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).

3
votes

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:

public class MyX509KeyManager extends X509ExtendedKeyManager
  {
    X509KeyManager defaultKeyManager;

    public MyX509KeyManager(X509KeyManager inKeyManager) {
        defaultKeyManager = inKeyManager;
    }

    public String chooseEngineClientAlias(String[] keyType,
            Principal[] issuers, SSLEngine engine) {
        return "<Alias of my cert>";
    }

    @Override
    public String chooseClientAlias(String[] strings, Principal[] prncpls, Socket socket) {
        return "<Alias of my cert>";
    }

    @Override
    public String[] getClientAliases(String string, Principal[] prncpls) {
        return defaultKeyManager.getClientAliases(string, prncpls);
    }

    @Override
    public String[] getServerAliases(String string, Principal[] prncpls) {
        return defaultKeyManager.getServerAliases(string, prncpls);
    }

    ...

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:

// clientKeyStore is initialized elsewhere from the SmartCard
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(clientKeyStore, clientKeyPassword.toCharArray());

MyX509KeyManager customKeyManager = new MyX509KeyManager((X509KeyManager) keyManagerFactory.getKeyManagers()[0]);
sslContext.init(new KeyManager[] {customKeyManager}, tmf.getTrustManagers(), null);