当 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 本身由于底层协议而无法知道。