接上文html
正如咱们在 section-2 中讨论的,TLS 使用一组通用的消息用于身份验证,密钥确认和握手的正确性:Certificate, CertificateVerify 和 Finished。(PSK binders 也以相似的方式进行密钥确认)。这三条消息老是做为握手消息的最后三条消息。Certificate 和 CertificateVerify 消息以下面描述的那样,只在某些状况才会发送。Finished 的消息老是做为认证块的一部分发送。这些消息使用从 sender_handshake_traffic_secret 派生出来的密钥进行加密。git
Authentication 消息的计算统一采用如下的输入方式:github
基于这些输入,消息包含:算法
Certificate:
用于认证的证书和链中任何支持的证书。请注意,基于证书的 Client 身份验证在 PSK 握手流中不可用(包括 0-RTT)数据库
CertificateVerify:
根据 Transcript-Hash(Handshake Context, Certificate) 的值得出的签名缓存
Finished:
根据 Transcript-Hash(Handshake Context, Certificate, CertificateVerify) 的值得出的 MAC 。使用从 Base key 派生出来的 MAC key 计算的 MAC 值。安全
对于每一个场景,下表定义了握手上下文和 MAC Base Key性能优化
+-----------+-------------------------+-----------------------------+
| Mode | Handshake Context | Base Key |
+-----------+-------------------------+-----------------------------+
| Server | ClientHello ... later | server_handshake_traffic_ |
| | of EncryptedExtensions/ | secret |
| | CertificateRequest | |
| | | |
| Client | ClientHello ... later | client_handshake_traffic_ |
| | of server | secret |
| | Finished/EndOfEarlyData | |
| | | |
| Post- | ClientHello ... client | client_application_traffic_ |
| Handshake | Finished + | secret_N |
| | CertificateRequest | |
+-----------+-------------------------+-----------------------------+
复制代码
TLS 中的许多加密计算都使用了哈希副本。这个值是经过级联每一个包含的握手消息的方式进来哈希计算的,它包含握手消息头部携带的握手消息类型和长度字段,可是不包括记录层的头部。例如:服务器
Transcript-Hash(M1, M2, ... Mn) = Hash(M1 || M2 || ... || Mn)
复制代码
做为此通常规则的例外,当 Server 用一条 HelloRetryRequest 消息来响应一条 ClientHello 消息时,ClientHello1 的值替换为包含 Hash(ClientHello1)的握手类型为 "message_hash" 的特殊合成握手消息。例如:cookie
Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
Hash(message_hash || /* Handshake type */
00 00 Hash.length || /* Handshake message length (bytes) */
Hash(ClientHello1) || /* Hash of ClientHello1 */
HelloRetryRequest || ... || Mn)
复制代码
设计这种结构的缘由是容许 Server 经过在 cookie 中仅存储 ClientHello1 的哈希值来执行无状态 HelloRetryRequest,而不是要求它导出整个中间哈希状态。
具体而言,哈希副本始终取自于下列握手消息序列,从第一个 ClientHello 开始,仅包括已发送的消息:ClientHello, HelloRetryRequest, ClientHello, ServerHello, EncryptedExtensions, server CertificateRequest, server Certificate, server CertificateVerify, server Finished, EndOfEarlyData, client Certificate, client CertificateVerify, client Finished。
一般上,实现方能够下面的方法来实现哈希副本:根据协商的哈希来维持一个动态的哈希副本。请注意,随后的握手后认证不会相互包含,只是经过主握手结束的消息。
此消息将端点的证书链发给对端。
每当约定的密钥交换方法是用证书进行认证(这包括本文档中除了 PSK 之外定义的全部密钥交换方法)的时候,Server 就必须发送 Certificate 消息。
当且仅当 Server 经过发送 CertificateRequest 消息请求 Client 认证时,Client 必须发送 Certificate 消息。
若是 Server 请求 Client 认证但没有合适的证书可用,则 Client 必须发送不包含证书的证书消息(例如,具备长度为 0 的 "certificate_list" 字段)。Finished 消息必须发送,不管 Certificate 消息是否为空。
Certificate 消息的结构体是:
enum {
X509(0),
RawPublicKey(2),
(255)
} CertificateType;
struct {
select (certificate_type) {
case RawPublicKey:
/* From RFC 7250 ASN.1_subjectPublicKeyInfo */
opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;
case X509:
opaque cert_data<1..2^24-1>;
};
Extension extensions<0..2^16-1>;
} CertificateEntry;
struct {
opaque certificate_request_context<0..2^8-1>;
CertificateEntry certificate_list<0..2^24-1>;
} Certificate;
复制代码
certificate_request_context:
若是此消息是响应 CertificateRequest 消息的,则该消息中的 certificate_request_context 的值不为 0。不然(在 Server 认证的状况下),该字段应为零长度。
certificate_list:
这是一个 CertificateEntry 结构的序列(链),每一个结构包含单个证书和一组扩展。
extensions:
CertificateEntry 的一组扩展值。"Extension" 的格式在 [Section 4.2] 中定义了。有效的扩展包括 OCSP 状态扩展 [RFC6066] 和 SignedCertificateTimestamp [RFC6962] 扩展。将来能够为此消息定义一些新的扩展。Server 的 Certificate 消息中的扩展必须对应于 ClientHello 消息中的扩展。Client 的 Certificate 消息中的扩展必须对应于 Server 的 CertificateRequest 消息中的扩展。若是一个扩展适应用于整个链,它应该被包括在第一个 CertificateEntry 中。
若是没有在 EncryptedExtensions 中协商相应的证书类型扩展名 ("server_certificate_type" 或 "client_certificate_type"),或者协商了 X.509 证书类型,则每一个 CertificateEntry 都要包含 DER 编码的 X.509 证书。发件者的证书必须位于列表中的第一个 CertificateEntry 中。以后的每一个证书都应该直接证实其前面的证书。因为证书验证要求信任锚独立分发,所以能够从链中省略指定信任锚的证书(前提是已知支持的对等方拥有可省略的证书)。
注意:TLS 1.3 以前的版本,"certificate_list" 排序须要每一个证书要证实紧接在其前面的证书,然而,一些实现容许一些灵活性。Server 有时为了过渡的目的而发送当前和已弃用的中间体,而其余的配置不正确,但这些状况仍然能够正确地验证。为了最大程度的兼容性,全部实现应该准备处理多是可有可无的证书和 TLS 版本的任意排序,但最终实体证书(排序的顺序)必须是第一个。
若是协商了 RawPublicKey 证书类型,则 certificate_list 必须包含不超过一个CertificateEntry,CertificateEntry 中包含定义在 [RFC7250], Section 3 中的 ASN1_subjectPublicKeyInfo 值。
OpenPGP 证书类型禁止在 TLS 1.3 中使用。
Server 的 certificate_list 必须始终是非空的。若是 Client 没有适当的证书要发送以响应 Server 的身份验证请求,则会发送空的 certificate_list。
[RFC6066] 和 [RFC6961] 提供了协商 Server 向 Client 发送 OCSP 响应的扩展。 在 TLS 1.2 及如下版本中,Server 回复空的扩展名以表示对此扩展的协商,而且在 CertificateStatus 消息中携带 OCSP 信息。在 TLS 1.3 中,Server 的 OCSP 信息在包含相关证书的 CertificateEntry 中的扩展中。特别的,来自 Server 的 "status_request" 扩展的主体必须是分别在 [RFC6066] 和 [RFC6960] 中定义的 CertificateStatus 结构。
注意:status_request_v2 扩展 [RFC6961] 已经废弃了,TLS 1.3 不能根据它是否存在或者根据它的信息来出来 ClientHello 消息。特别是,禁止在 EncryptedExtensions, CertificateRequest 和 Certificate 消息中发送 status_request_v2 扩展。TLS 1.3 的 Server 必需要可以处理包含它的 ClientHello 消息,由于这条消息多是由但愿在早期协议版本中使用它的 Client 发送的。
Server 能够经过在其 CertificateRequest 消息中发送空的 "status_request" 扩展来请求 Client 使用其证书来作 OCSP 的响应。若是 Client 选择性的发送 OCSP 响应,则其 "status_request" 扩展的主体必须是在 [RFC6966] 中定义的 CertificateStatus 结构。
相似地,[RFC6962] 为 Server 提供了一种机制,用在 TLS 1.2 及更低版本中的,可在 ServerHello 中发送签名证书时间戳 (SCT) 的扩展。 在 TLS 1.3 中,Server 的 SCT 信息在 CertificateEntry 的扩展中。
如下规则适用于 Server 发送的证书:
Server 的终端实体证书的公钥(和相关限制)必须与 Client的 "signature_algorithms" 扩展(目前为RSA,ECDSA 或 EdDSA)中的所选认证算法兼容。
证书必须容许密钥用于签名(即,若是存在密钥用法扩展,则必须设置 digitalSignature 位),并在 Client 的"signature_algorithms"/"signature_algorithms_cert" 扩展中指示签名方案。
"server_name" [RFC6066] 和 "certificate_authorities" 扩展用于指导证书选择。因为 Server 可能须要存在 "server_name" 扩展名,所以 Client 应该在适用时发送此扩展名。
若是 Server 可以提供证书链,Server 全部的证书都必须由 Client 提供的签名算法签名。自签名的证书或预期为信任锚的证书不会做为链的一部分进行验证,所以可使用任何算法进行签名。
若是 Server 不能产生只经过所指示的支持的算法签名的证书链,则它应当经过向 Client 发送其选择的证书链来继续握手,该证书链可能会包括 Client 不知道可否支持的算法。这个回退链能够只在 Client 容许的状况下才可使用已弃用的 SHA-1 哈希算法,其余状况都必须禁止使用 SHA-1 哈希算法。
若是 Client 没法使用提供的证书构造可接受的证书链,那么必须停止握手。停止握手并发送证书相关的 alert 消息(默认的,发送 "unsupported_certificate" alert 消息)
若是 Server 有多张证书,它会根据上述标准(除了其余标准之外,如传输层端点,本地配置和首选项)选择其中的一个证书。
如下的规则适用于 Client 发送的证书:
若是 CertificateRequest 消息中 "certificate_authorities" 扩展不为空,则证书链中的至少一个证书应该由所列出的 CA 之一发布的。
证书必须使用可接受的签名算法签名,如第 4.3.2 节所述。注意,这放宽了在 TLS 的先前版本中发现的证书签名算法的约束。
若是 CertificateRequest 消息包含非空的 "oid_filters" 扩展,则终端实体证书必须匹配 Client 识别的扩展 OID,如第 4.2.5 节中所述。
一般,详细的证书验证程序超出了 TLS 的范围(参见[RFC5280])。 本节提供特定于 TLS 的要求。
若是 Server 提供空的证书消息,则 Client 必须使用 "decode_error" alert 消息停止握手。
若是 Client 没有发送任何证书(即,它发送一个空的证书消息),Server 能够自行决定是否在没有 Client 认证的状况下继续握手,或者使用 "certificate_required" alert 消息停止握手。此外,若是证书链的某些方面是不可接受的(例如,它未由已知的可信 CA 签名),则 Server 能够自行决定是继续握手(考虑 Client 尚未通过身份验证)仍是停止握手。
任何端点接收任何须要使用任何签名算法使用 MD5 哈希验证的证书都必须使用 "bad_certificate" alert 消息停止握手。不推荐使用 SHA-1,而且建议任何接收任何使用 SHA-1 哈希使用任何签名算法验证的证书的端点都会使用 "bad_certificate" alert 消息停止握手。为清楚起见,这意味着端点能够接受这些算法用于自签名或信任锚的证书。
建议全部端点尽快转换为 SHA-256 或更好的算法,以保持与当前正在逐步淘汰 SHA-1 支持的实现的互操做性。
请注意,包含一个签名算法的密钥的证书可使用不一样的签名算法进行签名(例如,使用 ECDSA 密钥签名的 RSA 密钥)。
此消息用于提供端点拥有与其证书对应的私钥的明确证据。CertificateVerify 消息还为到此为止的握手提供完整性。Server 必须在经过证书进行身份验证时发送此消息。每当经过证书进行身份验证时(即,当证书消息非空时),Client 必须发送此消息。发送时,此消息必须在 Certificate 消息以后当即出现,而且紧接在 Finished 消息以前。
这条消息的结构体是:
struct {
SignatureScheme algorithm;
opaque signature<0..2^16-1>;
} CertificateVerify;
复制代码
algorithm 字段指定使用的签名算法(有关此类型的定义,请参见第 4.2.3 节)。signature 字段是使用该算法的数字签名。签名中涵盖的内容是第 4.4.1 节中描述的哈希输出,即:
Transcript-Hash(Handshake Context, Certificate)
复制代码
计算数字签名是级联计算的:
设计这个结构目的是为了防止对先前版本的 TLS 的攻击,其中 ServerKeyExchange 格式意味着攻击者能够得到具备所选 32 字节前缀(ClientHello.random)的消息的签名。 最初的 64 字节填充将清除 Server 控制的 ServerHello.random 中的前缀。
Server 签名的上下文字符串是 "TLS 1.3,Server CertificateVerify"。Client 签名的上下文字符串是 "TLS 1.3,Client CertificateVerify"。它用于在不一样的上下文中提供签名之间的分离,帮助抵御潜在的跨协议攻击。
例如,若是 hash副本 是 32 字节 01(这个长度对 SHA-256 有意义),Server 的 CertificateVerify 的数字签名所涵盖的内容将是:
2020202020202020202020202020202020202020202020202020202020202020
2020202020202020202020202020202020202020202020202020202020202020
544c5320312e332c207365727665722043657274696669636174655665726966
79
00
0101010101010101010101010101010101010101010101010101010101010101
复制代码
在发送方,用于计算 CertificateVerify 消息的签名字段的过程做为输入:
数字签名算涵盖的内容
与上一条消息中发送的证书对应的私有签名密钥
若是由 Server 发送 CertificateVerify 消息,则签名算法必须是 Client "signature_algorithms" 扩展中提供的,除非在没有不支持的算法的状况下不能生成有效的证书链(除非当前支持的算法都不能生成有效的证书链)。
若是由 Client 发送,则签名中使用的签名算法必须是 CertificateRequest 消息中 "signature_algorithms" 扩展的 supported_signature_algorithms 字段中存在的签名算法之一。
另外,签名算法必须与发送者的终端实体证书中的密钥兼容。不管 RSASSA-PKCS1-v1_5 算法是否出如今 "signature_algorithms" 中,RSA 签名都必须使用 RSASSA-PSS 算法。SHA-1 算法禁止用于 CertificateVerify 消息的任何签名。
本规范中的全部 SHA-1 签名算法仅定义用于旧证书,而且对 CertificateVerify 签名无效。
CertificateVerify 消息的接收者必须验证签名字段。验证过程做为输入:
数字签名所涵盖的内容
在关联的证书消息中找到的最终实体证书中包含的公钥
在 CertificateVerify 消息的签名字段中收到的数字签名
若是验证失败,接收方必须经过 "decrypt_error" 警报终止握手。
Finished 消息是认证块中的最后一条消息。它对提供握手和计算密钥的身份验证起了相当重要的做用。
Finished 消息的收件人必须验证内容是否正确,若是不正确,必须使用 "decrypt_error" alert 消息终止链接。
一旦一方已发送其 Finished 消息并已收到并验证来自其对端的 Finished 消息,它就能够开始经过该链接发送和接收应用数据。有两种设置容许在接收对端的 Finished 以前发送数据:
用于计算 Finished 消息的密钥是使用 HKDF,它是从第 4.4 节中定义的 Base Key 计算而来的(参见第7.1节)。特别的:
finished_key =
HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
复制代码
这条消息的数据结构是:
struct {
opaque verify_data[Hash.length];
} Finished;
复制代码
verify_data 按照以下方法计算:
verify_data =
HMAC(finished_key,
Transcript-Hash(Handshake Context,
Certificate*, CertificateVerify*))
* Only included if present.
复制代码
HMAC [RFC2104] 使用哈希算法进行握手。如上所述,HMAC 输入一般是经过动态的哈希实现的,即,此时仅是握手的哈希。
在之前版本的 TLS 中,verify_data 的长度老是 12 个八位字节。在 TLS 1.3 中,它是用来表示握手的哈希的 HMAC 输出的大小。
注意:警报和任何其余非握手记录类型不是握手消息,而且不包含在哈希计算中。
Finished 消息以后的任何记录都必须在适当的 client_application_traffic_secret_N 下加密,如第 7.2 节所述。特别是,这包括 Server 为了响应 Client 的 Certificate 消息和 CertificateVerify 消息而发送的任何 alert。
struct {} EndOfEarlyData;
复制代码
若是 Server 在 EncryptedExtensions 中发送了 "early_data" 扩展,则 Client 必须在收到 Server 的 Finished 消息后发送 EndOfEarlyData 消息。 若是 Server 没有在 EncryptedExtensions中发送 "early_data" 扩展,那么 Client 毫不能发送 EndOfEarlyData 消息。此消息表示已传输完了全部 0-RTT application_data消息(若是有),而且接下来的记录受到握手流量密钥的保护。Server 不能发送此消息,Client 若是收到了这条消息,那么必须使用 "unexpected_message" alert 消息终止链接。这条消息使用从 client_early_traffic_secret 中派生出来的密钥进行加密保护。
TLS 还容许在主握手后发送其余的消息。这些消息使用握手内容类型,并使用适当的应用程序流量密钥进行加密。
在 Server 接收到 Client 的 Finished 消息之后的任什么时候刻,它均可以发送 NewSessionTicket 消息。此消息在 ticket 值和从恢复主密钥派生出来的 PSK 之间建立了惟一的关联。
Client 在 ClientHello 消息中包含 "pre_shared_key" 扩展,并在扩展中包含 ticket ,那么 Client 就可能在将来的握手中使用 PSK。Server 可能在一个链接中发送多个 ticket,发送时机多是一个接一个的当即发送,也多是在某个特定事件之后发送。例如,Server 可能会在握手后身份验证以后发送新的 ticket,以封装其余 Client 身份验证状态。多个 ticket 对于 Client 来讲,可用于各类目的,例如:
打开多个并行的 HTTP 链接
经过(例如) Happy Eyeballs [RFC8305] 或相关的技术在接口和地址簇上进行链接竞争
任何 ticket 必须只能使用与用于创建原始链接的 KDF 哈希算法相同的密码套件来恢复会话。
Client 必须只有在新的 SNI 值对原始会话中提供的 Server 证书有效时才能恢复,而且只有在 SNI 值与原始会话中使用的 SNI 值匹配时才应恢复。后者是性能优化:一般,没有理由指望单个证书所涵盖的不一样 Server 之间可以相互接受彼此的 ticket;所以,在这种状况下尝试恢复会话将会浪费一次性的 ticket。若是提供了这种指示(外部或经过任何其余方式),则 Client 可能可使用不一样的 SNI 值进行恢复会话。
在恢复会话时,若是向调用的应用程序报告 SNI 值,则实现方必须使用在恢复 ClientHello 中发送的值而不是在先前会话中发送的值。请注意,若是 Server 的实现拒绝了不一样 SNI 值的全部 PSK 标识,则这两个值老是相同。
注意:虽然恢复主密钥取决于 Client 的第二次 flight,可是不请求 Client 身份验证的 Server 能够独立计算转录哈希的剩余部分,而后在发送 Finished 消息后当即发送 NewSessionTicket 而不是等待 Client 的 Finished 消息。这可能适用于 Client 须要并行打开多个 TLS 链接而且能够从减小恢复握手的开销中受益的状况。
struct {
uint32 ticket_lifetime;
uint32 ticket_age_add;
opaque ticket_nonce<0..255>;
opaque ticket<1..2^16-1>;
Extension extensions<0..2^16-2>;
} NewSessionTicket;
复制代码
ticket_lifetime:
这个字段表示 ticket 的生存时间,这个时间是以 ticket 发布时间为网络字节顺序的 32 位无符号整数表示以秒为单位的时间。Server 禁止使用任何大于 604800秒(7 天)的值。值为零表示应当即丢弃 ticket。不管 ticket_lifetime 如何,Client 都不得缓存超过 7 天的 ticket,而且能够根据本地策略提早删除 ticket。Server 能够将 ticket 视为有效的时间段短于 ticket_lifetime 中所述的时间段。
ticket_age_add:
安全的生成的随机 32 位值,用于模糊 Client 在 "pre_shared_key" 扩展中包含的 ticket 的时间。Client 的 ticket age 以模 2 ^ 32 的形式添加此值,以计算出 Client 要传输的值。Server 必须为它发出的每一个 ticket 生成一个新值。
ticket_nonce:
每个 ticket 的值,在本次链接中发出的全部的 ticket 中是惟一的。
ticket:
这个值是被用做 PSK 标识的值。ticket 自己是一个不透明的标签。它能够是数据库查找键,也能够是自加密和自我验证的值。
extensions:
ticket 的一组扩展值。扩展格式在 4.2 节中定义的。Client 必须忽略没法识别的扩展。
当前为 NewSessionTicket 定义的惟一扩展名是 "early_data",表示该 ticket 可用于发送 0-RTT 数据(第4.2.10节)。 它包含如下值:
PSK 关联的 ticket 计算方法以下:
HKDF-Expand-Label(resumption_master_secret,
"resumption", ticket_nonce, Hash.length)
复制代码
由于 ticket_nonce 值对于每一个 NewSessionTicket 消息都是不一样的,因此每一个 ticket 会派生出不一样的 PSK。
请注意,原则上能够继续发布新 ticket,该 ticket 无限期地延长生命周期,这个生命周期是最初从初始非 PSK 握手中(最可能与对等证书相关联)派生获得的密钥材料的生命周期。
建议实现方对密钥材料这些加上总寿命时间的限制。这些限制应考虑到对等方证书的生命周期,干预撤销的可能性以及自从对等方在线 CertificateVerify 签名到当前时间的这段时间。
当 Client 发送了 "post_handshake_auth" 扩展(参见第4.2.6节)时,Server 能够在握手完成后随时经过发送 CertificateRequest 消息来请求客户端身份验证。Client 必须使用适当的验证消息进行响应(参见第4.4节)。若是 Client 选择进行身份验证,则必须发送 Certificate,CertificateVerify,Finished 消息。若是 Client 拒绝身份验证,它必须发送一个 Certificate 证书消息,其中不包含证书,而后是 Finished 消息。响应 Server 的全部 Client 消息必须连续出如今线路上,中间不能有其余类型的消息。
在没有发送 "post_handshake_auth" 扩展的状况下接收 CertificateRequest 消息的 Client 必须发送 "unexpected_message" alert 消息。
注意:因为 Client 身份验证可能涉及提示用户,所以 Server 必须作好一些延迟的准备,包括在发送 CertificateRequest 和接收响应之间接收任意数量的其余消息。此外,Client 若是连续接收到了多个 CertificateRequests 消息,Client 可能会以不一样于它们的顺序响应它们(certificate_request_context 值容许服务器消除响应的歧义)
KeyUpdate 握手消息用于表示发送方正在更新其本身的发送加密密钥。任何对等方在发送 Finished 消息后均可以发送此消息。在接收 Finished 消息以前接收 KeyUpdate 消息的,实现方必须使用 "unexpected_message" alert 消息终止链接。发送 KeyUpdate 消息后,如第 7.2 节所描述的计算方法,发送方应使用新一代的密钥发送其全部流量。收到 KeyUpdate 后,接收方必须更新其接收密钥。
enum {
update_not_requested(0), update_requested(1), (255)
} KeyUpdateRequest;
struct {
KeyUpdateRequest request_update;
} KeyUpdate;
复制代码
若是 request_update 字段设置为 "update_requested",则接收方必须在发送其下一个应用数据记录以前发送本身的 KeyUpdate,其中 request_update 设置为 "update_not_requested"。此机制容许任何一方强制更新整个链接,但会致使一个实现方接收多个 KeyUpdates,而且它仍是静默的响应单个更新。请注意,实现方可能在发送 KeyUpdate (把 request_update 设置为 "update_requested") 与接收对等方的 KeyUpdate 之间接收任意数量的消息,由于这些消息可能早就已经在传输中了。可是,因为发送和接收密钥是从独立的流量密钥中导出的,所以保留接收流量密钥并不会影响到发送方更改密钥以前发送的数据的前向保密性。
若是实现方独立地发送它们本身的 KeyUpdates,其 request_update 设置为 "update_requested" 而且它们的消息都是传输中,结果是双方都会响应,双方都会更新密钥。
发送方和接收方都必须使用旧密钥加密其 KeyUpdate 消息。另外,在接受使用新密钥加密的任何消息以前,双方必须强制接收带有旧密钥的 KeyUpdate。若是不这样作,可能会引发消息截断攻击。
Reference:
GitHub Repo:Halfrost-Field
Follow: halfrost · GitHub
Source: halfrost.com/TLS_1.3_Han…