在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中没有可用的回调函数来告诉您这一点。
char a; if (recv(&a, 1, MSG_PEEK) == 0) return false;
然而,即使我知道服务器已经指示了握手错误,我看到客户端中的recv()调用成功了,而根据您所描述的情况,我本应该期望它失败。 - David Ritterrecv()
切换到SSL_read()
,它也表示成功读取。很抱歉我有些迟钝,我应该如何使用OpenSSL来解释返回的内容并确定是否为错误警报? - David RitterSSL_get_error()
,但我没有看到任何错误返回,所以我认为可能有其他部分的API我错过了。我需要再次检查我的代码,看看为什么服务器的SSL_do_handshake()
失败没有在客户端触发读取错误。谢谢你的帮助。 - David Ritter