【问题标题】:Tls 1.3 client does not report failed handshake when client certificate verification by server failed当服务器验证客户端证书失败时,tls 1.3 客户端不报告握手失败
【发布时间】:2020-06-18 21:37:34
【问题描述】:

我有一个使用 OpenSSL 的 C 客户端,当在服务器上调用 SSL_do_handshake() 期间使用的证书在服务器端验证失败时,该客户端未通过测试。当应用程序使用 TLS 1.2 时,服务器上的 SSL_do_handshake() 失败会在调用 SSL_do_handshake() 作为失败返回值时报告给客户端。

将我的应用程序升级到 OpenSSL 1.1.1 和 TLS 1.3 时,我注意到虽然验证错误仍在服务器上发生,但它不再报告给客户端。

我知道握手协议已完全重写为 TLS 1.3 的一部分,但是似乎有了所有可用的各种回调,我应该能够在客户端以某种方式确定身份验证失败而不必尝试将数据写入服务器。

有没有其他人遇到过这种情况,他们可以推荐一条前进的道路吗?

【问题讨论】:

    标签: ssl openssl tls1.3


    【解决方案1】:

    TLSv1.2 和 TLSv1.3 中的服务器和客户端都认为握手完成时,他们都写了“完成”消息,并从对等方收到了消息。这是 TLSv1.2 中握手的样子(取自 RFC5246):

          Client                                               Server
    
          ClientHello                  -------->
                                                          ServerHello
                                                         Certificate*
                                                   ServerKeyExchange*
                                                  CertificateRequest*
                                       <--------      ServerHelloDone
          Certificate*
          ClientKeyExchange
          CertificateVerify*
          [ChangeCipherSpec]
          Finished                     -------->
                                                   [ChangeCipherSpec]
                                       <--------             Finished
          Application Data             <------->     Application Data
    

    因此,您可以在此处看到客户端在与服务器的第二次通信中发送其证书和已完成消息。然后它会等待从服务器接收到 ChangeCipherSpec 和 Finished 消息,然后才认为握手“完成”并可以开始发送应用程序数据。

    这是取自 RFC8446 的 TLSv1.3 的等效流程:

           Client                                           Server
    
    Key  ^ ClientHello
    Exch | + key_share*
         | + signature_algorithms*
         | + psk_key_exchange_modes*
         v + pre_shared_key*       -------->
                                                      ServerHello  ^ Key
                                                     + key_share*  | Exch
                                                + pre_shared_key*  v
                                            {EncryptedExtensions}  ^  Server
                                            {CertificateRequest*}  v  Params
                                                   {Certificate*}  ^
                                             {CertificateVerify*}  | Auth
                                                       {Finished}  v
                                   <--------  [Application Data*]
         ^ {Certificate*}
    Auth | {CertificateVerify*}
         v {Finished}              -------->
           [Application Data]      <------->  [Application Data]
    

    TLSv1.3 的一个优点是它加快了完成握手所需的时间。在 TLSv1.3 中,客户端发送回其证书和已完成消息之前从服务器接收“已完成”消息。当客户端发送其“Finished”消息时,它已经收到“Finished”,因此握手已完成,它可以立即开始发送应用程序数据。

    这当然意味着客户端在下一次从服务器读取数据之前不会知道服务器是否接受了证书。如果它已被拒绝,那么客户端将读取的下一个内容将是失败警报(否则它将是正常的应用程序数据)。

    我知道握手协议已完全重写为 TLS 1.3 的一部分,但是似乎有了所有可用的各种回调,我应该能够在客户端以某种方式确定身份验证失败而不必尝试将数据写入服务器。

    重要的不是向服务器写入数据——而是读取数据。只有这样您才能知道服务器是否发送了警报或只是发送了正常的应用程序数据。在读取该数据之前,OpenSSL 中没有可用的回调可以告诉您这一点 - 因为 OpenSSL 本身由于底层协议而无法知道。

    【讨论】:

    • 感谢您的解释。这就说得通了。正如我之前提到的,我的客户端代码仅依赖函数 SSL_do_handshake() 返回错误来检测服务器是否有任何证书错误。我已经在客户端中添加了这个额外的检查:char a; if (recv(&amp;a, 1, MSG_PEEK) == 0) return false; } 但是,即使我知道服务器已指示握手错误,我看到客户端中的 recv() 调用成功,鉴于您所描述的,我预计它会失败。
    • 如果服务器出现故障,它会向客户端发送警报。这是 TLS 级别的错误消息。 recv() 仅在 TCP 级别工作,因此它将成功地看到“某物”的到来。由 OpenSSL 来解释它,确定它是一个错误警报,然后客户端也会失败。
    • 我从recv()切换到SSL_read(),也说明读取成功。抱歉我太密集了,我应该如何检查 OpenSSL 以解释返回的内容并确定它是否是错误警报?
    • 如果您收到警报,那么 SSL_read() 应该返回
    • 哦,好吧。抱歉,我知道SSL_get_error() 我没有看到任何错误返回,所以我认为我错过了 API 的其他部分。我将不得不再次检查我的代码,看看为什么服务器 SSL_do_handshake() 故障没有在客户端触发读取错误。感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 2019-02-28
    • 2012-04-28
    • 2016-01-18
    • 2018-05-15
    • 1970-01-01
    • 2020-02-14
    • 1970-01-01
    • 2016-07-22
    相关资源
    最近更新 更多