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

问题描述:

在Windows 7上运行的Java 1.7应用程序中,我尝试使用服务器执行双向SSL(a智能卡令牌通过openSC提供我的客户端证书)。服务器的证书得到了客户端的验证,但客户端不响应服务器的证书请求。我相信这是因为客户端无法从我的证书创建链条到服务器请求的链条(尽管存在这样的链条)。我的SSL客户端(Java)没有通过双向SSL握手将证书发送回服务器

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

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

通过关键的工具,我也装在truststore(java cacerts文件),我的证书颁发者DOD CA-30与服务器请求的内容之间应该有什么联系,DoD根CA 2.

来自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 

所以问题是,为什么客户端不能为响应创建证书链?下面是相关的代码:

// 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最有可能的。

尽管看起来您只是将服务器发送的CA列表的一部分复制到此问题中,但我认为CN=DOD CA-30, OU=PKI, OU=DoD, O=U.S. Government, C=US不在此列表中。

什么,似乎在链缺失是该证书(你提到以后):

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 

证书导入到客户的信任有客户端发送的证书上绝对没有任何影响。客户端证书(及其私钥)需要在客户端密钥库中设置。此外,如果您想发送客户端证书链(如果服务器在列表中未提供此中间CA证书),则需要将完整链关联到该证书条目。将其他证书放入密钥库并不足够。

要解决此问题,您应该使用客户端证书链来配置密钥存储库条目。这可以按照this answer中所述完成。然而,这可能是通过PKCS#11访问硬件令牌的事实可能会使这一点变得更加复杂(也许还有另一种与该卡一起提供的证书管理工具,可能独立于Java)。

+0

感谢您直接在不使用信任存储库的客户端上找到完整的证书链。由于证书来自智能卡,因此修改证书很困难或无法完成。我确实设法以另一种方式绕过它,我在下面详细说明了答案,以便其他人在相同情况下可能会有答案。 – PaulP 2012-08-01 15:29:40

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

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我推迟至除我要重写任何东西。然后,要在你的sslContext中使用它,请执行以下操作:

// 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); 
+1

这有助于客户端发送此证书,但如果服务器缺少中间证书以构建链以验证它,则几乎肯定会出现问题。 (这只能在服务器知道中间证书但不在证书请求消息中发送它的名字的情况下才有效,这是可能的。) – Bruno 2012-08-01 15:57:03

+0

你可以做的也是重写'getCertificateChain'来把这个证书添加到手动链。 – Bruno 2012-08-01 16:18:59

+0

正确,这是假定服务器确实拥有完整的证书链。 – PaulP 2012-08-01 18:20:34