My SSL client (Java) isn't sending a certificate back to the server in two-way SSL handshake

23,695

Solution 1

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

Solution 2

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);
Share:
23,695
PaulP
Author by

PaulP

Updated on September 11, 2020

Comments

  • PaulP
    PaulP over 3 years

    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.

  • PaulP
    PaulP almost 12 years
    Thanks for setting me straight on the client not using the trust store to find the full certificate chain. Since the cert is from a SmartCard, modifying the cert is difficult or impossible to do. I did manage to get around it another way, which I detailed below as the answer, provided so that anyone else in the same situation might have an answer.
  • Bruno
    Bruno almost 12 years
    This makes sense to make the client send this certificate, but there will almost certainly be a problem if the server is missing the intermediate certificate to build the chain in order to validate it. (This can only work if the server is aware of the intermediate certificate but doesn't send its name in the certificate request message, which is possible.)
  • Bruno
    Bruno almost 12 years
    What you can do too is to override getCertificateChain to add this certificate to the chain manually.
  • PaulP
    PaulP almost 12 years
    Correct, this is assuming that the server does have the full cert chain available.