【问题标题】:SSL Server socket want auth optionSSL 服务器套接字需要身份验证选项
【发布时间】:2013-01-30 06:29:47
【问题描述】:

关于SSLServerSocket.setWantClientAuth:
如果设置为true,如果客户端选择不发送证书,则协商继续。
我还注意到,如果客户端发送证书不是信任库的一部分,也会发生这种情况。在这种情况下协商也不会失败。

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

【问题讨论】:

  • Bruno:Java 和非 Java 客户端我都试过了

标签: java security sockets ssl jsse


【解决方案1】:

(在多个 cmets 之后多次编辑。)

setWantClientAuth 用于请求客户端证书认证,但如果没有提供认证,则保持连接。 setNeedClientAuth 用于请求和要求客户端证书身份验证:如果没有提供合适的客户端证书,连接将终止。

您可以在Client Certificate section of the TLS specification 中找到有关此主题的更多信息。了解一点历史:

  • 在 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 名称中列出的颁发者之一颁发的客户端证书(或者如果客户端不能自己构建链,即a common problem)。 默认情况下,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 消息,但证书列表将为空(Wireshark 中的Certificates Length: 0)。在这里,客户端实际上并没有发送客户端证书,即使它的密钥库被配置为这样做,只是因为它找不到合适的匹配项。连接将继续(如果您尝试从服务器上的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 specification of the trust manager 一致:“TrustManager 的主要职责是确定提供的身份验证凭据是否应受信任。如果凭据不受信任,则连接将终止。

这里的关键点是,如果您能够从SSLSession 获取客户端证书,则该证书应该已经过信任管理器的身份验证(我的意思是SSLSession,您一旦获得握手已完成,SSLSocket.getSession() 不是您在使用 getHandshakeSession() 握手期间获得的那个,Java 7 中引入)。

您似乎在 cmets 中表明您正在使用另一个 JSSE 提供程序,并且无论 CA 证书是否在服务器的信任库中,您的客户端都在发送客户端证书,因为您还有另一个不同的 CA 证书在具有相同主题 DN 的信任库中。假设这两个 CA 证书具有不同的密钥(否则它们实际上是相同的 CA),这将是一个相当严重的错误:使用客户端证书身份验证的应用程序有权期望客户端证书已由信任管理器验证(如 JSSE 参考指南中所述)。如果您使用的是 Apache Tomcat,一旦它获得客户端证书,远程连接就被认为是通过此证书进行身份验证的。如果此时 servlet 能够使用无法验证的客户端证书,则实际上并未进行任何身份验证,这将是一个严重的缺陷。

【讨论】:

  • OP 说如果发送了客户端证书并且不是信任库的一部分,协商也会继续。不过我觉得这很奇怪。协商不应该失败吗?
  • @Cratylus,OP 实际上并没有说发送了客户端证书。我刚刚通过更改服务器上接受的颁发者列表让客户端发送了一个不受默认信任存储信任的证书,并且正如预期的那样,您会收到一个 PKIX 异常:连接失败。
  • 你是对的。从 OP 中并不清楚(虽然我认为这是 OP 在I noticed this also happens if the client certificate is not part of the truststore 中的意思)。让我们等待 OP 澄清这一点
  • @Cratylus 不,它不应该失败。您正在考虑“needClientAuth”。
  • @Bruno:当客户端发送不受信任的证书时,协商不会失败。使问题更清楚。我注意到您提到了PKIX 异常。您是否使用PKIX 作为信任管理器工厂?也许这就是区别,因为我尝试使用 SunX509 并且连接继续以获取未知证书。
【解决方案2】:

当您想知道客户端凭据是否发送但您不想停止通信时使用它。与“needClientAuth”相比,如果他不发送握手将失败。

【讨论】:

  • 我的问题是:因为当客户端发送一个不受信任的证书并且连接仍然继续时,这个设置什么时候真正有用?
  • @Jim RFC 2246 下不允许客户端发送不受信任的证书。当服务器请求证书时,它会发送其受信任的根证书,客户端只允许发送证书如果其签名者链包含这些受信任的根证书之一。一个有用的情况是,如果客户有这样的证书,您不需要向他提供登录表单,但如果他没有,您允许连接继续并导航到登录表单。
  • 我看到RFC在7.4.6:...If no suitable certificate is available, the client should send a certificate message containing no certificates中说。这是SHOULD不是必须的。所以我想知道。可能是因为证书在wantClientAuth中是可选的,如果证书发送不受信任,这并不重要?我的意思是客户可能根本没有发送任何证书并且很好。这可能是我所看到的原因吗?即要接受的连接?
  • 此外,它也没有说明如果接受的发行人列表是 empty 会发生什么。我的意思是我只是想知道如果我们设置 wantClientAuth 并用“假”替换信任管理器会发生什么" 信任管理器
  • 查看我对布鲁诺的最后评论。我意识到了一些关于这个场景的事情
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-01
  • 2018-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多