SSL服务器套接字想要认证选项

10

关于 SSLServerSocket.setWantClientAuth:

如果将其设置为 true,则当客户端选择不发送证书时,协商过程会继续。
此外,我注意到如果客户端发送了证书不在信任存储库中,协商也不会失败。

那么这个设置的用例是什么?


Bruno:我尝试了Java和非Java客户端。 - Jim
2个回答

18
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.

一开始我认为当你使用 "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中引入的)。
您似乎在评论中表明您正在使用另一个JSSE提供程序,并且您的客户端无论是否将CA cert放在服务器的信任存储中,都会发送客户端证书,因为您的信任存储中还有另一个具有相同主题DN的不同CA cert。假设这两个CA证书具有不同的密钥(否则它们实际上将是相同的CA),那么这将是一个相当严重的错误:使用客户端证书身份验证的应用程序有权期望信任管理器已验证客户端证书(如JSSE参考指南中所述)。如果您正在使用Apache Tomcat,一旦它获取了客户端证书,远程连接将被视为使用此证书进行身份验证。如果此时servlet能够使用无法验证的客户端证书,则实际上未进行身份验证,这将是一个严重的缺陷。

OP说,如果客户端证书被发送并且不是信任存储的一部分,则协商也会继续。虽然我觉得这很奇怪。难道不应该协商失败吗? - Cratylus
你说得对。从 OP 中并不清楚(尽管我认为这就是 OP 在“我注意到如果客户端证书不在信任库中也会发生这种情况”中的意思)。让我们等待 OP 澄清这一点。 - Cratylus
@Cratylus 不,它不应该失败。你想的是“needClientAuth”。 - user207421
@Jim,很可能是这样。它是哪个库?如果它允许您从SSLSession获取客户端证书而不检查证书是否实际可信,则存在严重的错误。 - Bruno
@Jim,我已经编辑了我的回答,并将删除大部分评论(包括这个),因为它们很乱。在我的测试中,如果我提供一个无法根据信任存储库进行验证的客户端证书,连接会失败。 - Bruno
显示剩余16条评论

0

当您想要了解客户端凭据但不想在他们未发送凭据时终止通信时,可以使用它。与“needClientAuth”相反,如果客户端未发送凭据,则握手将失败。


我的问题是:当客户端发送一个不受信任的证书并且连接仍然继续时,这个设置实际上有什么用处? - Jim
@Jim 根据RFC 2246,客户端不允许发送不受信任的证书。当服务器请求证书时,它会发送其受信任的根证书,只有当客户端的签名链包含其中一个受信任的根证书时,才允许其发送证书。如果客户端拥有这样的证书,则无需向其呈现登录表单,但如果没有,则允许连接继续并导航到登录表单。 - user207421
我看到RFC在7.4.6中说:如果没有合适的证书可用,则客户端应发送一个不包含证书的证书消息。这是一个“SHOULD”而不是必须。所以我在想,如果在wantClientAuth中证书是可选的,那么如果发送的证书不受信任,它是否真的无关紧要?我的意思是,客户端可能根本没有发送证书,也能正常运行。这可能是我看到的原因吗?也就是说,连接被接受了? - Jim
另外,它并没有说明如果接受的发行者列表为空会发生什么。我的意思是,我在想如果我们设置了wantClientAuth并用“假”的信任管理器替换信任管理器会发生什么。 - Jim
检查我给Bruno的最后一条评论。我意识到了关于这种情况的一些事情。 - Jim
@Jim 如果接受的发行者列表为空,则客户端无法发送符合要求的证书。 - user207421

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