关于SSLServerSocket.setWantClientAuth:
如果设置为true
如果客户端选择不发送证书,则协商继续。
我还注意到,如果客户端发送证书但不是信任库的一部分,也会发生这种情况。在这种情况下,协商也不会失败。
那么这个设置的用例是什么?
(多个编辑,遵循一些评论。)
setWantClientAuth
用于请求客户端证书认证,但如果没有提供认证,则保持连接。setNeedClientAuth
用于请求和要求客户端证书身份验证:如果没有提供合适的客户端证书,连接将终止。
您可以在 TLS 规范的客户端证书部分找到有关此主题的更多信息。对于一点历史:
在 1.2 版中,它说“如果没有合适的证书可用,客户端必须发送不包含证书的证书消息。 ”。在此之前,它只是一个“应该”,但 Sun JSSE 客户端无论如何都会发送一个空列表。
1.2 版还添加了:
> Also, if some aspect of the certificate chain was unacceptable (e.g.,
> it was not signed by a known, trusted CA), the server MAY at its
> discretion either continue the handshake (considering the client
> unauthenticated) or send a fatal alert.
This gives some flexibility regarding what to do when a unacceptable certificate is sent. The JSSE chooses to send a fatal alert. (`setWantAuth` could in principle carry on with invalid certificates, but not treat the peer as authenticated, as if no client certificate was sent, but this isn't the case.)
Previous versions of the TLS spec said "*If client authentication is required by the server for the handshake to continue, it may respond with a fatal handshake failure alert.*". This is the difference between need or want as implemented in the JSSE: using "need", the server responds with a fatal handshake failure, whereas using "want", the server carries on with the connection, but doesn't treat it as authenticated.
我最初认为当您使用“需要”时您的客户没有发送其证书。实际上,如果大多数客户端找不到由服务器在其请求期间发送的 CA 名称中列出的颁发者之一颁发的客户端证书(或者如果客户端不能自己建立链,这是一个常见的问题)。默认情况下,JSSE 使用信任库中的 CA 来构建该列表。因此,如果合适的颁发者不在服务器的信任库中,您的客户端可能根本不会发送客户端证书。
您可以检查是否使用 Wireshark 发送了客户端证书。如果您没有在通常与 SSL/TLS 一起使用的端口上运行,则需要右键单击数据包并选择“解码为... -> 传输 -> SSL”。
在那里,您应该会看到Certificate Request
来自服务器的消息。(出于某种原因,当我将默认的 JRE 信任库与 Wireshark 一起使用时,该消息显示为“加密握手消息”,就在“服务器密钥交换”消息之后。但是,它没有加密:您可以清楚地看到一个数字如果您在底部面板中查看数据包的 ASCII 呈现,则 CA 名称的数量。也许这是因为此消息太长,我不确定。)使用较短的列表,例如,具有单个 CA 的信任存储, Wireshark 您将其正确解码为Certificate Request
消息,并且您应该在“可分辨名称”部分中看到接受的 CA 列表。
您还应该看到Certificate
来自客户端的消息(当然不是来自服务器的消息)。如果服务器请求(需要或需要)证书,则无论如何您都应该始终看到来自客户端的此消息。
假设您可以访问测试 CA(使用该 CA 颁发的客户端证书),您可以尝试以下实验。
如果您使用该测试 CA 证书设置信任库,请使用setWantClientAuth(true)
,客户端将发送其客户端证书,并且连接将继续。然后,服务器可以SSLSession
按预期从 中获取客户端证书。
如果您使用默认信任库(不包含您的测试 CA 证书),请使用setWantClientAuth(true)
,CA DN 将不在Certificate Request
. 客户端将发送一条Certificate
消息,但证书列表将为空(Certificates Length: 0
在 Wireshark 中)。在这里,客户端实际上并没有发送客户端证书,即使它的密钥库被配置为这样做,只是因为它找不到合适的匹配项。连接将继续(如果您尝试从服务器上读取对等证书,您可能会遇到异常SSLSession
,但这不是致命的)。这是setWantClientAuth(true)
;的用例 setNeedClientAuth(true)
会立即结束连接。
为了这个实验,你可以用 Java 伪造服务器发送的 DN 列表。
KeyManagerFactory kmf = //... Initialise a KMF with your server's keystore
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null); // Use the default trust store
TrustManager[] trustManagers = tmf.getTrustManagers();
final X509TrustManager origTrustManager = (X509TrustManager) trustManagers[0];
final X509Certificate caCert = // Load your test CA certificate here.
X509TrustManager fakeTrustManager = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// Key the behaviour of the default trust manager.
origTrustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// Key the behaviour of the default trust manager.
origTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
// This is only used for sending the list of acceptable CA DNs.
return new X509Certificate[] { caCert };
}
};
trustManagers = new X509TrustManager[] { fakeTrustManager };
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
在这种情况下,Certificate Request
服务器发送的消息应该包含您的测试 CA 的 DN。但是,信任管理器实际上并不信任该 CA,它仍然使用默认值。
客户端将发送其证书,但服务器将拒绝它,并说“javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed”,这将结束连接。这至少是使用 SunJSSE 提供程序、使用 PKIX 或 SunX509 信任管理器的实现。这也与信任管理器的 JSSE 规范一致:“ TrustManager 的主要职责是确定所提供的身份验证凭证是否应该被信任。如果凭证不被信任,则连接将被终止。 ”
这里的关键点是,如果您能够从 获取客户端证书,则SSLSession
该证书应该已由信任管理器进行身份验证(我的意思是,SSLSession
一旦握手完成,您将获得,而SSLSocket.getSession()
不是您在握手过程中使用getHandshakeSession()
,在 Java 7 中引入)。
您似乎在评论中指出您正在使用另一个 JSSE 提供程序,并且您的客户端无论如何都在发送客户端证书,无论 CA 证书是否在服务器的信任库中,因为您的信任中还有另一个不同的 CA 证书使用相同的主题 DN 存储。假设这两个 CA 证书具有不同的密钥(否则它们实际上是同一个 CA),这将是一个相当严重的错误:使用客户端证书身份验证的应用程序有权期望客户端证书已由信任管理器验证(如 JSSE 参考指南中所述)。如果您使用的是 Apache Tomcat,一旦它获得客户端证书,远程连接就被认为是通过此证书进行身份验证的。如果此时 servlet 能够使用可以'
当您想知道客户端凭据是否发送它们时使用它,但如果他不发送,您又不想停止通信。与“needClientAuth”相比,如果他不发送握手将失败。