我的SSL客户端(Java)没有通过双向SSL握手将证书发送回服务器

问题描述:

在Windows 7上运行的Java 1.7应用程序中,我尝试与服务器进行2向SSL(智能卡令牌通过openSC提供我的客户端证书).客户端可以很好地验证服务器的证书,但是客户端不响应服务器的证书请求.我相信这是因为客户端无法从我的证书到服务器请求的证书之一建立链(即使存在这样的链).

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

这是服务器的证书请求和客户端为空的响应的SSL调试:

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

我的客户证书如下:

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]

通过密钥工具,我还安装在信任库(java cacerts文件)中,证书的颁发者DOD CA-30与服务器所请求的DoD Root CA 2之间应该是什么链接.

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.

通过SSL调试:

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

我得到的错误是服务器返回了403.最可能的原因是客户端未向其发送客户端证书.

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.

由于我知道我需要使用哪个证书对服务器进行身份验证,因此我可以通过扩展X509ExtendedKeyManager并覆盖choiceClientAlias来强制客户端发送该特定证书. ()方法始终返回该证书的别名.代码:

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);
    }

    ...

因此,正如您所看到的,我接受了defaultKeyManager,除我要覆盖的内容外,我将使用defaultKeyManager.然后,要在您的sslContext中使用它,请执行以下操作:

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