Jim*_*Jim 6 java sockets security ssl jsse
关于SSLServerSocket.setWantClientAuth:
如果设置为true
如果客户端选择不发送证书,则协商继续.
此外,我注意到如果客户端发送证书但不是信任库的一部分,也会发生这种情况.在这种情况下,协商也不会失败.
那么这个设置的用例是什么?
Bru*_*uno 13
(多次编辑,发表了大量评论.)
setWantClientAuth
用于请求客户端证书身份验证,但如果未提供身份验证,请保持连接.setNeedClientAuth
用于请求并要求客户端证书身份验证:如果未提供合适的客户端证书,则连接将终止.
您可以在TLS规范的" 客户端证书"部分中找到有关此主题的更多信息.有点历史:
在版本1.2中,它说" 如果没有合适的证书可用,客户端必须发送不包含证书的证书消息. ".在此之前,它只是一个"应该",但是Sun JSSE客户端无论如何都会发送一个空列表.
版本1.2还添加了:
此外,如果证书链的某些方面是不可接受的(例如,它未由已知的可信CA签名),则服务器可以自行决定是继续握手(考虑客户端未经身份验证)还是发送致命警报.
这为发送不可接受的证书时的操作提供了一些灵活性.JSSE选择发送致命警报.(setWantAuth
原则上可以继续使用无效证书,但不能将对等体视为已经过身份验证,就好像没有发送客户端证书一样,但事实并非如此.)
早期版本的TLS规范称" 如果服务器需要客户端身份验证才能继续握手,则可能会以致命的握手失败警报响应. " 这是在JSSE中实现的需求或需求之间的区别:使用"需要",服务器响应致命的握手失败,而使用"want",服务器继续进行连接,但不将其视为已验证.
我最初认为,当您使用"需要"时,您的客户端并未发送其证书.实际上,大多数客户端如果找不到服务器在请求期间发送的CA名称中列出的发行者之一颁发的客户端证书(或者客户端不能),则根本不会发送客户端证书.自己建立链,这是一个常见的问题).默认情况下,JSSE使用信任库中的CA来构建该列表.因此,如果合适的颁发者不在服务器的信任库中,您的客户端可能根本不会发送客户端证书.
您可以使用Wireshark检查是否发送了客户端证书.如果您没有在通常与SSL/TLS一起使用的端口上运行,则需要右键单击数据包并选择"解码为... - >传输 - > SSL".
在那里,您应该看到Certificate Request
来自服务器的消息.(出于某种原因,当我使用Wireshark的默认JRE信任库时,该消息显示为"加密握手消息",就在"服务器密钥交换"消息之后.但是,它没有加密:您可以清楚地看到一个数字如果您在底部面板中查看数据包的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);
Run Code Online (Sandbox Code Playgroud)
在这种情况下,Certificate Request
服务器发送的消息应包含测试CA的DN.但是,信任管理器实际上并不信任该CA,它仍然使用默认值.
客户端将发送其证书,但服务器将拒绝它,说"javax.net.ssl.SSLHandshakeException:sun.security.validator.ValidatorException:PKIX路径验证失败",这将结束连接.这至少是使用SunJSSE提供程序的实现,使用PKIX或SunX509信任管理器.这也与信任管理器的JSSE规范一致:" TrustManager的主要职责是确定是否应该信任所呈现的身份验证凭据.如果凭据不受信任,则连接将被终止. "
这里的关键点是,如果您能够从中获取客户端证书,那么SSLSession
该证书应该由信任管理员进行身份验证(我的意思是,SSLSession
一旦握手完成,您就得到了,而SSLSocket.getSession()
不是那个人在握手期间使用getHandshakeSession()
,在Java 7中引入).
您似乎在评论中指出您正在使用另一个JSSE提供程序,并且您的客户端无论如何都在发送客户端证书,无论CA证书是否在服务器的信任存储中,因为您的信任中还有另一个不同的CA证书存储具有相同的主题DN.假设这两个CA证书具有不同的密钥(否则它们实际上是相同的CA),这将是一个相当严重的错误:使用客户端证书身份验证的应用程序有权期望客户端证书已由信任管理器验证(如JSSE参考指南中所述.如果您正在使用Apache Tomcat,则一旦获得客户端证书,远程连接将被视为使用此证书进行身份验证.如果此时servlet能够使用无法验证的客户端证书,则实际上没有进行身份验证,这将是一个严重的缺陷.
归档时间: |
|
查看次数: |
8737 次 |
最近记录: |