当有多个匹配的证书时,SSL客户端证书是如何选择的?

11

这并不是应该通过设计实现的,但出于安全考虑,我想知道如果有多个证书符合特定CA签名的要求,"正确"的证书将如何发送到服务器?

我正在使用一个简单的SSL JAVA客户端示例,连接到Apache HTTPD。

我尝试了四个证书的测试,每次删除选择的证书并记录下一个被选择的证书。我找不到一个合理的逻辑(例如:日期、别名等),除了可能是证书的"sha256"的字典序排序。但这对我来说似乎不太可能......

示例客户端执行以下操作:

System.setProperty("javax.net.ssl.keyStore","device.p12");  
System.setProperty("javax.net.ssl.keyStorePassword","password");  
System.setProperty("javax.net.ssl.keyStoreType", "PKCS12");  
System.setProperty("javax.net.ssl.trustStore","truststore.jks");  
System.setProperty("javax.net.ssl.trustStorePassword","password");  
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();  
SSLSocket sslSock = (SSLSocket) factory.createSocket("87.69.60.100",443);  
BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(sslSock.getOutputStream(), "UTF8"));  
wr.write("GET /lather HTTP/1.1\r\nhost: 87.69.60.100\r\n\r\n");  
wr.flush();  

Apache的配置为:

SSLCACertificateFile rootCA.crt  
SSLVerifyClient require  

我找不到相关的文档来回答这个问题。我还想知道,是否有可能Apache会以某种方式转发多个证书链?(比如说,当一个行为异常的客户端发送一些奇怪的东西时)。

谢谢!


没有具体说明。我不理解关于“Apache forwarding”的部分,但只会发送一个证书链。 - user207421
@EJP,我写了“forwarded”,因为我配置了Apache,使用“SSLOptions +ExportCertData”将证书转发到Tomcat。你的意思是这不是在标准和Java实现中指定的吗?很奇怪...但它确实选择了某些东西,我的唯一真正选择就是查看他们的代码以找出哪一个?(“他们”的意思是SSLSocket,我猜?) - yair
那没什么意义。如果没有指定,就不能依赖于当前代码的行为。它可能会在明天改变。 - user207421
1个回答

19

需要客户端认证的服务器会发送一份可接受的证书类型列表,可能还包括可接受的CA列表。默认情况下,您的Java客户端会应用以下算法:

  1. 对于服务器接受的每种证书类型(RSA、DSA、EC),在密钥库中查找使用指定算法生成的任何公钥/私钥对。
  2. 如果服务器发送了一个可接受的CA列表,则删除不包含列表中任何CA的证书链中的任何密钥对。
  3. 如果仍然有至少一个密钥对剩余,则选择与第一个相对应的私钥;否则回到步骤1,尝试下一种密钥类型。
客户端证书选择算法没有在RFC 5246中指定,但Java的简单默认实现似乎是合理的,但如EJP所指出的那样,未来可能会更改。特别地,“第一个”证书几乎是随机的 - 凭据当前存储在Map中,因此它将取决于条目集的迭代顺序。此外,KeyManager实现是可插拔的,并且有一个“NewSun”实现可用于OpenJDK,通过传递安全属性ssl.KeyManagerFactory.algorithm=NewSunX509激活。这第二个也将考虑您的客户端证书的keyUsage和extendedKeyUsage属性以及到期日期。
如果您需要保证来自可能列表的证书,并且发现默认行为无法满足您的需求,最好的选择是手动创建单个入口密钥库并使用它来初始化SSLContext,或编写自己的X509KeyManager实现以在chooseClientAlias中执行所需操作,例如这个问题这个问题答案中所述。

如果潜在的客户端证书中存在密钥使用,则该密钥使用也用于做出选择。 - Bruno
实际上,自Java 7u60和8u5以来,默认的KeyManager实现似乎没有检查密钥用途或扩展密钥用途。 - rxg
如果您查看getAliases中使用的CheckType,它本身被chooseAlias调用,那么具有正确EKU的证书将被优先选择。 - Bruno
哦,自从那个转储以来他们一定改变了算法——使用7u60我得不到相同的结果或相同的调试信息。我正在查看http://www.java.net/download/openjdk/jdk7u40/promoted/b43/openjdk-7u40-fcs-src-b43-26_aug_2013.zip获取源代码,如果我测试一个带有“-ext eku=codeSig -ext ku=cRLSign”的密钥,它仍然会被选中。 - rxg
拥有这些扩展并不意味着它不会被选中,只是意味着如果有更好的匹配项,它就不会被优先选择。您是否仅使用该证书进行测试,还是在密钥库中使用其他更好的匹配证书? - Bruno

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接