服务端 TLS 1.3
主线位置:握手主线的服务端一侧。ClientHello 到达后,
ExpectClientHello在一次handle里做完全部决定(版本、证书、套件、密钥交换组、要不要恢复会话),并把 ServerHello 到 Finished 的整批消息排进发送队列;之后只等客户端的 Finished,验过以后发会话票据,进入ExpectTraffic。
stateDiagram-v2 [*] --> ExpectClientHello ExpectClientHello --> ExpectClientHello: 发 HRR ExpectClientHello --> ExpectAndSkipRejectedEarlyData: HRR 且带 early_data ExpectAndSkipRejectedEarlyData --> ExpectClientHello ExpectClientHello --> ExpectFinished: 常见路径 ExpectClientHello --> ExpectEarlyData: 接受 0-RTT ExpectEarlyData --> ExpectFinished: EndOfEarlyData ExpectClientHello --> ExpectCertificate: 要客户端证书 ExpectCertificate --> ExpectCertificateVerify ExpectCertificate --> ExpectFinished: 可选且没给 ExpectCertificateVerify --> ExpectFinished ExpectFinished --> ExpectTraffic ExpectFinished --> ExpectQuicTraffic: QUIC
服务端的代码在 rustls/src/server/ 下:hs.rs 785 行,放 TLS 1.2 和 1.3 共用的 ClientHello 处理;tls13.rs 1604 行,放 TLS 1.3 的其余状态;server_conn.rs 1298 行,放 ServerConfig、ServerConnection、Acceptor 和几个 trait。状态机本身怎么驱动(State trait、Box<dyn State>、出错后的告警)见状态机,这里只讲服务端每一步做了什么决定。
实验平台:M2 Max、macOS 27、rustc 1.99.0,rustls 0.23.46,ring provider(显式 builder_with_provider),release 构建,客户端和服务端都在同一进程的内存里,write_tls 的输出直接交给对端的 read_tls。程序在 exp-rustls/hsb-lab/src/bin/ 下。
起点:ExpectClientHello
ServerConnection::new 调 ConnectionCore::for_server,第一个状态是 ExpectClientHello:
pub(super) struct ExpectClientHello {
pub(super) config: Arc<ServerConfig>,
pub(super) extra_exts: ServerExtensionsInput<'static>,
pub(super) transcript: HandshakeHashOrBuffer,
#[cfg(feature = "tls12")]
pub(super) session_id: SessionId,
#[cfg(feature = "tls12")]
pub(super) using_ems: bool,
pub(super) previous_hello: Option<PreviousClientHello>,
pub(super) send_tickets: usize,
}
transcript开始时是缓冲(HandshakeHashBuffer),因为还不知道套件,也就不知道用哪个哈希;选定套件后才变成增量哈希。转录哈希的细节见密钥日程。extra_exts只有 QUIC 用,放 QUIC 传输参数。previous_hello是None表示这是第一个 ClientHello;发过 HelloRetryRequest 之后,下一个ExpectClientHello带着Some,记住第一次的套件和有没有提供 PSK。
它的 handle 只有两行:
let (client_hello, sig_schemes) =
process_client_hello(&m, self.previous_hello.is_some(), cx)?;
self.with_certified_key(sig_schemes, client_hello, &m, cx)
拆成两步是为了 Acceptor:第一步不需要 ServerConfig,第二步需要。Acceptor 在两步之间把控制权交还给调用方,后面单独讲。
不看配置的检查:process_client_hello
process_client_hello 做的都是与配置无关的检查:
- 压缩方法里必须有
Null,否则发illegal_parameter。 check_aligned_handshake:ClientHello 之后,同一批记录里不能再跟别的握手消息。- SNI:
let sni = match &client_hello.server_name {
Some(ServerNamePayload::SingleDnsName(dns_name)) => Some(dns_name.to_lowercase_owned()),
Some(ServerNamePayload::IpAddress) => None,
Some(ServerNamePayload::Invalid) => {
return Err(cx.common.send_fatal_alert(
AlertDescription::IllegalParameter,
PeerMisbehaved::ServerNameMustContainOneHostName,
));
}
None => None,
};
名字先转小写再存进 cx.data.sni,证书选择器拿到的总是小写。RFC 6066 不允许在 host_name 里放 IP 字面量,有的客户端偏要放;rustls 解析成 IpAddress,然后当作客户端没发 SNI。发过 HelloRetryRequest 之后,第二个 ClientHello 的 SNI 必须和第一个相同,否则是 ServerNameDifferedOnRetry。
- 没有
signature_algorithms扩展就发handshake_failure:服务端需要它来挑证书和签名算法。
选版本
with_certified_key 第一件事是定版本:
let tls12_enabled = self
.config
.supports_version(ProtocolVersion::TLSv1_2, cx.common.protocol)
&& !self.previous_hello.is_some();
// Are we doing TLS1.3?
let version = if let Some(versions) = &client_hello.supported_versions {
if versions.tls13 && tls13_enabled {
ProtocolVersion::TLSv1_3
} else if !versions.tls12 || !tls12_enabled {
return Err(cx.common.send_fatal_alert(
AlertDescription::ProtocolVersion,
PeerIncompatible::Tls12NotOfferedOrEnabled,
));
// ...
- 客户端发了
supported_versions扩展:双方都支持 1.3 就是 1.3;否则退到 1.2,1.2 也不行就发protocol_version告警。 - 没发这个扩展的是老客户端,按
legacy_version判断,低于 1.2 直接拒绝。rustls 不实现 1.0 / 1.1。 supports_version不只看ServerConfig里开了哪些版本,还看 provider 里有没有这个版本的套件、协议是不是 QUIC(QUIC 只能 1.3)。- 发过 HelloRetryRequest 之后
tls12_enabled恒为假:HelloRetryRequest 本身就是 1.3 的消息,第二个 ClientHello 不能再退回 1.2。
一个只开 TLS 1.3 的 rustls 客户端连只开 TLS 1.2 的 rustls 服务端,两边的错误:
TLS 1.3-only client: server Err(PeerIncompatible(Tls12NotOfferedOrEnabled)); sent Alert 2; client Err(AlertReceived(ProtocolVersion))
选证书:ResolvesServerCert
版本定了,接下来选证书,再选套件。顺序是这样的原因在 TLS 1.2:1.2 的套件名里写着签名算法(ECDHE_ECDSA / ECDHE_RSA),套件能不能用取决于证书的密钥类型,所以先有证书。TLS 1.3 的套件和签名算法无关,usable_for_signature_algorithm 对 1.3 套件直接返回 true。
选证书之前,先把客户端的 signature_algorithms 按双方共有的套件过滤一遍(compatible_sigscheme_for_suites),然后拼出一个 ClientHello 交给 config.cert_resolver:
pub struct ClientHello<'a> {
pub(super) server_name: &'a Option<DnsName<'a>>,
pub(super) signature_schemes: &'a [SignatureScheme],
pub(super) alpn: Option<&'a Vec<ProtocolName>>,
pub(super) server_cert_types: Option<&'a [CertificateType]>,
pub(super) client_cert_types: Option<&'a [CertificateType]>,
pub(super) cipher_suites: &'a [CipherSuite],
// ...
pub(super) certificate_authorities: Option<&'a [DistinguishedName]>,
pub(super) named_groups: Option<&'a [NamedGroup]>,
}
全是借用,不拷贝。certificate_authorities 在 TLS 1.2 下总是 None:代码注释说这是遵守 TLS 1.2 RFC。
ResolvesServerCert 只有一个必须实现的方法:
pub trait ResolvesServerCert: Debug + Send + Sync {
fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<sign::CertifiedKey>>;
// ...
}
返回 None 时服务端发 access_denied 告警,错误是 General("no server certificate chain resolved")。rustls 自带两个实现:
with_single_cert用的SingleCertAndKey,不管客户端说什么都给同一张证书。ResolvesServerCertUsingSni,一个HashMap<String, Arc<CertifiedKey>>。add时用 webpki 检查证书确实对这个名字有效(注释说这只是帮服务端发现配置错误,与安全无关);resolve按 SNI 查表,客户端没发 SNI 就返回None。
两张自签证书(a.test、b.test)装进 ResolvesServerCertUsingSni,客户端分别连 b.test 和 c.test,再故意把 a.test 的证书登记成 c.test:
SNI b.test: server Ok(()); sent Handshake 122 [ServerHello/HRR] | ChangeCipherSpec 1 | ApplicationData 523; client Ok(())
SNI c.test: server Err(General("no server certificate chain resolved")); sent Alert 2; client Err(AlertReceived(AccessDenied))
add("c.test", cert for a.test) -> Err(InvalidCertificate(NotValidForNameContext { expected: DnsName("c.test"), presented: ["DnsName(\"a.test\")"] }))
客户端只看到一个 AccessDenied 告警,看不出是名字不对。排查时要看服务端日志。
resolve 是同步调用,在握手状态机内部执行。要查数据库、远程取证书这类需要 IO 的情况,trait 的文档指向 Acceptor。
选套件和组
choose_suite_and_kx_group 分三步:
- 遍历客户端的
supported_groups,按顺序为每个组在服务端 provider 里找对应的SupportedKxGroup,记下 ECDHE / FFDHE 是否可能。都不可能就是NoKxGroupsInCommon。 - 从服务端 provider 的套件里筛出可用的(版本对、协议对、密钥交换算法对、TLS 1.2 下签名算法对),再和客户端的套件列表求交:
let suite = if self.config.ignore_client_order {
suitable_suites_iter.find(|suite| client_suites.contains(&suite.suite()))
} else {
let suitable_suites = suitable_suites_iter.collect::<Vec<_>>();
client_suites
.iter()
.find_map(|client_suite| {
suitable_suites
.iter()
.find(|x| *client_suite == x.suite())
})
.copied()
}
.ok_or(PeerIncompatible::NoCipherSuitesInCommon)?;
默认(ignore_client_order: false)按客户端的顺序挑;设成 true 才按服务端的顺序。 3. 组从第一步的列表里取第一个与套件相容的:
let maybe_skxg = supported_groups
.iter()
.find_map(|maybe_skxg| match maybe_skxg {
Some(skxg) => suite
.usable_for_kx_algorithm(skxg.name().key_exchange_algorithm())
.then_some(*skxg),
None => None,
});
这个列表是按客户端 supported_groups 的顺序排的,ignore_client_order 管不到它。所以组总是按客户端的偏好选,服务端 provider 里 kx_groups 的顺序只决定「支不支持」。
[secp256r1, X25519](只为 secp256r1 发了密钥共享),服务端的组是 [X25519, secp256r1],协商出哪个组?会不会有 HelloRetryRequest?两边都是 ring provider,只改了 CryptoProvider::kx_groups。答案在文末。
一次 handle 发完第一批消息
选完参数,TLS 1.3 交给 tls13::CompleteClientHelloHandling 的 handle_client_hello。它在同一个调用里依次:
- 在客户端的
key_share里找选定组的那一份。没有就发 HelloRetryRequest,返回新的ExpectClientHello(下一节)。 - 如果客户端带了
pre_shared_key,逐个尝试解出会话、核对绑定值(见会话恢复与 0-RTT)。 emit_server_hello:用对方的公钥start_and_complete完成密钥交换,发 ServerHello,推进密钥日程,装上握手密钥。emit_fake_ccs:第一次 ClientHello 就成功时发一个假的 ChangeCipherSpec,给中间设备看(RFC 8446 附录 D.4 的兼容模式);QUIC 不发。- 往一个
HandshakeFlightTls13里加 EncryptedExtensions、(要客户端证书时)CertificateRequest、Certificate(客户端支持且服务端配了压缩器时发 CompressedCertificate)、CertificateVerify、Finished。恢复会话时跳过证书三件套。 flight.finish把整批消息放进一条加密记录。- 根据要不要客户端证书、有没有接受 0-RTT,返回
ExpectCertificate、ExpectEarlyData或ExpectFinished。
所以完整握手里服务端只经过 ExpectClientHello → ExpectFinished → ExpectTraffic 三个状态;客户端那边要走六个(见客户端 TLS 1.3)。实验里的第一批服务端消息是一次 write_tls 拿到的三条记录:
flight 2 S->C 664 bytes: Handshake 122 [ServerHello/HRR] | ChangeCipherSpec 1 | ApplicationData 526
526 字节那条记录里装的是哪四条消息、各多长,见一次握手的逐条解密。
send_half_rtt_data(默认 false)打开时,服务端在 Finished 之后马上可以发应用数据,不等客户端的 Finished;要客户端证书时这个选项被忽略,不给未认证的对端发数据。
HelloRetryRequest
ring provider 下,rustls 客户端的 ClientHello 只为一个组发密钥共享(provider 的第一个组,或上次记下的组;aws-lc-rs 默认的后量子组另有规则,见客户端 TLS 1.3)。服务端选中的组不在 key_share 里,就要发 emit_hello_retry_request:
let req = HelloRetryRequest {
legacy_version: ProtocolVersion::TLSv1_2,
session_id,
cipher_suite: suite.common.suite,
extensions: HelloRetryRequestExtensions {
key_share: Some(group),
supported_versions: Some(ProtocolVersion::TLSv1_3),
..Default::default()
},
};
// ...
transcript.rollup_for_hrr();
transcript.add_message(&m);
common.send_msg(m, false);
common.handshake_kind = Some(HandshakeKind::FullWithHelloRetryRequest);
- HelloRetryRequest 在线上就是一条 ServerHello,
random是 RFC 规定的固定值;它只告诉客户端「用这个组再来一次」,并且已经定下了套件。 rollup_for_hrr把转录里的第一个 ClientHello 换成它的哈希(RFC 8446 §4.4.1 的message_hash)。- 返回的下一个状态还是
ExpectClientHello,带上previous_hello。如果第一个 ClientHello 带了early_data,外面再包一层ExpectAndSkipRejectedEarlyData,把客户端已经发出、服务端解不开的早期数据按长度丢掉。
第二个 ClientHello 要过几道额外的检查:不能再带 early_data;第一次提供过 PSK 就不能撤回;协商出的套件必须和 HelloRetryRequest 里写的相同(CipherSuiteDifferedOnRetry);这次还没有所选组的密钥共享就是 RefusedToFollowHelloRetryRequest。
9fafe6dd「reject a second ClientHello that drops pre_shared_key」和 185a063b「reject a second ClientHello that changes the cipher suite」都首次发布在 0.23.45。之前服务端在发过 HelloRetryRequest 之后只记一个 done_retry: bool:第二个 ClientHello 撤掉 pre_shared_key、或者客户端改了套件列表让服务端选出另一个套件,都会被接受。两个提交分别加了 offered_psk_before_retry: bool 和 suite_before_retry: Option<CipherSuite> 两个字段,注释引的是 RFC 9846(RFC 8446 的修订版)§4.2.2 和 §4.2.4。随后的 c4e92e8c「Keep data needed for HRR processing together」(同在 0.23.45)把三个字段合成今天的 previous_hello: Option<PreviousClientHello>:Some 本身就表示「发过 HelloRetryRequest」,不会再出现 done_retry 为假、suite_before_retry 却有值这种组合。
实验:客户端 provider 的组改成 [secp256r1, X25519],服务端只有 [X25519]。服务端选 X25519(客户端列表里第二个、服务端唯一支持的),而客户端只发了 secp256r1 的共享:
A default/default: kind Full, group Some(X25519), 4 flights, 1164 bytes
flight 1 C->S 236 bytes: Handshake 231 [ClientHello]
flight 2 S->C 664 bytes: Handshake 122 [ServerHello/HRR] | ChangeCipherSpec 1 | ApplicationData 526
flight 3 C->S 80 bytes: ChangeCipherSpec 1 | ApplicationData 69
flight 4 S->C 184 bytes: ApplicationData 179
B client[P256,X25519] server[X25519]: kind FullWithHelloRetryRequest, group Some(X25519), 6 flights, 1521 bytes
flight 1 C->S 267 bytes: Handshake 262 [ClientHello]
flight 2 S->C 99 bytes: Handshake 88 [ServerHello/HRR] | ChangeCipherSpec 1
flight 3 C->S 240 bytes: ChangeCipherSpec 1 | Handshake 229 [ClientHello]
flight 4 S->C 657 bytes: Handshake 122 [ServerHello/HRR] | ApplicationData 525
flight 5 C->S 74 bytes: ApplicationData 69
flight 6 S->C 184 bytes: ApplicationData 179
(记录列表是实验程序按 5 字节记录头切出来的;明文握手记录里再按 4 字节消息头列出消息类型,类型 2 既可能是 ServerHello 也可能是 HelloRetryRequest。)
- 多了两批:88 字节的 HelloRetryRequest 和第二个 ClientHello,等于多一个往返(RTT)。总字节数 1521 对 1164,多 357。
- 第一个 ClientHello 是 262 字节,比默认的 231 大 31:secp256r1 的公钥 65 字节,X25519 是 32 字节。
- 假的 ChangeCipherSpec 只发一次:服务端跟在 HelloRetryRequest 后面发,所以 flight 4 里没有;客户端跟在第二个 ClientHello 前面发,所以 flight 5 里没有。
- 服务端的状态多走了一次
ExpectClientHello,其余不变。
内存里两种握手的耗时(各 300 次,取最小值和中位数;机器负载 5 左右):
time n=300: full min 223 us median 229 us; with HRR min 240 us median 245 us
CPU 上只多 17 µs(多生成的一把 secp256r1 密钥、多处理的两条消息都在里面)。真正的代价是网络上多出的那个往返:内存里没有传输时延,实际连接上它是一个完整的 RTT。
同一个 ClientConfig 第二次连同一个服务器,就不再有 HelloRetryRequest:客户端把服务端选中的组记在会话存储里(kx_hint),下次第一个 ClientHello 直接发这个组的共享,细节见客户端 TLS 1.3。服务端关掉票据(send_tls13_tickets = 0),排除会话恢复的影响:
C2 no tickets, 1st connection: kind FullWithHelloRetryRequest, group Some(X25519), 5 flights, 1338 bytes
C2 no tickets, 2nd connection: kind Full, group Some(X25519), 3 flights, 977 bytes
Acceptor:先读 ClientHello,再给配置
ServerConnection::new 要求一开始就给出 ServerConfig。按 SNI 选证书靠 ResolvesServerCert 就够了;但如果配置本身要变(不同的 ALPN、不同的客户端证书要求、证书要从远处异步取),需要先看见 ClientHello。Acceptor 就是为此准备的:
pub fn accept(&mut self) -> Result<Option<Accepted>, (Error, AcceptedAlert)> {
let Some(mut connection) = self.inner.take() else {
return Err((
Error::General("Acceptor polled after completion".into()),
AcceptedAlert::empty(),
));
};
let message = match connection.first_handshake_message() {
Ok(Some(msg)) => msg,
Ok(None) => {
self.inner = Some(connection);
return Ok(None);
}
Err(err) => return Err((err, AcceptedAlert::from(connection))),
};
let mut cx = Context::from(&mut connection);
let sig_schemes = match hs::process_client_hello(&message, false, &mut cx) {
// ...
};
Ok(Some(Accepted {
connection,
message,
sig_schemes,
}))
}
Acceptor里已经是一个完整的ConnectionCommon,状态是占位的Accepting(它的handle只返回错误,永远不会被调到)。去帧、拼接跨记录的 ClientHello 都复用普通连接的代码。- ClientHello 还不完整时返回
Ok(None),调用方接着read_tls。 - 拿到 ClientHello 后只跑
process_client_hello,也就是上面那段与配置无关的检查。 - 出错时返回
AcceptedAlert,里面是要发给客户端的告警字节,调用方自己写出去。
Accepted::into_connection 拿调用方给的 ServerConfig 新建一个 ExpectClientHello,直接调 with_certified_key 处理那条保存下来的 ClientHello,再把状态换进连接。之后它就是一个普通的 ServerConnection。
实验把 ClientHello 切成两段喂给 Acceptor,按 SNI 选配置,再完成握手;最后喂一段明文 HTTP 请求:
after 100 bytes: accept() -> Ok(false)
after 233 bytes: server_name Some("a.test"), alpn None, 10 cipher suites, 9 signature schemes, named_groups Some([X25519, secp256r1, secp384r1])
handshake via Acceptor: kind Some(Full), sni Some("a.test")
flight 1 S->C 660 bytes: Handshake 122 [ServerHello/HRR] | ChangeCipherSpec 1 | ApplicationData 522
flight 2 C->S 80 bytes: ChangeCipherSpec 1 | ApplicationData 69
flight 3 S->C 184 bytes: ApplicationData 179
plain HTTP: Err(InvalidMessage(InvalidContentType)) "received corrupt message of type InvalidContentType"; alert bytes [21, 3, 3, 0, 2, 2, 50]
Ok(false)就是Ok(None):233 字节的 ClientHello 只来了 100 字节。- 10 个套件是 ring provider 的 3 个 TLS 1.3 套件和 6 个 TLS 1.2 套件,再加
TLS_EMPTY_RENEGOTIATION_INFO_SCSV。 - 明文 HTTP 请求的第一个字节
G(0x47)不是合法的记录类型。告警字节是一条记录:类型 21(alert)、版本 0x0303、长度 2、级别 2(fatal)、描述 50(decode_error)。
问: 用 Acceptor 给不同的 SNI 发不同的 ServerConfig,这些配置能共用同一个 session_storage 来提高恢复率吗?
答: 能共用,但 rustls 不替你检查后果。ServerConfig 的文档举了例子:配置 A 要求客户端证书、配置 B 不要求,两者共用会话存储时,一个在 B 上没认证过的客户端拿到的会话可以在 A 上恢复,A 会以为它认证过。文档要求共用存储(或 ticketer)的配置在 verifier 和 cert_resolver 上等价,并明确写了「Unlike ClientConfig, rustls does not enforce any policy here」。客户端那边会话值里存着校验器的弱引用,换了校验器就不恢复(见会话恢复与 0-RTT)。
收尾:ExpectFinished
客户端的 Finished 到达,ExpectFinished 用常数时间比较(ConstantTimeEq)核对 verify_data,不对就发 decrypt_error。对了以后:
let (key_schedule_traffic, resumption) =
key_schedule_before_finished.into_traffic(self.transcript.current_hash());
let mut flight = HandshakeFlightTls13::new(&mut self.transcript);
for _ in 0..self.send_tickets {
Self::emit_ticket(&mut flight, self.suite, cx, &resumption, &self.config)?;
}
flight.finish(cx.common);
- 读方向这时才换成应用密钥。写方向在发 Finished 时就换了,所以 half-RTT 数据才可能存在。
send_tickets默认是ServerConfig::send_tls13_tickets的 2,客户端发了 RFC 9149 的ticket_request且服务端设了max_tls13_tickets时按客户端的请求取较小值。两张票放进同一批,实验里就是最后那条 179 字节的记录。票据里是什么、存在哪,见会话恢复与 0-RTT。- 返回
ExpectTraffic,QUIC 下是ExpectQuicTraffic,后者拒收一切 TLS 消息,因为 QUIC 连接上的消息走 QUIC 自己的帧(见 TLS 1.2 与其他)。
要客户端证书时中间多 ExpectCertificate(或先经过 ExpectCertificateOrCompressedCertificate)和 ExpectCertificateVerify;客户端证书可选且客户端发了空证书时,ExpectCertificate 直接跳到 ExpectFinished。客户端证书怎么校验见客户端证书与自定义校验。
动脑筋答案
secp256r1,没有 HelloRetryRequest:
D client[P256,X25519] server[X25519,P256]: kind Full, group Some(secp256r1), 4 flights, 1227 bytes
flight 1 C->S 267 bytes: Handshake 262 [ClientHello]
flight 2 S->C 697 bytes: Handshake 155 [ServerHello/HRR] | ChangeCipherSpec 1 | ApplicationData 526
flight 3 C->S 80 bytes: ChangeCipherSpec 1 | ApplicationData 69
flight 4 S->C 184 bytes: ApplicationData 179
choose_suite_and_kx_group 按客户端 supported_groups 的顺序找第一个服务端支持的组,第一个就是 secp256r1,服务端也支持,于是选它;客户端恰好为它发了共享,不用重试。服务端 provider 把 X25519 排在前面不起作用。ServerHello 是 155 字节而不是 122,多出的 33 字节同样是 secp256r1 公钥比 X25519 长。
小结
- 服务端在
ExpectClientHello的一次handle里做完所有决定,并把 ServerHello 到 Finished 一起排进队列;完整握手只经过三个状态。 - 顺序是:与配置无关的检查(
process_client_hello)→ 版本 → 证书(ResolvesServerCert)→ 套件和组。证书在套件之前,是 TLS 1.2 套件依赖签名算法的缘故。 - 套件默认按客户端顺序,
ignore_client_order可改;组总是按客户端顺序,这个开关管不到。 - 选中的组没有密钥共享就发 HelloRetryRequest:多一个往返,实测多 357 字节、CPU 多 17 µs;客户端记下组之后下次不再触发。
Acceptor把handle的两半拆开,先给调用方看 ClientHello,再用调用方选的ServerConfig继续。- 客户端 Finished 验过之后,服务端换读密钥、默认发 2 张票据,进入
ExpectTraffic。