关于 SSLServerSocket.setWantClientAuth:
如果将其设置为 true
,则当客户端选择不发送证书时,协商过程会继续。
此外,我注意到如果客户端发送了证书但不在信任存储库中,协商也不会失败。
那么这个设置的用例是什么?
setWantClientAuth
用于请求客户端证书认证,但如果没有提供认证,则保持连接。 setNeedClientAuth
用于请求并要求客户端证书认证:如果未提交适当的客户端证书,则连接将终止。在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.
一开始我认为当你使用 "need" 时,你的客户端没有发送其证书。实际上,如果客户端在请求期间无法找到由服务器发送的 CA 名称列表中列出的颁发者颁发的客户端证书(或者客户端无法自己建立链路,这是常见问题),大多数客户端根本不会发送客户端证书。
默认情况下,JSSE 使用信任存储中的 CA 来构建该列表。
因此,如果服务器的信任存储中没有合适的颁发者,则您的客户端可能根本不会发送客户端证书。
您可以使用 Wireshark 检查是否发送了客户端证书。如果您没有在通常使用 SSL/TLS 的端口上运行,则需要右键单击数据包并选择 "Decode As... -> Transport -> SSL"。
在那里,您应该看到一个来自服务器的 Certificate Request
消息。(由于某些原因,当我使用默认的 JRE 信任存储与 Wireshark 一起使用时,该消息会出现为一个 "Encrypted Handshake Message",紧随 "Server Key Exchange" 消息之后。但是,它没有加密:如果您查看底部面板中数据包的 ASCII 渲染,您可以清楚地看到许多 CA 名称。也许这是因为这条消息太长了,我不确定。)在较短的列表中(例如,只有一个 CA 的信任存储),Wireshark 可以正确解码此消息,并且您应该在 "Distinguished Names" 部分中看到接受的 CA 列表。
您还应该看到来自客户端的 Certificate
消息(而不是来自服务器的那个)。如果服务器要求(使用 want 或 need)证书,则您应该始终从客户端看到此消息。
假设您可以访问测试 CA(具有由该 CA 颁发的客户端证书),则可以尝试以下实验。
If you set up your trust store with that test CA cert, use setWantClientAuth(true)
, the client will send its client certificate, and the connection will proceed. The server can then get the client certificate from the SSLSession
, as expected.
If you use the default trust store (that doesn't contain your test CA cert), use setWantClientAuth(true)
, the CA DN will not be in the Certificate Request
. The client will send a Certificate
message, but the certificate list will be empty (Certificates Length: 0
in Wireshark). Here, the client is actually not sending a client certificate, even if its keystore is configured to do so, simply because it can't find a suitable match. The connection will proceed (you may get an exception if you try to read the peer certificate from the SSLSession
on the server, but that's not fatal). This is the use-case for setWantClientAuth(true)
; setNeedClientAuth(true)
would have ended the connection immediately.
For the sake of this experiment, you can fake the list of DNs sent by the server in Java.
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);
In this case, the Certificate Request
message sent by the server should contain the your test CA's DN. However, that CA isn't actually trusted by the trust manager, which still uses the default values.
The client will send its certificate, but the server will reject it, saying "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed", and this will end the connection. This is at least the implementation using the SunJSSE provider, using the PKIX or SunX509 trust managers. This is also consistent with the JSSE specification of the trust manager: "The primary responsibility of the TrustManager is to determine whether the presented authentication credentials should be trusted. If the credentials are not trusted, the connection will be terminated."
SSLSession
获取客户端证书,则该证书应已由信任管理器进行身份验证(我的意思是您使用SSLSocket.getSession()
完成握手后获得的SSLSession
,而不是您在握手期间使用getHandshakeSession()
获得的会话,这是在Java 7中引入的)。当您想要了解客户端凭据但不想在他们未发送凭据时终止通信时,可以使用它。与“needClientAuth”相反,如果客户端未发送凭据,则握手将失败。
7.4.6
中说:如果没有合适的证书可用,则客户端应发送一个不包含证书的证书消息。这是一个“SHOULD”而不是必须。所以我在想,如果在wantClientAuth
中证书是可选的,那么如果发送的证书不受信任,它是否真的无关紧要?我的意思是,客户端可能根本没有发送证书,也能正常运行。这可能是我看到的原因吗?也就是说,连接被接受了? - JimwantClientAuth
并用“假”的信任管理器替换信任管理器会发生什么。 - Jim