目录
1.2.6 Post-Handshake Client Authentication
1.2.8.1 Diffie-Hellman Parameters
学习目标:
- 学习握手协议
学习内容:
一、握手协议
握手协议用于协商连接的安全参数。把握手消息传递给TLS记录层,TLS记录层把它们封装到一个或多个TLSPlaintext或TLSCiphertext中,然后按照当前活动连接状态的规定进行处理和传输。
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”警告终止握手。
1.1 Key Exchange Messages
在安全通信协议中(如TLS和SSL),密钥交换消息是非常重要的。这些消息的主要目的是:
- 确定安全能力:通过交换密钥消息,客户端和服务器可以了解对方支持哪些安全特性,比如支持的加密算法、认证方法等。
- 建立共享秘密:在通信双方之间创建一个只有他们知道的秘密密钥。这个共享秘密通常用于生成会话密钥。
- 生成流量密钥:这些密钥用于加密实际传输的数据,以确保数据的机密性和完整性。流量密钥的生成是基于之前建立的共享秘密。
1.1.1 密钥协商
在TLS中,密码协商通过client在ClientHello中提供下面4个选项集合来实现:
- 一个密码套件列表,指的是client所支持的AEAD算法或HKDF hash对。
- 一个supported_groups扩展,指的是client支持的(EC)DHE组;一个key_share扩展,包含了一些或全部组所的(EC)DHE共享秘钥(shares )。
- 一个signature_algorithms扩展,指的是client能接受的签名算法;可能会添加一个signature_algorithms_cert扩展来指明证书指定的签名算法。
- 一个pre_shared_key 扩展,包含了一个client知晓的对称密钥;一个psk_key_exchange_modes 扩展,表明与PSK一起使用的密钥交换模式。
对于接收到信息的服务器来说,可以有如下情况:
(1)非PSK情况:服务器独立选择密码套件、(EC)DHE组和密钥共享,以及签名算法/证书对。如果客户端和服务器的“supported_groups”没有重叠,服务器必须中止握手。
(2)PSK情况:服务器必须从客户端的“psk_key_exchange_modes”扩展中选择一个密钥建立模式。服务器将发送一个“pre_shared_key”扩展,指示所选的密钥。
(3)HelloRetryRequest:如果服务器选择(EC)DHE组而客户端没有提供兼容的“key_share”扩展,服务器必须发送HelloRetryRequest消息。
当通过证书进行认证时,服务器将发送 Certificate 和 CertificateVerify消息。根据本文定义的TLS 1.3,总是使用PSK或证书,但不会同时使用两者。
1.1.2 Client Hello
当client第一次连接server时,需要发送ClientHello作为第一个消息。当server用HelloRetryRequest来响应ClientHello时,client也应当发送ClientHello。这种条件下,client必须发送相同的ClientHello(无修改),除非:
- 如果HelloRetryRequest带有一个key_share扩展,则将共享列表用包含指定组中的一个KeyShareEntry的列表取代。
- 如果存在early_data扩展则将其移除。Early data不允许在HelloRetryRequest之后出现。
- 如果HelloRetryRequest中提供了一个cookie扩展,则需要也包含一个cookie扩展。
- 如果需要重新计算obfuscated_ticket_age和binder则更新pre_shared_key扩展、(可选地)删除与server指定的密码族不兼容的任何PSK。
- 选择性增加、删除或更改padding扩展[RFC 7685]的长度。
- 将来定义的其他HelloRetryRequest中扩展允许的修改。
由于TLS 1.3禁止重协商,如果server已经协商完成了TLS 1.3,在任何其它时间收到了ClientHello,必须用unexpected_message警报中止连接。
如果server用以前版本的TLS建立了连接并在重协商时接收了一个TLS1.3的ClientHello,它必须保持以前的协议版本,不能协商TLS 1.3。
这个消息的结构:
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 1.3中固定为0x0303,以避免版本不宽容问题。
- random:由安全随机数生成器生成的32字节。
- legacy_session_id:用于会话恢复,必须非空或为零长度向量。非空时,此值不必是随机的,但应该是不可预测的,以避免实现固定在特定值上(也称为硬化)。
- cipher_suites:客户端支持的密码套件列表,服务器必须忽略不认识的套件。
- legacy_compression_methods:必须设置为零,表示“null”压缩方法。
- extensions:客户端通过扩展请求服务器的扩展功能,服务器必须忽略不认识的扩展。
1.1.3 Server Hello
当可以根据ClientHello协商出一组可接受的握手参数时,server将发送此消息响应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中固定为0x0303,以避免中间设备失败。
- random:由安全随机数生成器生成的32字节,最后8字节在协商TLS 1.2或TLS 1.1时会被覆盖。
- legacy_session_id_echo:回显客户端的legacy_session_id字段内容,如果不匹配则中止握手。
- cipher_suite:服务器选择的密码套件,如果客户端未提供则中止握手。
- legacy_compression_method:值必须为0。
- extensions:包含必需的扩展以建立加密上下文和协商协议版本,必须包含“supported_versions”扩展。
为了与中间件向后兼容, 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类型的消息时,实现必须首先检查Random值,如果它匹配此值,则按照第1.1.4节的描述进行处理。
TLS 1.3具有在服务器随机值中的降级保护机制。当TLS 1.3服务器响应ClientHello协商TLS 1.2或更低版本时,必须在其ServerHello中特别设置其随机值的最后8个字节。
如果协商TLS 1.2,TLS 1.3服务器必须将其随机值的最后8个字节设置为:
44 4F 57 4E 47 52 44 01
如果协商TLS 1.1或更低版本,TLS 1.3服务器必须,而TLS 1.2服务器应该,将其ServerHello.Random值的最后8个字节设置为:
44 4F 57 4E 47 52 44 00
当TLS 1.3客户端收到一个指示TLS 1.2或更低版本的ServerHello时,**必须检查最后8个字节是否不等于上述任一值**。TLS 1.2客户端也**应该检查最后8个字节是否不等于第二个值**,如果ServerHello指示的是TLS 1.1或更低版本。如果发现匹配,客户端**必须用“illegal_parameter”警报中止握手**。这种机制提供了有限的保护,以防止降级攻击,这超出了由Finished交换所提供的保护:因为ServerKeyExchange(存在于TLS 1.2及更低版本中的一个消息)包括对两个随机值的签名,只要使用临时密钥密码,活跃的攻击者就不可能在不被检测到的情况下修改随机值。**当使用静态RSA时,它不提供降级保护**。
注意,这是与[RFC5246]的变化,因此实际上许多TLS 1.2客户端和服务器不会按上述规定行事。
执行TLS 1.2或更早版本重协商的遗留TLS客户端,在重协商期间收到TLS 1.3 ServerHello时,**必须用“protocol_version”警报中止握手**。注意,当已经协商了TLS 1.3时,重协商是不可能的。
1.1.4 Hello Retry Request
当服务器能够找到一组可接受的参数,但ClientHello消息中不包含足够的信息以继续握手时,服务器将发送此消息作为对ClientHello的响应。如第1.1.3节中讨论的,HelloRetryRequest与ServerHello消息具有相同的格式,legacy_version、legacy_session_id_echo、cipher_suite和legacy_compression_method字段具有相同的含义。
服务器的扩展**必须包含“supported_versions”**。此外,它**应该包含客户端生成正确ClientHello对所需的最小扩展集**。与ServerHello一样,HelloRetryRequest**不得包含任何客户端在其ClientHello中未首先提供的扩展**,cookie扩展(见第1.2.2节)除外。
在收到HelloRetryRequest后,客户端**必须检查legacy_version、legacy_session_id_echo、cipher_suite和legacy_compression_method**,然后处理扩展,首先使用“supported_versions”确定版本。如果HelloRetryRequest不会导致ClientHello的任何更改,客户端**必须用“illegal_parameter”警报中止握手**。如果客户端在同一连接中收到第二个HelloRetryRequest(即ClientHello本身是对HelloRetryRequest的响应),它**必须用“unexpected_message”警报中止握手**。否则,客户端**必须处理HelloRetryRequest中的所有扩展并发送第二个更新的ClientHello**。本规范中定义的HelloRetryRequest扩展包括:
- supported_versions(见第1.2.1节)
- cookie(见第1.2.2节)
- key_share(见第1.2.8节)
如果客户端收到一个未提供的密码套件,它**必须中止握手**。服务器**必须确保在接收符合要求的更新ClientHello时协商相同的密码套件**(如果服务器选择密码套件作为协商的第一步,则会自动发生)。在收到ServerHello后,客户端**必须检查ServerHello中提供的密码套件是否与HelloRetryRequest中的相同**,否则用“illegal_parameter”警报中止握手。
此外,在其更新的ClientHello中,客户端**不应该提供与选定密码套件的哈希不同的任何预共享密钥**。这允许客户端避免在第二个ClientHello中为多个哈希计算部分哈希记录。
HelloRetryRequest“supported_versions”扩展中的selected_version值**必须保留在ServerHello中**,如果值发生变化,客户端**必须用“illegal_parameter”警报中止握手**。
1.2 Extensions
结构体如下:
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;
这里:
- "extension_type"表示指定扩展类型。
- "extension_data"包含指定扩展类型的信息。
扩展通常以请求/响应方式构造,尽管一些扩展仅仅是没有相应响应的指示。客户端在ClientHello消息中发送其扩展请求,服务器在ServerHello、EncryptedExtensions和HelloRetryRequest消息中发送扩展响应。服务器在CertificateRequest消息中发送扩展请求,客户端可能以Certificate消息进行响应。服务器也可以在NewSessionTicket中发送未经请求的扩展,但客户端不直接响应这些。
如果对端没有发送相应的扩展请求(除HelloRetryRequest中的cookie扩展外),严禁发送扩展响应。在接收到这样的扩展时,端点必须用“unsupported_extension”警报中止握手。
下表列出了给定扩展可能出现的消息:CH(ClientHello),SH(ServerHello),EE(EncryptedExtensions),CT(Certificate),CR(CertificateRequest),NST(NewSessionTicket)和HRR ( HelloRetryRequest)。如果接收到可识别的扩展,并且对应消息未指定它,必须用“illegal_parameter”警报来中止握手。
+--------------------------------------------------+-------------+
| 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”(第1.2.11节)必须是ClientHello中的最后一个扩展(但在ServerHello扩展块中可以出现在任何位置)。在给定的扩展块中,不允许有超过一个相同类型的扩展。
在TLS 1.3中,与TLS 1.2不同,即使在恢复-PSK模式下,扩展也会为每次握手进行协商。然而,0-RTT参数是之前握手协商的参数;不匹配可能需要拒绝0-RTT(见第1.2.10节)。
1.2.1 Supported Versions
struct {
select (Handshake.msg_type) {
case client_hello:
ProtocolVersion versions<2..254>;
case server_hello: /* and HelloRetryRequest */
ProtocolVersion selected_version;
};
} SupportedVersions;
“supported_versions”扩展用于客户端指示其支持的TLS版本,并由服务器指示其使用的版本。该扩展包含一个按偏好顺序排列的支持版本列表,最偏好的版本排在第一位。本规范的实现**必须在ClientHello中发送此扩展**,包含它们准备协商的所有TLS版本(对于本规范,这意味着至少是0x0304,但如果允许协商先前版本的TLS,它们也**必须出现**)。
如果此扩展不存在,符合本规范并且也支持TLS 1.2的服务器**必须协商TLS 1.2或更早版本**,即使ClientHello.legacy_version是0x0304或更高版本。服务器**可以在收到legacy_version为0x0304或更高版本的ClientHello时中止握手**。
如果ClientHello中存在此扩展,服务器**不得使用ClientHello.legacy_version值进行版本协商**,并且**必须仅使用“supported_versions”扩展来确定客户端偏好**。服务器**只能选择该扩展中存在TLS版本**,并且**必须忽略该扩展中出现的任何未知版本**。请注意,这种机制使得如果一方支持稀疏范围,则可以协商TLS 1.2之前的版本。选择支持TLS 1.3之前版本的TLS 1.3实现**应该支持TLS 1.2**。服务器**必须准备好接收包含此扩展但不在版本列表中包含0x0304的ClientHellos**。
协商TLS 1.3之前的TLS版本的服务器**必须设置ServerHello.version**,并且**不得发送“supported_versions”扩展**。协商TLS 1.3的服务器**必须通过发送包含选定版本值(0x0304)的“supported_versions”扩展来响应**。它**必须将ServerHello.legacy_version字段设置为0x0303(TLS 1.2)**。客户端**必须在处理ServerHello的其余部分之前检查此扩展**(尽管他们将不得不解析ServerHello以读取扩展)。如果此扩展存在,客户端**必须忽略ServerHello.legacy_version值**,并且**必须仅使用“supported_versions”扩展来确定选定的版本**。如果ServerHello中的“supported_versions”扩展包含客户端未提供的版本或包含TLS 1.3之前的版本,客户端**必须用“illegal_parameter”警报中止握手**。
1.2.2 Cookie
struct {
opaque cookie<1..2^16-1>;
} Cookie;
Cookies在TLS握手中有两个主要目的:
- 验证客户端的可达性:允许服务器强制客户端证明其在表面上的网络地址是可达的(从而提供一定程度的DoS保护)。这主要用于非面向连接的传输(例如,参见[RFC6347])。
- 将状态卸载到客户端:允许服务器将状态卸载到客户端,从而允许它在不存储任何状态的情况下发送HelloRetryRequest。服务器可以通过在HelloRetryRequest cookie中存储ClientHello的哈希(使用某种适当的完整性保护算法进行保护)来实现这一点。
当发送HelloRetryRequest时,服务器可以提供一个“cookie”扩展给客户端(这是对通常规则的一个例外,即唯一可以发送的扩展是那些出现在ClientHello中的扩展)。当发送新的ClientHello时,客户端必须将收到的HelloRetryRequest中的扩展内容复制到新的ClientHello中的“cookie”扩展中。客户端不得在后续连接的初始ClientHello中使用cookies。
当服务器以无状态方式运行时,它可能会在第一个和第二个ClientHello之间收到一个未受保护的change_cipher_spec记录。由于服务器没有存储任何状态,这将看起来像是收到的第一条消息。以无状态方式运行的服务器必须忽略这些记录。
1.2.3 Signature Algorithms
TLS 1.3提供了两个扩展来表示数字签名里会用哪种签名算法。signature_algorithms_cert扩展提供证书中的签名,signature_algorithms扩展是从TLS 1.2出现的,提供CertificateVerify消息中的签名。证书中的秘钥也必须是所使用的签名算法的适当类型。这是RSA密钥和PSS签名的一个特殊问题,如下所述。如果没有signature_algorithms_cert扩展,signature_algorithms扩展也提供证书中的出现的签名。客户端如果期望服务器通过证书证明其身份,必须发送signature_algorithms扩展。如果服务器通过证书证明了其身份,且客户端没有发送signature_algorithms扩展,那么服务器必须以missing_extension alert终止握手.
signature_algorithms_cert扩展允许支持证书的不同算法集的实现明确表达自己的能力。TLS 1.2实现应该也处理这些扩展。两种情况里具有相同策略的实现都可以使用signature_algorithms_cert扩展。
ClientHello扩展中的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类型,将被此替换。 我们在全文中使用术语Signature Algorithms。
每个SignatureScheme值列出客户端愿意验证的单一签名算法。 这些值以优先级的降序排列。 注意, 签名算法输入任意长度的消息, 而不是摘要。
上面列出的码点组具有以下含义:
(1)RSASSA-PKCS1-v1_5算法:
- 使用RSASSA-PKCS1-v1_5签名算法,与相应的哈希算法结合使用。
- 这些值仅指证书中出现的签名,并未定义用于签名的TLS握手消息中,尽管它们可能出现在“signature_algorithms”和“signature_algorithms_cert”中,以与TLS 1.2向后兼容。
(2)ECDSA算法:
- 使用ECDSA签名算法,相应的曲线在ANSI X9.62和FIPS 186-4中定义,相应的哈希算法在[SHS]中定义。
- 签名表示为DER编码的ECDSA-Sig-Value结构。
(3)RSASSA-PSS RSAE算法:
- 使用RSASSA-PSS签名算法,带有掩码生成函数1。
- 掩码生成函数中使用的摘要和被签名的摘要都是相应的哈希算法。
- 盐的长度必须等于摘要算法的输出长度。
- 如果公钥在X.509证书中,则必须使用rsaEncryption OID。
(4)EdDSA算法:
- 使用EdDSA签名算法,如RFC8032或其后续版本中定义。
- 这些算法对应于“PureEdDSA”算法,而不是“prehash”变体。
(5)RSASSA-PSS PSS算法:
- 使用RSASSA-PSS签名算法,带有掩码生成函数1。
- 掩码生成函数中使用的摘要和被签名的摘要都是相应的哈希算法。
- 盐的长度必须等于摘要算法的长度。
- 如果公钥在X.509证书中,则必须使用RSASSA-PSS OID。
- 在证书签名中使用时,算法参数必须是DER编码的。
- 如果相应公钥的参数存在,则签名中的参数必须与公钥中的参数相同。
遗留算法指的是那些因使用已知弱点的算法而被弃用的算法,具体来说是SHA-1,它在这种情况下与以下两种算法之一结合使用:
- 使用RSASSA-PKCS1-v1_5的RSA。
- ECDSA。
这些值仅指证书中出现的签名,并未定义用于签名的TLS握手消息中,尽管它们可能出现在“signature_algorithms”和“signature_algorithms_cert”中,以与TLS 1.2向后兼容。终端不应该协商这些算法,但为了向后兼容,允许这样做。提供这些值的客户端必须将它们列为最低优先级(在SignatureSchemeList中列在所有其他算法之后)。
TLS 1.3服务器不得提供一个使用SHA-1签名的证书,除非没有它就无法生成有效的证书链.
自签名证书或信任锚点证书上的签名不会被验证,因为它们标志着认证路径的开始(参见[RFC5280]第3.2节)。开始认证路径的证书可以使用一种在“signature_algorithms”扩展中未声明为支持的签名算法。
1.2.4 Certificate Authorities
certificate_authorities扩展用于指示端点支持的证书授权机构(CA),并且接收端点应该使用它来指导证书选择。
certificate_authorities扩展的内容由CertificateAuthoritiesExtension结构组成。
opaque DistinguishedName<1..2^16-1>;
struct {
DistinguishedName authorities<3..2^16-1>;
} CertificateAuthoritiesExtension;
authorities: 可接受证书颁发机构的名称[X501]列表,以 DER编码[X690]格式表示。 这些名称为信任锚或从属CA指定所需名称,因此,该消息可以用于描述已知的信任锚以及期望的授权空间。
客户端可以在ClientHello消息中发送certificate_authorities扩展。 服务器可以在CertificateRequest消息中发送。
trusted_ca_keys扩展[RFC6066]目的类似,但更为复杂,TLS 1.3中不使用,但可能出现在先前TLS版本客户端的ClientHello消息中。
1.2.5 OID Filters
oid_filters扩展允许服务器提供一组希望客户端的证书匹配的OID/value对。如果是服务器提供的扩展,必须只能在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;
filters: 证书扩展OID列表[RFC5280]和允许值,以DER-encoded [X690]格式呈现。一些证书扩展OID允许多个值(如Extended Key Usage)。如果服务器包含了一个非空filters列表,回应的客户端证书必须包含所有客户端认识的指定扩展OID。对于每个客户端认识的扩展OID,所有指定值都必须出现在客户端证书中(但证书也可以有其他值)。然而,客户端必需忽略并跳过任何不认识的扩展OID。如果客户端忽略一些要求的证书扩展OID,并提供一个不满足请求的证书,服务器可以自行决定是在没有客户端认证的情况下继续连接,还是以"unsupported_certificate" alert放弃握手。任何给定的OID禁止在filters列表中出现多次。
PKIX RFCs定义了各种各样的证书扩展OID和对应值。匹配证书扩展值不一定位相等,取决于类型。TLS实现最好由它们的PKI库使用证书扩展OID执行证书选择。
本文为[RFC5280]中的两个标准证书扩展定义了匹配规则:
- 当请求中assert的所有key usage位也在Key Usage证书扩展中assert,则证书中的Key Usage扩展与请求匹配。
- 当请求中所有秘钥用途OID也在Extended Key Usage证书扩展中找到,则证书中的Extended Key Usage扩展与请求匹配。
不同规范可以为其他证书扩展定义匹配规则。
1.2.6 Post-Handshake Client Authentication
post_handshake_auth扩展用于表示客户端愿意执行握手后的认证(4.6.2)。 服务器禁止向不提供此扩展的客户端发送post-handshake CertificateRequest。 服务器不得发送此扩展。
struct {} PostHandshakeAuth;
post_handshake_auth扩展的extension_data字段为零长度。
1.2.7 Supported Groups
当客户端发送supported_groups扩展时,表示客户端支持的用于密钥交换的命名组(named groups),顺序从最优选到最不优选。
注意: 在TLS 1.3之前的TLS版本中,此扩展名称为elliptic_curves,并且只包含椭圆曲线组(见[RFC4492]和[RFC7919])。 此扩展也用于协商ECDSA曲线。签名算法现在独立协商(见4.2.3)。
此扩展的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保留供私用。
- Finite Field Groups(DHE):表示支持相应的有限域组,在[RFC7919]中定义。值0x01FC至0x01FF保留供私用。
named_group_list中的项根据发送者的偏好排序(最优先选择的排在第一位).
从TLS 1.3开始,服务器被允许向客户端发送supported_groups扩展。客户端在成功完成握手之前,不得根据supported_groups中找到的任何信息采取行动。但是,客户端可以使用从成功完成的握手中获得的信息来更改后续连接中key_share扩展中使用的组
如果服务器有一个它更偏好的组(相对于key_share扩展中的组),但仍然愿意接受ClientHello,它应该发送supported_groups来更新客户端对其偏好的视图。这个扩展应该包含服务器支持的所有组,无论客户端是否当前支持它们。
1.2.8 Key Share
key_share扩展包含端点的加密参数。
客户端可以发送空的 client_shares向量,以一个额外的往返代价从服务器请求组选择(见1.1.4)
struct {
NamedGroup group;
opaque key_exchange<1..2^16-1>;
} KeyShareEntry;
- group:要交换的密钥的命名组。
- key_exchange: 密钥交换信息。 此字段的内容由指定组及其相应的定义确定。有限域Diffie-Hellman [DH76]参数在1.2.8.1中描述。椭圆曲线Diffie-Hellman参数在1.2.8.2中描述。
在ClientHello消息中,这个扩展的extension_data字段包含一个KeyShareClientHello值:
struct {
KeyShareEntry client_shares<0..2^16-1>;
} KeyShareClientHello;
- client_shares:一组提供的KeyShareEntry值,以客户端偏好降序排列。
如果客户端请求HelloRetryRequest,则此向量可以为空。 每个KeyShareEntry值必须对应于在supported_groups扩展中提供的组,并且必须以相同的顺序排列。 然而,值可以是supported_groups扩展的非连续子集,并且可以省略最优选的组。如果最优选的组是新的,并且没有足够的空间更高效地预生成共享秘钥,则可能出现这种情况。
客户端可以提供很多KeyShareEntry值,数量跟提供的支持组一样,每个值表示一组密钥交换参数。例如,客户端可能为几个椭圆曲线或多个FFDHE组提供共享秘钥。每个KeyShareEntry的key_exchange值必须独立生成。 客户不得为同一组提供多个KeyShareEntry值。 客户端不得为supported_groups扩展中未列出的组提供KeyShareEntry值。 服务器可能会检查违反了这些规则的行为,如果违反了则使用illegal_parameter警报来中止握手。
在HelloRetryRequest消息中,此扩展的extension_data字段包含一个KeyShareHelloRetryRequest值:
struct {
NamedGroup selected_group;
} KeyShareHelloRetryRequest;
- selected_group:服务器打算协商的都支持的组,准备为请求重试ClientHello/KeyShare。
在HelloRetryRequest中接收到此扩展时,客户端必须验证:
- selected_group字段必须对应于在原始ClientHello中的supported_groups扩展中提供的组
- selected_group字段不得对应于在原始ClientHello中的key_share扩展中提供的组
如果这些检查中的任一个失败,则客户端必须用illegal_parameter警报来中止握手。 否则,当发送新的ClientHello时,客户端必须用仅包含新KeyShareEntry的扩展替换原来的key_share扩展,新的扩展对应于触发HelloRetryRequest的selected_group字段中指示的组。
在ServerHello中,此扩展的extension_data字段包含KeyShareServerHello值:
struct {
KeyShareEntry server_share;
} KeyShareServerHello;
- server_share:一个跟客户端共享的在同一个组中的KeyShareEntry值。
如果使用(EC)DHE密钥协商,服务器在ServerHello中只提供一个KeyShareEntry。 该值必须与服务器为协商密钥交换选择的客户端提供的KeyShareEntry值在同一组。服务器不得为supported_groups扩展中指定的任何组发送KeyShareEntry,并且在使用psk_ke的PskKeyExchangeMode时不得发送KeyShareEntry。如果使用(EC)DHE密钥协商,并且客户端收到包含key_share扩展的HelloRetryRequest,客户端必须验证ServerHello中选择的NamedGroup与HelloRetryRequest中的相同,否则必须以illegal_parameter警报中止握手。
1.2.8.1 Diffie-Hellman Parameters
客户端和服务器的Diffie-Hellman [DH76]参数都编码在KeyShare结构中的KeyShareEntry的opaque key_exchange字段中。opaque值包含指定组(参见[RFC7919]的组定义)的Diffie-Hellman公共值(Y = g ^ X mod p),编码为大端字节序整数,并用填充 0到左侧至p字节。
注意:对于给定的Diffie-Hellman组,填充使所有公钥长度相同。
对端应该通过确保1<Y<p-1来验证对方的公钥Y。此检查确保对端在行为正常,并且不强制本地系统进入小型组。
1.2.8.2. ECDHE参数
客户端和服务器的ECDHE参数都编码在KeyShare结构中KeyShareEntry的opaque key_exchange字段中。
对于secp256r1,secp384r1和secp521r1,是以下结构体的序列化值:
struct {
uint8 legacy_form = 4;
opaque X[coordinate_length];
opaque Y[coordinate_length];
} UncompressedPointRepresentation;
X和Y分别是X和Y值的网络序二进制表示。由于没有内部长度标记,因此每个数字占用曲线参数隐含的字节数。 对于P-256,这意味着X和Y分别使用32字节,如果需要,则在左侧填充零。对于P-384,分别占用48字节,对于P-521,各占用66字节。
对于曲线secp256r1,secp384r1和secp521r1,对端必须通过确保该点是椭圆曲线上的有效点来验证彼此的公共值Y。相应的验证程序在[X962]的4.3.7中定义,或者在[KEYAGREEMENT]的5.6.2.6中定义。 该过程由三个步骤组成:
(1)验证Q不是无穷大点(O),
(2)验证Q =(x,y)中x和y两个整数都在正确的间隔,
(3)确保(x,y)是椭圆曲线方程的正确解。对于这些曲线,实现者不需要验证正确子组中的成员。
对于X25519和X448,公共值的内容是[RFC7748]中定义的相应功能的字节串输入和输出:X25519是32字节,X448是56字节。
注意: 1.3之前版本的TLS允许 point format 协商;TLS 1.3删除了此功能,从而使每个曲线有一个point format。
1.2.9 Pre-Shared Key Exchange Modes
为了使用PSK,客户端还必须发送一个psk_key_exchange_modes扩展。 此扩展的意思是客户端仅支持使用这些模式的PSK,这限制了在这个ClientHello中提供的PSK的使用以及服务器通过NewSessionTicket提供的PSK的使用。
如果客户端提供了一个pre_shared_key扩展,也必须提供一个psk_key_exchange_modes扩展。 如果客户端提供了pre_shared_key,但没提供 psk_key_exchange_modes,服务器必须中止握手。服务器不得选择客户端未给出的密钥交换模式。此扩展还限制PSK恢复使用的模式。服务器不应发送与通告模式不兼容的NewSessionTicket,但是如果服务器这样做,影响将只是客户端尝试恢复会失败。
服务器不得发送psk_key_exchange_modes扩展。
enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;
struct {
PskKeyExchangeMode ke_modes<1..255>;
} PskKeyExchangeModes;
- psk_ke: PSK-only密钥建立。 在这种模式下,服务器不得提供key_share值。
- psk_dhe_ke: PSK和(EC)DHE的秘钥建立。 在这种模式下,客户端和服务器必须提供key_share值(见1.2.8)。
任何将来分配的值必须确保传输协议消息明确识别服务器选择的模式。目前这由ServerHello中的key_share表示。
1.2.10 Early Data Indication
当使用PSK时,客户端可以在第一个消息中发送应用数据。 如果客户端选择这样做,就必须提供early_data扩展和pre_shared_key扩展。
此扩展的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字段的使用见1.6.1。
0-RTT参数(版本号、对称密码套件、ALPN协议[RFC7301]等)是使用PSK的关联值。对于外部配置的PSK,关联值与秘钥一起配置。对于通过NewSessionTicket消息确定的PSK,关联值是在确定PSK的连接里协商的。用于加密早期数据的PSK必须是客户端pre_shared_key扩展中列出的第一个PSK。
对于通过NewSessionTicket提供的PSK,服务器必须验证所选PSK标识的 ticket 生存期(PskIdentity.obfuscated_ticket_age 模 2 ^ 32中减去ticket_age_add)在从ticket开始使用的小时间范围内(见第8章)。如果不是,服务器应该继续握手,但拒绝0-RTT,并且不应该采取任何假定该ClientHello是全新的其他操作。
在第一个消息中发送的0-RTT消息与其他消息(握手和应用程序数据)中发送的相应消息具有相同(加密)的内容类型,但受到不同密钥的保护。在收到服务器的Finished消息后,如果服务器已接收到早期数据,则会发送EndOfEarlyData消息以指示密钥更改。该消息使用0-RTT流量密钥加密。
接收early_data扩展的服务器必须以三种方式之一进行操作:
- 忽略扩展并返回常规的1-RTT响应。然后,服务器忽略早期数据并尝试使用握手流量秘钥解密收到的数据,忽略解密失败的数据(直到配置的max_early_data_size)。一旦数据成功解密,则被当做客户端第二个消息的开始,服务端当做普通1-RTT握手继续处理。
- 通过响应HelloRetryRequest请求客户端发送另一个ClientHello。 客户端不得在其后续ClientHello中包含early_data扩展。 然后,服务器跳过外部内容类型application_data的所有记录(表示被加密)来忽略早期数据,直到配置的max_early_data_size长度。
- 在EncryptedExtensions中返回自己的early_data扩展,表示它打算处理早期的数据。 服务器不可能只接受早期数据消息的一部分。 即使服务器发送接收早期数据的消息,但是实际的早期数据本身可能已经在服务器生成此消息时发送了
为了接受早期数据,服务器必须先接受了PSK密码套件并且选择了客户端的pre_shared_key扩展中提供的第一个密钥。此外,必须验证以下值与选择的PSK相关联:
- TLS版本号
- 选择的密码套件
- 选择的ALPN协议 [RFC7301](如果有)
这些要求是使用相关PSK执行1-RTT握手的要求的超集。对于外部配置的PSK,关联值(associated values)与秘钥一起提供。对于通过NewSessionTicket消息确定的PSK,关联值是通过连接协商的。
未来的扩展必须定义它们与0-RTT的交互。
如果任一上述检查失败,服务器不得使用扩展进行响应,并且必须使用上面前两种机制之一丢弃所有第一个报文中的数据(回退到1-RTT或2-RTT)。 如果客户端尝试进行0-RTT握手,但是服务器拒绝,则服务器通常没有0-RTT保护密钥,必须使用试用解密(使用1-RTT握手密钥或在HelloRetryRequest的情况下通过寻找cleartext ClientHello)找到第一个非0-RTT消息。
如果服务器选择接受early_data扩展,那么在处理早期数据时,它必须遵守与所有记录相同的错误处理要求。 具体来说,如果服务器在接受early_data扩展后无法解密任何0-RTT记录,则必须根据5.2使用bad_record_mac警报终止连接。
如果服务器拒绝early_data扩展,则客户端应用程序可以在握手完成后重新发送早期数据。 请注意,早期数据的自动重新传输可能导致关于连接状态不正确的假设。 例如,当协商的连接选择与早期数据不同的ALPN协议时,应用程序可能需要构建不同的消息。 类似地,如果早期数据假定任何连接状态,则握手完成后可能发送错误。
TLS实现不应自动重新发送早期数据;应用程序能够更好地决定重新传输是否合适。 除非协商的连接选择相同的ALPN协议,否则TLS实现不得自动重新发送早期数据。
1.2.11 Pre-Shared Key Extension
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:秘钥标签。例如,附录B.3.4中定义的ticket,或外部配置的psk标签。
- obfuscated_ticket_age:密钥生存时间的混淆版本。1.2.11.1描述了怎样通过NewSessionTicket消息建立的标识生成此值。对于外部配置的标识,应该使用0的obfuscated_ticket_age,服务器必须忽略该值。
- Identities:客户端想要与服务器协商的标识列表。如果与early_data扩展一起发送(1.2.10),第一个标识是用于0-RTT数据。
- Binders:一系列HMAC值,每个标识值一个,并且以相同顺序排列,计算过程如下所述。
- selected_identity:服务器选择的标识,以客户端列表中的标识表示为(0-based)的索引。
每个PSK与一个哈希算法相对应。对于通过 ticket机制建立的PSK(1.6.1节),是建立ticket的连接上的 KDF Hash算法。对于外部配置的PSK,当PSK建立时,必须设置哈希算法,或者没指定算法时默认为SHA-256。 服务器必须确保选择兼容的PSK(如果有的话)和密码套件。
TLS 1.3之前的版本中,服务器名字标识(Server Name Identification,SNI)值需要与session相对应([RFC6066])中第3章),服务器需要强制SNI值与握手恢复中指定的匹配session相对应。然而,现实中使用提供的SNI值中的哪一个,实现并不一致,这导致客户端事实上强制执行一致性要求。在TLS 1.3中,SNI值总是在恢复握手中指定,服务器没必要将SNI值与ticket关联。但客户端应该与PSK一起存储SNI来满足1.6.1中的要求。
实现需注意:当会话恢复是PSK主要应用场景时,实现PSK/密码套件匹配要求的最直接方法是先协商密码套件,然后排除任何不兼容的PSK。任何未知的PSK(如不在PSK数据库中或者以未知秘钥加密)应该忽略。如果没有可接受的PSK,服务器可能的话应该执行non-PSK握手。如果前向兼容很重要,客户端的外部配置PSK应该决定密码套件选择。
在接受PSK密钥建立之前,服务器务必验证相应的binder值(1.2.11.2)。如果此值不存在或未验证,则服务器必须中止握手。服务器不应该尝试验证多个binder,而是应该选择单个PSK并且仅验证对应于该PSK的binder。该要求的安全原理见8.2和附录E.6。为了接受PSK密钥建立,服务器发送pre_shared_key扩展来指示选择的标识。
客户端必须验证服务器的selected_identity是否在客户端提供的范围内,服务器选择了包含与PSK关联哈希的加密套件,并且如果ClientHello的psk_key_exchange_modes扩展需要,服务器应该发送key_share扩展。 如果这些值不一致,客户端必须使用illegal_parameter警报中止握手。
如果服务器提供了early_data扩展, 客户端必须验证服务器的selected_identity是否为0。如果返回任何其他值,客户端必须使用illegal_parameter警报中止握手。
pre_shared_key扩展必须是ClientHello中的最后一个扩展(这有助于如下所述的实现)。 服务器必须检查它是最后一个扩展,否则以illegal_parameter警报握手失败。
1.2.11.1. Ticket Age
客户端对ticket生存时间的看法是收到NewSessionTicket消息后的时间。 客户端不得尝试使用ticket生存时间大于ticket_lifetime值的ticket。每个PskIdentity 的obfuscated_ticket_age字段包含了ticket生存时间的混淆版本,以毫秒为单位,加上ticket中包含的ticket_age_add值(1.6.1),模2^32。这个添加可以防止被动观察者关联连接,除非tiket被重复使用。
请注意, NewSessionTicket消息中的ticket_lifetime字段的单位是秒,但obfuscated_ticket_age的单位是毫秒。因为ticket生存期被限制在一周之内,32位足以代表任何可信的生存期,即使是以毫秒为单位。
1.2.11.2. PSK Binder
PSK binder值绑定了PSK和当前握手,并且绑定生成PSK的握手(如果通过NewSessionTicket消息)和当前握手。binder列表中的每个条目被计算为直到(并包括)PreSharedKeyExtension.identities字段的ClientHello部分(包括握手首部)上的HMAC(transcript hash,见1.4.1)。也就是说,它包括所有ClientHello,但不包括binder列表本身。消息的长度字段(包括总长度,扩展块长度和pre_shared_key扩展长度)都是按照正确长度的binder来设置的。
PskBinderEntry以Finished消息(1.4.4)相同方式计算,但是BaseKey是提供相应PSK的密钥派生表派生的binder_key(7.1)。
如果握手包括HelloRetryRequest,则初始ClientHello和HelloRetryRequest与新的ClientHello一起被包括在副本中。例如,如果客户端发送ClientHello1,则其binder将如下计算:
Transcript-Hash(Truncate(ClientHello1))
其中Truncate()从ClientHello中移除binder列表。
如果服务器响应HelloRetryRequest,然后客户端然后发送ClientHello2,其binder将通过计算:
Transcript-Hash(ClientHello1,
HelloRetryRequest,
Truncate(ClientHello2))
完整的ClientHello1/ClientHello2包括在所有其他握手哈希计算中。注意在第一次发送中,Truncate(ClientHello1)是直接哈希的,但在第二次发送中ClientHello1先哈希,然后作以message_hash消息注入,如1.4.1所述。
1.2.11.3. 处理顺序
客户端接收服务器的Finished之前都可以stream 0-RTT数据,然后发送EndOfEarlyData消息,接着是剩下的握手。 为了避免死锁,当接受early_data时,服务器必须处理客户端的ClientHello,然后立即发送消息,而不是在发送ServerHello前等待客户端的EndOfEarlyData消息。