TLS 1.3客户端在服务器证书验证失败时未报告握手失败。

5
我有一个使用OpenSSL的C客户端,在服务器端SSL_do_handshake()调用期间使用未经验证的证书时会失败。当应用程序使用TLS 1.2时,服务器上的SSL_do_handshake()故障将在客户端调用SSL_do_handshake()时报告为失败返回值。
当我将应用程序升级到OpenSSL 1.1.1和TLS 1.3后,我注意到虽然验证错误仍在服务器上发生,但不再向客户端报告。
我知道在TLS 1.3中握手协议完全被重写,但是似乎有多种可用的回调函数,我应该能够在客户端确定认证失败,而无需尝试向服务器写入数据。
有其他人遇到过这个问题吗?他们能推荐一条前进的路吗?
1个回答

6

在TLSv1.2和TLSv1.3中,服务器和客户端会在彼此写入“Finished”消息并且接收到对方的“Finished”消息时认为握手过程已经完成。以下是TLSv1.2中握手的流程(摘自RFC5246):

      Client                                               Server

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

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

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

       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”消息。当客户端发送其“Finished”消息时,它已经接收到了“Finished”,因此握手已经完成,它可以立即开始发送应用程序数据。
当然,这意味着客户端只有在下一次读取来自服务器的数据时才会知道服务器是否接受了证书。如果被拒绝,则客户端读取的下一件事将是失败提示(否则将是正常的应用程序数据)。
我知道握手协议在TLS 1.3中完全重写了,但似乎由于可用的各种回调函数,我应该能够在客户端确定身份验证失败而无需尝试向服务器写入数据。
重要的不是向服务器写入数据,而是读取数据。只有在读取数据后,您才会知道服务器是否发送了警报或仅发送了普通应用程序数据。在那之前,由于基础协议,OpenSSL本身并不知道,因此在OpenSSL中没有可用的回调函数来告诉您这一点。

谢谢您的解释,我明白了。正如我之前所提到的,我的客户端代码仅依赖于函数SSL_do_handshake()返回错误来检测服务器是否有任何证书错误。我在客户端中添加了这个额外的检查:char a; if (recv(&a, 1, MSG_PEEK) == 0) return false;然而,即使我知道服务器已经指示了握手错误,我看到客户端中的recv()调用成功了,而根据您所描述的情况,我本应该期望它失败。 - David Ritter
如果服务器出现故障,它会向客户端发送警报。这是一个TLS级别的错误消息。recv()仅在TCP级别上工作,因此它将成功地看到“某些东西”的到来。由OpenSSL来解释它,决定它是否为错误警报,只有在这种情况下客户端才会失败。 - Matt Caswell
我从recv()切换到SSL_read(),它也表示成功读取。很抱歉我有些迟钝,我应该如何使用OpenSSL来解释返回的内容并确定是否为错误警报? - David Ritter
如果您收到了警报,则SSL_read()应返回<=0。然后,您应该使用SSL_get_error()来确定错误是否致命(警报将给出SSL_ERROR_SSL结果)。如果SSL_read()成功,则您已成功从服务器读取应用程序数据(这就是SSL_read()的作用)。如果它向您发送应用程序数据,则服务器未失败。 - Matt Caswell
好的。抱歉,我知道SSL_get_error(),但我没有看到任何错误返回,所以我认为可能有其他部分的API我错过了。我需要再次检查我的代码,看看为什么服务器的SSL_do_handshake()失败没有在客户端触发读取错误。谢谢你的帮助。 - David Ritter

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