握手协议用于协商链接的安全参数。握手消息被提供给 TLS 记录层,在记录层它们被封装到一个或多个 TLSPlaintext 或 TLSCiphertext 中,它们按照当前活动链接状态进行处理和传输。html
enum {
client_hello(1),
server_hello(2),
new_session_ticket(4),
end_of_early_data(5),
encrypted_extensions(8),
certificate(11),
certificate_request(13),
certificate_verify(15),
finished(20),
key_update(24),
message_hash(254),
(255)
} HandshakeType;
struct {
HandshakeType msg_type; /* handshake type */
uint24 length; /* remaining bytes in message */
select (Handshake.msg_type) {
case client_hello: ClientHello;
case server_hello: ServerHello;
case end_of_early_data: EndOfEarlyData;
case encrypted_extensions: EncryptedExtensions;
case certificate_request: CertificateRequest;
case certificate: Certificate;
case certificate_verify: CertificateVerify;
case finished: Finished;
case new_session_ticket: NewSessionTicket;
case key_update: KeyUpdate;
};
} Handshake;
复制代码
协议消息必须按照必定顺序发送(顺序见下文)。若是对端发现收到的握手消息顺序不对,必须使用 “unexpected_message” alert 消息来停止握手。git
另外 IANA 分配了新的握手消息类型,见第 11 章github
密钥交换消息用于确保 Client 和 Server 的安全性和创建用于保护握手和数据的通讯密钥的安全性。算法
在 TLS 协议中,密钥协商的过程当中,Client 在 ClientHello 中能够提供如下 4 种 options。数据库
若是 Server 不选择 PSK,那么上面 4 个 option 中的前 3 个是正交的, Server 独立的选择一个加密套件,独立的选择一个 (EC)DHE 组,独立的选择一个用于创建链接的密钥共享,独立的选择一个签名算法/证书对用于给 Client 验证 Server 。若是 Server 收到的 "supported_groups" 中没有 Server 能支持的算法,那么就必须返回 "handshake_failure" 或者 "insufficient_security" 的 alert 消息。缓存
若是 Server 选择了 PSK,它必须从 Client 的 "psk_key_exchange_modes" 扩展消息中选择一个密钥创建模式。这个时候 PSK 和 (EC)DHE 是分开的。在 PSK 和 (EC)DHE 分开的基础上,即便,"supported_groups" 中不存在 Server 和 Client 相同的算法,也不会终止握手。安全
若是 Server 选择了 (EC)DHE 组,而且 Client 在 ClientHello 中没有提供合适的 "key_share" 扩展, Server 必须用 HelloRetryRequest 消息做为回应。cookie
若是 Server 成功的选择了参数,也就不须要 HelloRetryRequest 消息了。 Server 将发送 ServerHello 消息,它包含如下几个参数:网络
若是 Server 不能协商出可支持的参数集合,即在 Client 和 Server 各自支持的参数集合中没有重叠,那么 Server 必须发送 "handshake_failure" 或者 "insufficient_security" 消息来停止握手。session
当一个 Client 第一次链接一个 Server 时,它须要在发送第一条 TLS 消息的时候,发送 ClientHello 消息。当 Server 发送 HelloRetryRequest 消息的时候,Client 收到了之后也须要回应一条 ClientHello 消息。在这种状况下,Client 必须发送相同的无修改的 ClientHello 消息,除非如下几种状况:
因为 TLS 1.3 严禁重协商,若是 Server 已经完成了 TLS 1.3 的协商了,在将来某一时刻又收到了 ClientHello ,Server 不该该理会这条消息,必须当即断开链接,并发送 "unexpected_message" alert 消息。
若是一个 Server 创建了一个 TLS 之前版本的 TLS 链接,并在重协商的时候收到了 TLS 1.3 的 ClientHello ,这个时候,Server 必须继续保持以前的版本,严禁协商 TLS 1.3 。
ClientHello 消息的结构是
uint16 ProtocolVersion;
opaque Random[32];
uint8 CipherSuite[2]; /* Cryptographic suite selector */
struct {
ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
Random random;
opaque legacy_session_id<0..32>;
CipherSuite cipher_suites<2..2^16-2>;
opaque legacy_compression_methods<1..2^8-1>;
Extension extensions<8..2^16-1>;
} ClientHello;
复制代码
关于结构体的一些说明:
legacy_version:
在 TLS 之前的版本里,这个字段被用来版本协商和表示 Client 所能支持的 TLS 最高版本号。经验代表,不少 Server 并无正确的实现版本协商,致使了 "version intolerance" —— Sever 拒绝了一些原本能够支持的 ClientHello 消息,只由于这些消息的版本号高于 Server 能支持的版本号。在 TLS 1.3 中,Client 在 "supported_versions" 扩展中代表了它的版本。而且 legacy_version 字段必须设置成 0x0303,这是 TLS 1.2 的版本号。在 TLS 1.3 中的 ClientHello 消息中的 legacy_version 都设置成 0x0303,supported_versions 扩展设置成 0x0304。更加详细的信息见附录 D。
random:
由一个安全随机数生成器产生的32字节随机数。额外信息见附录 C。
legacy_session_id:
TLS 1.3 版本以前的版本支持会话恢复的特性。在 TLS 1.3 的这个版本中,这一特性已经和预共享密钥 PSK 合并了。若是 Client 有 TLS 1.3 版本以前的 Server 设置的缓存 Session ID,那么这个字段要填上这个 ID 值。在兼容模式下,这个值必须是非空的,因此一个 Client 要是不能提供 TLS 1.3 版本以前的 Session 的话,就必须生成一个新的 32 字节的值。这个值不要求是随机值,但必须是一个不可预测的值,防止实现上固定成了一个固定的值了。不然,这个字段必须被设置成一个长度为 0 的向量。(例如,一个0字节长度域)
cipher_suites:
这个列表是 Client 所支持对称加密选项的列表,特别是记录保护算法(包括密钥长度) 和 HKDF 一块儿使用的 hash 算法。以 Client 的偏好降序排列。若是列表包含的密码套件是 Server 不能识别的或者是不能支持的,或者是但愿使用的,Server 必须忽略这些密码套件,照常处理剩下来的密码套件。若是 Client 尝试创建 PSK 密钥,则它应该至少包含一个与 PSK 相关的哈希加密套件。
legacy_compression_methods:
TLS 1.3 以前的 TLS 版本支持压缩,在这个字段中发送支持的压缩方法列表。对于每一个 ClientHello,该向量必须包含一个设置为 0 的一个字节,它对应着 TLS 以前版本中的 null 压缩方法。若是 TLS 1.3 中的 ClientHello 中这个字段包含有值,Server 必须当即发送 “illegal_parameter” alert 消息停止握手。注意,TLS 1.3 Server 可能接收到 TLS 1.2 或者以前更老版本的 ClientHellos,其中包含了其余压缩方法。若是正在协商这些以前的版本,那么必须遵循 TLS 以前版本的规定。
extensions:
Client 经过在扩展字段中发送数据,向 Server 请求扩展功能。“Extension” 遵循格式定义。在 TLS 1.3 中,使用肯定的扩展项是强制的。由于功能被移动到了扩展中以保持和以前 TLS 版本的 ClientHello 消息的兼容性。Server 必须忽略不能识别的 extensions。
全部版本的 TLS 都容许可选的带上 compression_methods 这个扩展字段。TLS 1.3 ClientHello 消息一般包含扩展消息(至少包含 “supported_versions”,不然这条消息会被解读成 TLS 1.2 的 ClientHello 消息)然而,TLS 1.3 Server 也有可能收到以前 TLS 版本发来的不带扩展字段的 ClientHello 消息。扩展是否存在,能够经过检测 ClientHello 结尾的 compression_methods 字段内是否有字节来肯定。请注意,这种检测可选数据的方法与具备可变长度字段的普通 TLS 方法不一样,可是在扩展被定义以前,这种方法能够用来作兼容。TLS 1.3 Server 须要首先执行此项检查,而且仅当存在 “supported_versions” 扩展时才尝试协商 TLS 1.3。若是协商的是 TLS 1.3 以前的版本,Server 必须作 2 项检查:legacy_compression_methods 字段后面是否还有数据;有效的 extensions block 后没有数据跟随。若是上面这 2 项检查都不经过,须要当即发送 "decode_error" alert 消息停止握手。
若是 Client 经过扩展请求额外功能,可是这个功能 Server 并不提供,则 Client 能够停止握手。
发送 ClientHello 消息后,Client 等待 ServerHello 或者 HelloRetryRequest 消息。若是 early data 在使用中,Client 在等待下一条握手消息期间,能够先发送 early Application Data。
若是 Server 和 Client 能够在 ClientHello 消息中协商出一套双方均可以接受的握手参数的话,那么 Server 会发送 Server Hello 消息回应 ClientHello 消息。
消息的结构体是:
struct {
ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
Random random;
opaque legacy_session_id_echo<0..32>;
CipherSuite cipher_suite;
uint8 legacy_compression_method = 0;
Extension extensions<6..2^16-1>;
} ServerHello;
复制代码
legacy_version:
在 TLS 1.3 以前的版本,这个字段被用来版本协商和标识创建链接时候双方选择的版本号。不幸的是,一些中间件在给这个字段赋予新值的时候可能会失败。在 TLS 1.3 中,Server 用 "supported_versions" 扩展字段来标识它支持的版本,legacy_version 字段必须设置为 0x0303(这个值表明的 TLS 1.2)。(有关向后兼容性的详细信息,请参阅附录D.)
random:
由安全随机数生成器生成的随机 32 字节。若是协商的是 TLS 1.1 或者 TLS 1.2 ,那么最后 8 字节必须被重写,其他的 24 字节必须是随机的。这个结构由 Server 生成而且必须独立于 ClientHello.random。
legacy_session_id_echo:
Client 的 legacy_session_id 字段的内容。请注意,即便 Server 决定再也不恢复 TLS 1.3 以前的会话,Client 的 legacy_session_id 字段缓存的是 TLS 1.3 以前的值,这个时候 legacy_session_id_echo 字段也会被 echoed。Client 收到的 legacy_session_id_echo 值和它在 ClientHello 中发送的值不匹配的时候,必须当即用 "illegal_parameter" alert 消息停止握手。
cipher_suite:
Server 从 ClientHello 中的 cipher_suites 列表中选择的一个加密套件。Client 若是接收到并无提供的密码套件,此时应该当即用 "illegal_parameter" alert 消息停止握手。
legacy_compression_method:
必须有 0 值的单一字节。
extensions:
扩展列表。ServerHello 中必须仅仅只能包括创建加密上下文和协商协议版本所需的扩展。全部 TLS 1.3 的 ServerHello 消息必须包含 "supported_versions" 扩展。当前的 ServerHello 消息还另外包含 "pre_shared_key" 扩展或者 "key_share" 扩展,或者两个扩展都有(当使用 PSK 和 (EC)DHE 创建链接的时候)。其余的扩展会在 EncryptedExtensions 消息中分别发送。
出于向后兼容中间件的缘由,HelloRetryRequest 消息和 ServerHello 消息采用相同的结构体,但须要随机设置 HelloRetryRequest 的 SHA-256 特定值:
CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
复制代码
当收到 server_hello 消息之后,实现必须首先检查这个随机值是否是和上面这个值匹配。若是和上面这个值是一致的,再继续处理。
TLS 1.3 具备降级保护机制,这种机制是经过嵌入在 Server 的随机值实现的。TLS 1.3 Server 协商 TLS 1.2 或者更老的版本,为了响应 ClientHello ,ServerHello 消息中必须在最后 8 个字节中填入特定的随机值。
若是协商的 TLS 1.2 ,TLS 1.3 Server 必须把 ServerHello 中的 Random 字段的最后 8 字节设置为:
44 4F 57 4E 47 52 44 01
D O W N G R D
复制代码
若是协商的 TLS 1.1 或者更老的版本,TLS 1.3 Server 和 TLS 1.2 Server 必须把 ServerHello 中的 Random 字段的最后 8 字节的值改成:
44 4F 57 4E 47 52 44 00
D O W N G R D
复制代码
TLS 1.3 Client 接收到 TLS 1.2 或者 TLS 更老的版本的 ServerHello 消息之后,必需要检查 ServerHello 中的 Random 字段的最后 8 字节不等于上面 2 个值才对。TLS 1.2 的 Client 也须要检查最后 8 个字节,若是协商的是 TLS 1.1 或者是更老的版本,那么 Random 值也不该该等于上面第二个值。若是都没有匹配上,那么 Client 必须用 "illegal_parameter" alert 消息停止握手。这种机制提供了有限的保护措施,抵御降级攻击。经过 Finished exchange ,能超越保护机制的保护范围:由于在 TLS 1.2 或更低的版本上,ServerKeyExchange 消息包含 2 个随机值的签名。只要使用了临时的加密方式,攻击者就不可能在不被发现的状况下,修改随机值。因此对于静态的 RSA,是没法提供降级攻击的保护。
请注意,上面这些改动在 RFC5246 中说明的,实际上许多 TLS 1.2 的 Client 和 Server 都没有按照上面的规定来实践。
若是 Client 在从新协商 TLS 1.2 或者更老的版本的时候,协商过程当中收到了 TLS 1.3 的 ServerHello,这个时候 Client 必须当即发送 “protocol_version” alert 停止握手。请注意,一旦 TLS 1.3 协商完成,就没法再从新协商了,由于 TLS 1.3 严禁从新协商。
若是在 Client 发来的 ClientHello 消息中可以找到一组能够相互支持的参数,可是 Client 又不能为接下来的握手提供足够的信息,这个时候 Server 就须要发送 HelloRetryRequest 消息来响应 ClientHello 消息。在上一节中,谈到 HelloRetryRequest 和 ServerHello 消息是有相同的数据结构,legacy_version, legacy_session_id_echo, cipher_suite, legacy_compression_method 这些字段的含义也是同样的。为了讨论的方便,下文中,咱们讨论 HelloRetryRequest 消息都当作不一样的消息来对待。
Server 的扩展集中必须包含 "supported_versions"。另外,它还须要包含最小的扩展集,能让 Client 生成正确的 ClientHello 对。相比 ServerHello 而言,HelloRetryRequest 只能包含任何在第一次 ClientHello 中出现过的扩展,除了可选的 "cookie" 之外。
Client 接收到 HelloRetryRequest 消息之后,必需要先校验 legacy_version, legacy_session_id_echo, cipher_suite, legacy_compression_method 这四个参数。先从 “supported_versions” 开始决定和 Server 创建链接的版本,而后再处理扩展。若是 HelloRetryRequest 不会致使 ClientHello 的任何更改,Client 必须用 “illegal_parameter” alert 消息停止握手。若是 Client 在一个链接中收到了第 2 个 HelloRetryRequest 消息( ClientHello 自己就是响应 HelloRetryRequest 的),那么必须用 “unexpected_message” alert 消息停止握手。
不然,Client 必须处理 HelloRetryRequest 中全部的扩展,而且发送第二个更新的 ClientHello。在本规范中定义的 HelloRetryRequest 扩展名是:
Client 在接收到本身并无提供的密码套件的时候必须当即停止握手。Server 必须确保在接收到合法而且更新过的 ClientHello 时,它们在协商相同的密码套件(若是 Server 把选择密码套件做为协商的第一步,那么这一步会自动发送)。Client 收到 ServerHello 后必须检查 ServerHello 中提供的密码套件是否与 HelloRetryRequest 中的密码套件相同,不然将以 “illegal_parameter” alert 消息停止握手。
此外,Client 在其更新的 ClientHello 中,Client 不能提供任何与所选密码套件之外的预共享密钥(与哈希相关联的)。这容许 Client 避免在第二个 ClientHello 中计算多个散列的部分哈希转录。
在 HelloRetryRequest 的 "support_versions" 扩展中的 selected_version 字段的值必须被保留在 ServerHello 中,若是这个值变了,Client 必须用 “illegal_parameter” alert 消息停止握手。
许多 TLS 的消息都包含 tag-length-value 编码的扩展数据结构:
struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;
enum {
server_name(0), /* RFC 6066 */
max_fragment_length(1), /* RFC 6066 */
status_request(5), /* RFC 6066 */
supported_groups(10), /* RFC 8422, 7919 */
signature_algorithms(13), /* RFC 8446 */
use_srtp(14), /* RFC 5764 */
heartbeat(15), /* RFC 6520 */
application_layer_protocol_negotiation(16), /* RFC 7301 */
signed_certificate_timestamp(18), /* RFC 6962 */
client_certificate_type(19), /* RFC 7250 */
server_certificate_type(20), /* RFC 7250 */
padding(21), /* RFC 7685 */
pre_shared_key(41), /* RFC 8446 */
early_data(42), /* RFC 8446 */
supported_versions(43), /* RFC 8446 */
cookie(44), /* RFC 8446 */
psk_key_exchange_modes(45), /* RFC 8446 */
certificate_authorities(47), /* RFC 8446 */
oid_filters(48), /* RFC 8446 */
post_handshake_auth(49), /* RFC 8446 */
signature_algorithms_cert(50), /* RFC 8446 */
key_share(51), /* RFC 8446 */
(65535)
} ExtensionType;
复制代码
这里:
全部的扩展类型由 IANA 维护,具体的见附录。
扩展一般以请求/响应方式构建,虽然有些扩展只是一些标识,并不会有任何响应。Client 在 ClientHello 中发送其扩展请求,Server 在 ServerHello, EncryptedExtensions, HelloRetryRequest,和 Certificate 消息中发送对应的扩展响应。Server 在 CertificateRequest 消息中发送扩展请求,Client 可能回应 Certificate 消息。Server 也有可能不请自来的在 NewSessionTicket 消息中直接发送扩展请求,Client 能够不用直接响应这条消息。
若是远端没有发送相应的扩展请求,除了 HelloRetryRequest 消息中的 “cookie” 扩展之外,实现方不得发送扩展响应。在接收到这样的扩展之后,端点必须用 "unsupported_extension" alert 消息停止握手。
下表给出了可能出现的消息的扩展名,使用如下表示法:CH (ClientHello), SH (ServerHello), EE (EncryptedExtensions), CT (Certificate), CR (CertificateRequest), NST (NewSessionTicket), 和 HRR (HelloRetryRequest) 。当实现方在接收到它能识别的消息,而且并无为出现的消息作规定的话,它必须用 "illegal_parameter" alert 消息停止握手。
+--------------------------------------------------+-------------+
| Extension | TLS 1.3 |
+--------------------------------------------------+-------------+
| server_name [RFC6066] | CH, EE |
| | |
| max_fragment_length [RFC6066] | CH, EE |
| | |
| status_request [RFC6066] | CH, CR, CT |
| | |
| supported_groups [RFC7919] | CH, EE |
| | |
| signature_algorithms (RFC 8446) | CH, CR |
| | |
| use_srtp [RFC5764] | CH, EE |
| | |
| heartbeat [RFC6520] | CH, EE |
| | |
| application_layer_protocol_negotiation [RFC7301] | CH, EE |
| | |
| signed_certificate_timestamp [RFC6962] | CH, CR, CT |
| | |
| client_certificate_type [RFC7250] | CH, EE |
| | |
| server_certificate_type [RFC7250] | CH, EE |
| | |
| padding [RFC7685] | CH |
| | |
| key_share (RFC 8446) | CH, SH, HRR |
| | |
| pre_shared_key (RFC 8446) | CH, SH |
| | |
| psk_key_exchange_modes (RFC 8446) | CH |
| | |
| early_data (RFC 8446) | CH, EE, NST |
| | |
| cookie (RFC 8446) | CH, HRR |
| | |
| supported_versions (RFC 8446) | CH, SH, HRR |
| | |
| certificate_authorities (RFC 8446) | CH, CR |
| | |
| oid_filters (RFC 8446) | CR |
| | |
| post_handshake_auth (RFC 8446) | CH |
| | |
| signature_algorithms_cert (RFC 8446) | CH, CR |
+--------------------------------------------------+-------------+
复制代码
当存在多种不一样类型的扩展的时候,除了 "pre_shared_key" 必须是 ClientHello 的最后一个扩展,其余的扩展间的顺序能够是任意的。("pre_shared_key" 能够出如今 ServerHello 中扩展块中的任何位置)。不能存在多个同一个类型的扩展。
在 TLS 1.3 中,与 TLS 1.2 不一样,即便是恢复 PSK 模式,每次握手都须要协商扩展。然而,0-RTT 的参数是在前一次握手中协商的。若是参数不匹配,须要拒绝 0-RTT。
在 TLS 1.3 中新特性和老特性之间存在微妙的交互,这可能会使得总体安全性显著降低。下面是设计新扩展的时候须要考虑的因素:
Server 不一样意扩展的某些状况是错误的(例如握手不能继续),有些状况只是简单的不支持特定的功能。通常来讲,前一种状况应该用错误的 alert,后一种状况应该用 Server 的扩展响应中的一个字段来处理。
扩展应尽量设计为防止能经过人为操纵握手信息,从而强制使用(或不使用)特定功能的攻击。无论这个功能是否会引发安全问题,这个原则都必须遵照。一般,包含在 Finished 消息的哈希输入中的扩展字段是不用担忧的,可是在握手阶段,扩展试图改变了发送消息的含义,这种状况须要特别当心。设计者和实现者应该意识到,在握手完成身份认证以前,攻击者均可以修改消息,插入、删除或者替换扩展。
struct {
select (Handshake.msg_type) {
case client_hello:
ProtocolVersion versions<2..254>;
case server_hello: /* and HelloRetryRequest */
ProtocolVersion selected_version;
};
} SupportedVersions;
复制代码
“supported_versions” 对于 Client 来讲,Client 用它来标明它所能支持的 TLS 版本,对于 Server 来讲,Server 用它来标明正在使用的 TLS 版本。这个扩展包含一个按照优先顺序排列的,能支持的版本列表。最优先支持的版本放在第一个。TLS 1.3 这个版本的规范是必须在发送 ClientHello 消息时候带上这个扩展,扩展中包含全部准备协商的 TLS 版本。(对于这个规范来讲,这意味着最低是 0x0304,可是若是要协商 TLS 的之前的版本,那么这个扩展必需要带上)
若是不存在 “supported_versions” 扩展,知足 TLS 1.3 而且也兼容 TLS 1.2 规范的 Server 须要协商 TLS 1.2 或者以前的版本,即便 ClientHello.legacy_version 是 0x0304 或者更高的版本。Server 在接收到 ClientHello 中的 legacy_version 的值是 0x0304 或者更高的版本的时候,Server 可能须要马上停止握手。
若是 ClientHello 中存在 “supported_versions” 扩展,Server 禁止使用 ClientHello.legacy_version 的值做为版本协商的值,只能使用 "supported_versions" 决定 Client 的偏好。Server 必须只选择该扩展中存在的 TLS 版本,而且必需要忽略任何未知版本。注意,若是通讯的一方支持稀疏范围,这种机制使得能够在 TLS 1.2 以前的版本间进行协商。选择支持 TLS 的之前版本的 TLS 1.3 的实现应支持 TLS 1.2。Server 应准备好接收包含此扩展名的 ClientHellos 消息,但不要在 viersions 列表中包含 0x0304。
Server 在协商 TLS 1.3 以前的版本,必需要设置 ServerHello.version,不能发送 "supported_versions" 扩展。Server 在协商 TLS 1.3 版本时候,必须发送 "supported_versions" 扩展做为响应,而且扩展中要包含选择的 TLS 1.3 版本号(0x0304)。还要设置 ServerHello.legacy_version 为 0x0303(TLS 1.2)。Client 必须在处理 ServerHello 以前检查此扩展(尽管须要先解析 ServerHello 以便读取扩展名)。若是 "supported_versions" 扩展存在,Client 必须忽略 ServerHello.legacy_version 的值,只使用 "supported_versions" 中的值肯定选择的版本。若是 ServerHello 中的 "supported_versions" 扩展包含了 Client 没有提供的版本,或者是包含了 TLS 1.3 以前的版本(原本是协商 TLS 1.3 的,却又包含了 TLS 1.3 以前的版本),Client 必须当即发送 "illegal_parameter" alert 消息停止握手。
struct {
opaque cookie<1..2^16-1>;
} Cookie;
复制代码
Cookies 有 2 大主要目的:
容许 Server 强制 Client 展现网络地址的可达性(所以提供了一个保护 Dos 的度量方法),这主要是面向无链接的传输(参考 RFC 6347 中的例子)
容许 Server 卸载状态。从而容许 Server 在向 Client 发送 HelloRetryRequest 消息的时候,不存储任何状态。为了实现这一点,能够经过 Server 把 ClientHello 的哈希存储在 HelloRetryRequest 的 cookie 中(用一些合适的完整性算法保护)。
当发送 HelloRetryRequest 消息时,Server 能够向 Client 提供 “cookie” 扩展(这是常规中的一个例外,常规约定是:只能是可能被发送的扩展才能够出如今 ClientHello 中)。当发送新的 ClientHello 消息时,Client 必须将 HelloRetryRequest 中收到的扩展的内容复制到新 ClientHello 中的 “cookie” 扩展中。Client 不得在后续链接中使用首次 ClientHello 中的 Cookie。
当 Server 在无状态运行的时候,在第一个和第二个 ClientHello 之间可能会收到不受保护的 change_cipher_spec 消息。因为 Server 没有存储任何状态,它会表现出像到达的第一条消息同样。无状态的 Server 必须忽略这些记录。
TLS 1.3 提供了 2 种扩展来标明在数字签名中可能用到的签名算法。"signature_algorithms_cert" 扩展提供了证书里面的签名算法。"signature_algorithms" 扩展(TLS 1.2 中就有这个扩展了),提供了 CertificateVerify 消息中的签名算法。证书中的密钥必需要根据所用的签名算法匹配合适的类型。对于 RSA 密钥和 PSS 签名,这是一个特殊问题,描述以下:若是没有 "signature_algorithms_cert" 扩展,则 "signature_algorithms" 扩展一样适用于证书中的签名。Client 想要 Server 经过证书来认证本身,则必须发送 "signature_algorithms" 扩展。若是 Server 正在进行证书的认证,这个时候 Client 又没有提供 "signature_algorithms"扩展,Server 必须 发送 "missing_extension" 消息停止握手。
加入 "signature_algorithms_cert" 扩展的意图是为了让已经支持了证书的不一样算法集的实现方,能明确的标识他们的能力。TLS 1.2 实现应该也应该处理这个扩展。在两种状况下具备相同策略的实现能够省略 "signature_algorithms_cert" 扩展名。
这些扩展中的 "extension_data" 字段包含一个 SignatureSchemeList 值:
enum {
/* RSASSA-PKCS1-v1_5 algorithms */
rsa_pkcs1_sha256(0x0401),
rsa_pkcs1_sha384(0x0501),
rsa_pkcs1_sha512(0x0601),
/* ECDSA algorithms */
ecdsa_secp256r1_sha256(0x0403),
ecdsa_secp384r1_sha384(0x0503),
ecdsa_secp521r1_sha512(0x0603),
/* RSASSA-PSS algorithms with public key OID rsaEncryption */
rsa_pss_rsae_sha256(0x0804),
rsa_pss_rsae_sha384(0x0805),
rsa_pss_rsae_sha512(0x0806),
/* EdDSA algorithms */
ed25519(0x0807),
ed448(0x0808),
/* RSASSA-PSS algorithms with public key OID RSASSA-PSS */
rsa_pss_pss_sha256(0x0809),
rsa_pss_pss_sha384(0x080a),
rsa_pss_pss_sha512(0x080b),
/* Legacy algorithms */
rsa_pkcs1_sha1(0x0201),
ecdsa_sha1(0x0203),
/* Reserved Code Points */
private_use(0xFE00..0xFFFF),
(0xFFFF)
} SignatureScheme;
struct {
SignatureScheme supported_signature_algorithms<2..2^16-2>;
} SignatureSchemeList;
复制代码
请注意:这个枚举之因此名为 "SignatureScheme",是由于在 TLS 1.2 中已经存在了 "SignatureAlgorithm" 类型,取而代之。在本篇文章中,咱们都使用术语 "签名算法"。
每个列出的 SignatureScheme 的值是 Client 想要验证的单一签名算法。这些值按照优先级降序排列。请注意,签名算法以任意长度的消息做为输入,而不是摘要做为输入。传统上用于摘要的算法应该在 TLS 中定义,首先使用指定的哈希算法对输入进行哈希计算,而后再进行常规处理。上面列出的代码具备如下含义:
RSASSA-PKCS1-v1_5 algorithms:
表示使用 RSASSA-PKCS1-v1_5 RFC8017 和定义在 SHS 中对应的哈希算法的签名算法。这些值仅指,出如今证书中又没有被定义用于签名 TLS 握手消息的签名。这些值会出如今 "signature_algorithms" 和 "signature_algorithms_cert" 中,由于须要向后兼容 TLS 1.2 。
ECDSA algorithms:
表示签名算法使用 ECDSA,对应的曲线在 ANSI X9.62 ECDSA 和 FIPS 186-4 DSS 中定义了,对应的哈希算法在 SHS 中定义了。签名被表示为 DER 编码的 ECDSA-Sig-Value 结构。
RSASSA-PSS RSAE algorithms:
表示使用带有掩码生成函数 1 的 RSASSA-PSS 签名算法。在掩码生成函数中使用的摘要和被签名的摘要都是在 SHS 中定义的相应的哈希算法。盐的长度必须等于摘要算法输出的长度。若是公钥在 X.509 证书中,则必须使用 rsaEncryption OID RFC5280。
EdDSA algorithms:
表示使用定义在 RFC 8032 中的 EdDSA 算法或者其后续改进算法。请注意,这些相应算法是 "PureEdDSA" 算法,而不是 "prehash" 变种算法。
RSASSA-PSS PSS algorithms:
表示使用带有掩码生成函数 1 的 RSASSA-PSS RFC 8017 签名算法。在掩码生成函数中使用的摘要和被签名的摘要都是在 SHS 中定义的相应的哈希算法。盐的长度必须等于摘要算法的长度。若是公钥在 X.509 证书中,则必须使用 RSASSA-PSS OID RFC5756。当它被用在证书签名中,算法参数必须是 DER 编码。若是存在相应的公钥参数,则签名中的参数必须与公钥中的参数相同。
Legacy algorithms:
表示使用正在被废弃中的算法,由于这些算法有已知的缺点。特别是 SHA-1 配合上文提到的 RSASSA-PKCS1-v1_5 和 ECDSA 算法一块儿使用。这些值仅指,出如今证书中又没有被定义用于签名 TLS 握手消息的签名。这些值会出如今 "signature_algorithms" 和 "signature_algorithms_cert" 中,由于须要向后兼容 TLS 1.2 。终端不该该协商这些算法,但容许这样作只是为了向后兼容。提供这些值的 Client 必须把他们列在最低优先级的位置上(在 SignatureSchemeList 中的全部其余算法以后列出)。TLS 1.3 Server 毫不能提供 SHA-1 签名证书,除非没有它就没法生成有效的证书链。
自签名证书上的签名或信任锚的证书不能经过校验,由于它们开始了一个认证路径(见 RFC 5280)。开始认证路径的证书可使用 "signature_algorithms" 扩展中不建议支持的签名算法。
请注意,TLS 1.2 中这个扩展的定义和 TLS 1.3 的定义不一样。在协商 TLS 1.2 版本时,愿意协商 TLS 1.2 的 TLS 1.3 实现必须符合 RFC5246 的要求,尤为是:
TLS 1.2 ClientHellos 能够忽略此扩展。
在 TLS 1.2 中,扩展包含 hash/signature pairs。这些 pairs 被编码为两个八位字节,因此已经分配空间的 SignatureScheme 值与 TLS 1.2 的编码对齐。 一些传统的 pairs 保留未分配。这些算法已被 TLS 1.3 弃用。它们不得在任何实现中被提供或被协商。 特别是,不得使用 MD5 [SLOTH] 、SHA-224 和 DSA。
ECDSA 签名方案和 TLS 1.2 的 hash/signature pairs 一致。然而,旧的语义并无限制签名曲线。若是 TLS 1.2 被协商了,实现方必须准备接受在 "supported_groups" 扩展中使用任何曲线的签名。
即便协商了 TLS 1.2,支持了 RSASSA-PSS(在TLS 1.3中是强制性的)的实现方也准备接受该方案的签名。在TLS 1.2中,RSASSA-PSS 与 RSA 密码套件一块儿使用。
"certificate_authorities" 扩展用于表示终端支持的 CA, 而且接收的端点应该使用它来指导证书的选择。
"certificate_authorities" 扩展的主体包含了一个 CertificateAuthoritiesExtension 结构:
opaque DistinguishedName<1..2^16-1>;
struct {
DistinguishedName authorities<3..2^16-1>;
} CertificateAuthoritiesExtension;
复制代码
Client 可能会在 ClientHello 消息中发送 "certificate_authorities" 扩展,Server 可能会在 CertificateRequest 消息中发送 "certificate_authorities" 扩展。
"trusted_ca_keys" 扩展和 "certificate_authorities" 扩展有相同的目的,可是更加复杂。"trusted_ca_keys" 扩展不能在 TLS 1.3 中使用,可是它在 TLS 1.3 以前的版本中,可能出如今 Client 的 ClientHello 消息中。
"oid_filters" 扩展容许 Server 提供一组 OID/value 对,用来匹配 Client 的证书。若是 Server 想要发送这个扩展,有且仅有在 CertificateRequest 消息中才能发送。
struct {
opaque certificate_extension_oid<1..2^8-1>;
opaque certificate_extension_values<0..2^16-1>;
} OIDFilter;
struct {
OIDFilter filters<0..2^16-1>;
} OIDFilterExtension;
复制代码
PKIX RFC 定义了各类证书扩展 OID 及其对应的值类型。根据类型,匹配的证书扩展值不必定是按位相等的。指望 TLS 实现将依靠它们的 PKI 库,使用证书扩展 OID 来作证书的选择。
本文档定义了 RFC5280 中定义的两个标准证书扩展的匹配规则:
当请求中声明的全部 Key Usage 位也一样在 Key Usage 证书扩展声明了,那么证书中的 Key Usage 扩展匹配了请求。
当请求中全部的密钥 OIDs 在 Extended Key Usage 证书扩展中也存在,那么证书中的 Extended Key Usage 匹配了请求。特殊的 anyExtendedKeyUsage OID 必定不能在请求中使用。
单独的规范能够为其余证书扩展的规则定义匹配规则。
"post_handshake_auth" 扩展用于代表 Client 愿意握手后再认证。Server 不能向没有提供此扩展的 Client 发送握手后再认证的 CertificateRequest 消息。Server 不能发送此扩展。
struct {} PostHandshakeAuth;
复制代码
"post_handshake_auth" 扩展名中的 "extension_data" 字段为零长度。
当 Client 发送 "supported_groups" 扩展的时候,这个扩展代表了 Client 支持的用于密钥交换的命名组。按照优先级从高到低。
请注意:在 TLS 1.3 以前的版本中,这个扩展原来叫 "elliptic_curves",而且只包含椭圆曲线组。具体请参考 RFC8422 和 RFC7919。这个扩展一样能够用来协商 ECDSA 曲线。签名算法如今独立协商了。
这个扩展中的 "extension_data" 字段包含一个 "NamedGroupList" 值:
enum {
/* Elliptic Curve Groups (ECDHE) */
secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
x25519(0x001D), x448(0x001E),
/* Finite Field Groups (DHE) */
ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),
ffdhe6144(0x0103), ffdhe8192(0x0104),
/* Reserved Code Points */
ffdhe_private_use(0x01FC..0x01FF),
ecdhe_private_use(0xFE00..0xFEFF),
(0xFFFF)
} NamedGroup;
struct {
NamedGroup named_group_list<2..2^16-1>;
} NamedGroupList;
复制代码
Elliptic Curve Groups (ECDHE):
表示支持在 FIPS 186-4 [DSS] 或者 [RFC7748] 中定义的对应命名的曲线。0xFE00 到 0xFEFF 的值保留使用[RFC8126]。
Finite Field Groups (DHE):
表示支持相应的有限域组,相关定义能够参考 [RFC7919]。0x01FC 到 0x01FF 的值保留使用。
named_group_list 中的项根据发送者的优先级排序(最好是优先选择的)。
在 TLS 1.3 中,Server 容许向 Client 发送 "supported_groups" 扩展。Client 不能在成功完成握手以前,在 "supported_groups" 中找到的任何信息采起行动,但可使用从成功完成的握手中得到的信息来更改在后续链接中的 "key_share" 扩展中使用的组。若是 Server 中有一个组,它更想接受 "key_share" 扩展中的那些值,但仍然愿意接受 ClientHello 消息,这时候它应该发送 "supported_groups" 来更新 Client 的偏好视图。不管 Client 是否支持它,这个扩展名都应该包含 Server 支持的全部组。
"key_share" 扩展包含终端的加密参数。
Client 可能会发送空的 client_shares 向量,以额外的往返代价,向 Server 请求选择的组。
struct {
NamedGroup group;
opaque key_exchange<1..2^16-1>;
} KeyShareEntry;
复制代码
group:
要交换的密钥的命名组。
key_exchange:
密钥交换信息。这个字段的内容由特定的组和相应的定义肯定。有限域的 Diffie-Hellman 参数在下面会描述。椭圆曲线 Diffie-Hellman 参数也会下面会描述。
在 ClientHello 消息中,"key_share" 扩展中的 "extension_data" 包含 KeyShareClientHello 值:
struct {
KeyShareEntry client_shares<0..2^16-1>;
} KeyShareClientHello;
复制代码
若是 Client 正在请求 HelloRetryRequest, 则这个向量能够为空。每一个 KeyShareEntry 值必须对应一个在 "supported_groups" 扩展中提供的组,而且出现的顺序必须相同。然而,当优先级排名第一的组合是新的,而且不足以提供预生成 key shares 的时候,那么值能够是 "supported_groups" 扩展的非连续子集,而且能够省略最优选的组,这种状况是可能会出现的。
Client 能够提供与其提供的 support groups 同样多数量的 KeyShareEntry 的值。每一个值都表明了一组密钥交换参数。例如,Client 可能会为多个椭圆曲线或者多个 FFDHE 组提供 shares。每一个 KeyShareEntry 中的 key_exchange 值必须独立生成。Client 不能为相同的 group 提供多个 KeyShareEntry 值。Client 不能为,没有出如今 Client 的 "supported_group" 扩展中列出的 group 提供任何 KeyShareEntry 值。Server 会检查这些规则,若是违反了规则,当即发送 "illegal_parameter" alert 消息停止握手。
在 HelloRetryRequest 消息中,"key_share" 扩展中的 "extension_data" 字段包含 KeyShareHelloRetryRequest 值。
struct {
NamedGroup selected_group;
} KeyShareHelloRetryRequest;
复制代码
在 HelloRetryRequest 消息中收到此扩展后,Client 必需要验证 2 点。第一点,selected_group 必须在原始的 ClientHello 中的 "supported_groups" 中出现过。第二点,selected_group 没有在原始的 ClientHello 中的 "key_share" 中出现过。若是上面 2 点检查都失败了,那么 Client 必须经过 "illegal_parameter" alert 消息来停止握手。不然,在发送新的 ClientHello 时,Client 必须将原始的 "key_share" 扩展替换为仅包含触发 HelloRetryRequest 的 selected_group 字段中指示的组,这个组中只包含新的 KeyShareEntry。
在 ServerHello 消息中,"key_share" 扩展中的 "extension_data" 字段包含 KeyShareServerHello 值。
struct {
KeyShareEntry server_share;
} KeyShareServerHello;
复制代码
若是使用 (EC)DHE 密钥创建连接,Server 在 ServerHello 中只提供了一个 KeyShareEntry。这个值必须与,Server 为了协商密钥交换在 Client 提供的 KeyShareEntry 值中选择的值,在同一组中。Server 不能为 Client 的 "supported_groups" 扩展中指定的任何 group 发送 KeyShareEntry 值。Server 也不能在使用 "psk_ke" PskKeyExchangeMode 时候发送 KeyShareEntry 值。若是使用 (EC)DHE 创建连接,Client 收到了包含在 "key_share" 扩展中的 HelloRetryRequest 消息,Client 必须验证在 ServerHello 中选择的 NameGroup 与 HelloRetryRequest 中是否相同。若是不相同,Client 必须当即发送 "illegal_parameter" alert 消息停止握手。
Client 和 Server 二者的 Diffie-Hellman [DH76] 参数都编码在 KeyShareEntry 中的 KeyShare 数据结构中 opaque 类型的 key_exchange 字段中。opaque 类型的值包含指定 group 的 Diffie-Hellman 公钥(Y = g^X mod p),是用大端整数编码的。这个值大小为 p 字节,若是字节不够,须要在其左边添加 0 。
请注意:对于给定的 Diffie-Hellman 组,填充会致使全部的公钥具备相同的长度。
对端必需要相互验证对方的公钥,确保 1 < Y < p-1。此检查确保远程对端正常运行,也使得本地系统不会强制进入进入更小的 subgroup。
Client 和 Server 二者的 ECDHE 参数都编码在 KeyShareEntry 中的 KeyShare 数据结构中 opaque 类型的 key_exchange 字段中。
对于 secp256r1,secp384r1 和 secp521r1,内容是如下结构体的序列化值:
struct {
uint8 legacy_form = 4;
opaque X[coordinate_length];
opaque Y[coordinate_length];
} UncompressedPointRepresentation;
复制代码
X 和 Y 分别是网络字节顺序中 X 和 Y 值的二进制表示。因为没有内部长度标记,因此每一个数字占用曲线参数隐含的 8 位字节数。对于 P-256,这意味着 X 和 Y 中的每个占用 32 个八位字节,若是须要,则在左侧填充零。对于 P-384,它们分别占用 48 个八位字节,对于 P-521,它们各占用 66 个八位字节。
对于曲线 secp256r1, secp384r1, 和 secp521r1,对端必须验证对方的的公钥 Q,以保证这个点是椭圆曲线上有效的点。合适的验证方法定义在 [ECDSA] 中或者 [KEYAGREEMENT]。这个处理包括了 3 步。第一步:验证 Q 不是无穷大的点 (O)。第二步,验证 Q = (x, y) 中的两个整数 x,y 有正确的间隔。第三步,验证 (x, y) 是椭圆曲线方程的正确的解。对于这些曲线,实现方不须要再验证正确的 subgroup 中的成员身份。
对于 X25519 和 X448 来讲,公共值的内容是 [RFC7748] 中定义的相应函数的字节串输入和输出,X25519 的是 32 个字节, X448 的是 56 个字节。
请注意:TLS 1.3 以前的版本容许 point format 协商,TLS 1.3 移除了这个功能,以利于每一个曲线的单独 point format。
为了使用 PSK,Client 还必须发送一个 "psk_key_exchange_modes" 扩展。这个扩展语意是 Client 仅支持使用具备这些模式的 PSK。这就限制了在这个 ClientHello 中提供的 PSK 的使用,也限制了 Server 经过 NewSessionTicket 提供的 PSK 的使用。
若是 Client 提供了 "pre_shared_key" 扩展,那么它必须也要提供 "psk_key_exchange_modes" 扩展。若是 Client 发送不带 "psk_key_exchange_modes" 扩展名的 "pre_shared_key",Server 必须当即停止握手。Server 不能选择一个 Client 没有列出的密钥交换模式。此扩展还限制了与 PSK 恢复使用的模式。Server 也不能发送与建议的 modes 不兼容的 NewSessionTicket。不过若是 Server 必定要这样作,影响的只是 Client 在尝试恢复会话的时候会失败。
Server 不能发送 "psk_key_exchange_modes" 扩展:
enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;
struct {
PskKeyExchangeMode ke_modes<1..255>;
} PskKeyExchangeModes;
复制代码
psk_ke:
仅 PSK 密钥创建。在这种模式下,Server 不能提供 "key_share" 值。
psk_dhe_ke:
PSK 和 (EC)DHE 创建。在这种模式下,Client 和 Server 必须提供 "key_share" 值。
将来分配的任何值都必需要能保证传输的协议消息能够明确的标识 Server 选择的模式。目前 Server 选择的值由 ServerHello 中存在的 "key_share" 表示。
当使用 PSK 而且 PSK 容许使用 early_data 的时候,Client 能够在其第一个消息中发送应用数据。若是 Client 选择这么作,则必须发送 "pre_shared_key" 和 "early_data" 扩展。
Early Data Indication 扩展中的 "extension_data" 字段包含了一个 EarlyDataIndication 值。
struct {} Empty;
struct {
select (Handshake.msg_type) {
case new_session_ticket: uint32 max_early_data_size;
case client_hello: Empty;
case encrypted_extensions: Empty;
};
} EarlyDataIndication;
复制代码
有关 max_early_data_size 字段的使用请看 New Session Ticket Message 章节。
0-RTT 数据(版本,对称加密套件,应用层协议协商协议[RFC7301],等等)的参数与使用中的 PSK 参数相关。对于外部配置的 PSK,关联值是由密钥提供的。对于经过 NewSessionTicket 消息创建的 PSK,关联值是在创建 PSK 链接时协商的值。PSK 用来加密 early data 必须是 Client 在 "pre_shared_key" 扩展中列出的第一个 PSK。
对于经过 NewSessionTicket 提供的 PSK,Server 必须验证所选 PSK 标识中的 ticket age(从 PskIdentity.obfuscated_ticket_age 取 2^32 模中减去 ticket_age_add)距离 ticket 发出的时间是否有一个很小的公差。若是相差的时间不少,那么 Server 应该继续握手,可是要拒绝 0-RTT,而且还要假定这条 ClientHello 是新的,也不能采起任何其余措施。
在第一次 flight 中发送的 0-RTT 消息与其余 flight (握手和应用数据)中发送的相同类型的消息具备相同(加密)的内容类型,但受到不一样密钥的保护。若是 Server 已经接收了 early data,Client 在收到 Server 的 Finished 消息之后,Client 则会发送 EndOfEarlyData 消息表示密钥更改。这条消息将会使用 0-RTT 的 traffic 密钥进行加密。
Server 接收 "early_data" 扩展必须如下面三种方式之一操做:
忽略 "early_data" 扩展,并返回常规的 1-RTT 响应。Server 尝试经过用握手中的流量密钥(traffic key)解密收到的记录,并忽略掉 early data。丢弃解密失败的记录(取决于配置的 max_early_data_size)。一旦一个记录被解密成功,它将会被 Server 看作 Client 第二次 flight 的开始而且 Server 会把它当作普通的 1-RTT 来处理。
经过回应 HelloRetryRequest 来请求 Client 发送另一个 ClientHello。Client 不能在这个 ClientHello 中包含 "early_data" 扩展。Server 经过跳过具备外部内容类型的 "application_data"(说明他们被加密了) 的全部记录来忽略 early data(一样取决于配置的 max_early_data_size)。
在 EncryptedExtensions 中返回本身的 "early_data" 扩展,代表它准备处理 early data。Server 不可能只接受 early data 消息中的一部分。即便 Server 发送了一条接收 early data 的消息,可是实际上 early data 可能在 Server 生成这条消息的时候已经在 flight 了。
为了接受 early data,Server 必须已经接受了 PSK 密码套件而且选择了 Client 的 "pre_shared_key" 扩展中提供的第一个密钥。此外,Server 还须要验证如下的值和选择的 PSK 关联值同样:
这些要求是使用相关 PSK 执行 1-RTT 握手所需的超集。对于外部创建的 PSK,关联值是与密钥一块儿提供的值。对于经过 NewSessionTicket 消息创建的 PSK,关联值是在链接中协商的值,在这期间 ticket 被创建了。
将来的扩展必须定义它们与 0-RTT 的交互。
若是任何检查失败了,Server 不得在响应中附带扩展,而且必须使用上面列出的前两种机制中的一个,丢弃全部 first-flight 数据(所以回落到 1-RTT 或者 2-RTT)。若是 Client 尝试 0-RTT 握手但 Server 拒绝了它,则 Server 一般不会有 0-RTT 记录保护密钥,而必须使用试用解密(使用 1-RTT 握手密钥或者经过在有 HelloRetryRequest 消息的状况下查找明文 ClientHello)找到第一个非 0-RTT 消息。
若是 Server 选择接受 early_data 扩展,那么在处理 early data 记录的时候,Server 必须遵照用相同的标准(指定的相同错误处理要求)来处理全部记录。具体来讲,若是 Server 没法解密已经接受的 "early_data" 扩展中的记录,则它必须发送 "bad_record_mac" alert 消息停止握手。
若是 Server 拒绝 "early_data" 扩展,则 Client 应用程序能够选择在握手完成后从新发送先前在 early data 中发送的应用数据。请注意,early data 的自动重传可能会致使关于链接状态的误判。例如,当协商链接从用于 early data 的协议中选择不一样的 ALPN 协议时,应用程序可能须要构造不一样的消息。一样,若是 early data 假定包含有关链接状态的任何内容,则在握手完成后可能会错误地发送这些内容。
TLS 的实现不该该自动从新发送 early data;应用程序能够很好的决定什么时候重传。除非协商链接选择相同的 ALPN 协议,不然 TLS 实现毫不能自动从新发送 early data。
"pre_shared_key" 扩展用来协商标识的,这个标识是与 PSK 密钥相关联的给定握手所使用的预共享密钥的标识。
这个扩展中的 "extension_data" 字段包含一个 PreSharedKeyExtension 值:
struct {
opaque identity<1..2^16-1>;
uint32 obfuscated_ticket_age;
} PskIdentity;
opaque PskBinderEntry<32..255>;
struct {
PskIdentity identities<7..2^16-1>;
PskBinderEntry binders<33..2^16-1>;
} OfferedPsks;
struct {
select (Handshake.msg_type) {
case client_hello: OfferedPsks;
case server_hello: uint16 selected_identity;
};
} PreSharedKeyExtension;
复制代码
identity:
key 的标签。例如,一个 ticket 或者是一个外部创建的预共享密钥的标签。
obfuscated_ticket_age:
age of the key 的混淆版本。这一章节描述了经过 NewSessionTicket 消息创建,如何为标识(identities)生成这个值。对于外部创建的标识(identities),应该使用 0 的 obfuscated_ticket_age,而且 Server 也必须忽略这个值。
identities:
Client 愿意和 Server 协商的 identities 列表。若是和 "early_data" 一块儿发送,第一个标识被用来标识 0-RTT 的。
binders:
一系列的 HMAC 值。和 identities 列表中的每个值都一一对应,而且顺序一致。
selected_identity:
Server 选择的标识,这个标识是以 Client 列表中标识表示为基于 0 的索引。
每个 PSK 都和单个哈希算法相关联。对于经过 ticket 创建的 PSK,当 ticket 在链接中被创建,这时候用的哈希算法是 KDF 哈希算法。对于外部创建的 PSK,当 PSK 创建的时候,哈希算法必须设置,若是没有设置,默认算法是 SHA-256。Server 必须确保它选择的是兼容的 PSK (若是有的话) 和密钥套件。
在 TLS 1.3 以前的版本中,Server Name Identification (SNI) 的值旨在与会话相关联。Server 被强制要求,与会话关联的 SNI 值要和恢复握手中指定的 SNI 值相互匹配。然而事实上,实现方和他们使用的两个提供的 SNI 值是不一致的,这样就会致使 Client 须要执行一致性的要求。在 TLS 1.3 版本中,SNI 的值始终在恢复握手中被明确的指出,而且 Server 不须要将 SNI 值和 ticket 相关联。不过 Client 须要将 SNI 和 PSK 一块儿存储,以知足 [4.6.1 章节] 的要求。
实现者请注意:会话恢复是 PSK 最主要的用途,实现 PSK/密钥套件 匹配要求的最直接的方法是先协商密码套件,而后再排除任何不兼容的 PSK。任何未知的 PSK (例如:不在 PSK 数据库中,或者用未知的 key 进行编码的)都必须忽略。若是找不到可接受的 PSK,若是可能,Server 应该执行 non-PSK 握手。若是向后兼容性很重要,Client 提供的,外部创建的 PSK 应该影响密码套件的选择。
在接受PSK密钥创建以前,Server 必须先验证相应的 binder 值(见 [4.2.11.2 节])。若是这个值不存在或者未验证,则 Server 必须当即停止握手。Server 不该该尝试去验证多个 binder,而应该选择单个 PSK 而且仅验证对应于该 PSK 的 binder。见 Appendix E.6 和 [8.2 节] 描述了针对这个要求的安全性解释。为了接受 PSK 密钥创建链接,Server 发送 "pre_shared_key" 扩展,标明它所选择的 identity。
Client 必须验证 Server 的 selected_identity 是否在 Client 提供的范围以内。Server 选择的加密套件标明了与 PSK 关联的哈希算法,若是 ClientHello "psk_key_exchange_modes" 有须要,Server 还应该发送 "key_share" 扩展。若是这些值不一致,Client 必须当即用 "illegal_parameter" alert 消息停止握手。
若是 Server 提供了 "early_data" 扩展,Client 必须验证 Server 的 selected_identity 是否为 0。若是返回任何其余值,Client 必须使用 "illegal_parameter" alert 消息停止握手。
"pre_shared_key" 扩展必须是 ClientHello 中的最后一个扩展(这有利于下面的描述的实现)。Server 必须检查它是最后一个扩展,不然用 "illegal_parameter" alert 消息停止握手。
从 Client 的角度来看,ticket 的时间指的是,收到 NewSessionTicket 消息开始到当前时刻的这段时间。Client 决不能使用时间大于 ticket 本身标明的 "ticket_lifetime" 这个时间的 ticket。每一个 PskIdentity 中的 "obfuscated_ticket_age" 字段都必须包含 ticket 时间的混淆版本,混淆方法是用 ticket 时间(毫秒为单位)加上 "ticket_age_add" 字段,最后对 2^32 取模。除非这个 ticket 被重用了,不然这个混淆就能够防止一些相关联链接的被动观察者。注意,NewSessionTicket 消息中的 "ticket_lifetime" 字段是秒为单位,可是 "obfuscated_ticket_age" 是毫秒为单位。由于 ticke lifetime 限制为一周,32 位就足够去表示任何合理的时间,即便是以毫秒为单位也能够表示。
PSK binder 的值造成了 2 种绑定关系,一种是 PSK 和当前握手的绑定,另一种是 PSK 产生之后(若是是经过 NewSessionTicket 消息)的握手和当前握手的绑定。每个在 binder 列表中的条目都会根据有一部分 ClientHello 的哈希副本计算 HMAC,最终 HMAC 会包含 PreSharedKeyExtension.identities 字段。也就是说,HMAC 包含全部的 ClientHello,可是不包含 binder list 。若是存在正确长度的 binders,消息的长度字段(包括总长度,扩展块的长度和 "pre_shared_key" 扩展的长度)都被设置。
PskBinderEntry 的计算方法和 Finished 消息同样。可是 BaseKey 是派生的 binder_key,派生方式是经过提供的相应的 PSK 的密钥派生出来的。
若是握手包括 HelloRetryRequest 消息,则初始的 ClientHello 和 HelloRetryRequest 随着新的 ClientHello 一块儿被包含在副本中。例如,若是 Client 发送 ClientHello,则其 binder 将经过如下方式计算:
Transcript-Hash(Truncate(ClientHello1))
复制代码
Truncate() 函数的做用是把 ClientHello 中的 binders list 移除。
若是 Server 响应了 HelloRetryRequest,那么 Client 会发送 ClientHello2,它的 binder 会经过如下方式计算:
Transcript-Hash(ClientHello1,
HelloRetryRequest,
Truncate(ClientHello2))
复制代码
完整的 ClientHello1/ClientHello2 都会包含在其余的握手哈希计算中。请注意,在第一次发送中,Truncate(ClientHello1)
是直接计算哈希的,可是在第二次发送中,ClientHello1 计算哈希,而且还会再注入一条 "message_hash" 消息。
Client 被容许流式的发送 0-RTT 数据,直到它收到 Server 的 Finished 消息。Client 收到 Finished 消息之后,须要在握手的末尾,发送 EndOfEarlyData 消息。为了防止死锁,当 Server 接收 "early_data" 消息的时候,Server 必须当即处理 Client 的 ClientHello 消息,而后当即回应 ServerHello,而不是等待收到 Client 的 EndOfEarlyData 消息之后再发送 ServerHello。
Server 接下来的 2 条消息,EncryptedExtensions 和 CertificateRequest 消息,包含来自 Server 的消息,这个 Server 肯定了握手的其他部分。这些消息是加密的,经过从 server_handshake_traffic_secret 中派生的密钥加密的。
在全部的握手中,Server 必须在 ServerHello 消息以后当即发送 EncryptedExtensions 消息。这是在从 server_handshake_traffic_secret 派生的密钥下加密的第一条消息。
EncryptedExtensions 消息包含应该被保护的扩展。即,任何不须要创建加密上下文但不与各个证书相互关联的扩展。Client 必须检查 EncryptedExtensions 消息中是否存在任何禁止的扩展,若是有发现禁止的扩展,必须当即用 "illegal_parameter" alert 消息停止握手。
Structure of this message:
struct {
Extension extensions<0..2^16-1>;
} EncryptedExtensions;
复制代码
使用证书进行身份验证的 Server 能够选择性的向 Client 请求证书,这条请求消息(若是发送了)要跟在 EncryptedExtensions 消息后面。
消息的结构体:
struct {
opaque certificate_request_context<0..2^8-1>;
Extension extensions<2..2^16-1>;
} CertificateRequest;
复制代码
certificate_request_context:
一个不透明的字符串,这个字符串用来标识证书请求,并在 Client 的 Certificate 消息中回显。certificate_request_context 必须在本次链接中必须是惟一的(从而防止 Client 的 CertificateVerify 重放攻击)。这个字段通常状况下都是 0 长度,除非用于 [4.6.2] 中描述的握手后身份验证交换。当请求握手后身份验证之后,Server 应该发送不可预测的上下文给 Client (例如,用随机数生成),这样是为了防止攻击者破解。攻击者能够预先计算有效的 CertificateVerify 消息,从而获取临时的 Client 私钥的权限。
extensions:
一组描述正在请求的证书须要的参数扩展集。"signature_algorithms" 扩展必须是特定的,若是其余的扩展被这个消息所定义,那么其余扩展也可能可选的被包含进来。Client 必须忽略不能识别的扩展。
在 TLS 1.3 以前的版本中,CertificateRequest 消息携带了签名算法列表和 Server 可接受的证书受权列表。在 TLS 1.3 中,签名算法列表能够经过 "signature_algorithms" 和可选的 "signature_algorithms_cert" 扩展来表示。然后者证书受权列表能够经过发送 "certificate_authorities" 扩展来表示。
经过 PSK 进行验证的 Server 不能在主握手中发送 CertificateRequest 消息,不过它们可能能够在握手后身份验证中发送 CertificateRequest 消息,前提是 Client 已经发送了 "post_handshake_auth" 扩展名。
Reference:
GitHub Repo:Halfrost-Field
Follow: halfrost · GitHub
Source: halfrost.com/TLS_1.3_Han…