0%

tls1.3【翻译】

tls1.3翻译备忘

摘要

该文档定义了TLS(Transport Layer Security)协议的1.3版本。TLS可以让C/S应用在互联网上通过一种能抵御信息泄露、破坏或篡改的方式安全通信。
该文档更新了RFC 5705和6066,废弃了RFC 5077、5246、6961。也对TLS1.2的实现做了些新的规定。

1. 简介

TLS的首要目标在两个通信端点之间提供一个安全通道;对底层传输通道的唯一要求就是传输可靠、没有乱序。具体来说,这个安全通道应该提供一下特性:

  • 可鉴别:server端总是可被鉴别,client端可选被鉴别。鉴别可以通过非对称算法(比如RSA,ECDSA或者EdDSA)或者对称的预共享密钥(PSK)进行。
  • 可信: 通道建立后传输的数据只能被两端看到。TLS不会隐藏传输数据的长度,但端点可以对TLS记录层进行填充,以此来隐藏实际长度,提高抵抗流量分析的能力。
  • 完整性:通道建立后传输的数据一旦被攻击者篡改,就可以被检测到。

这些特性在面对那些完全掌握网络的攻击者的时候,应该也要保证是对的,见[RFC3552]。附录E对相关的安全特性有个更完整的说明。

TLS包含两个主要部分:

  • 一个握手协议(#4),负责鉴别通信实体、协商加密模式和参数、建立共享的密钥材料。握手协议被设计的可以抗篡改:一个主动攻击者是没有办法迫使通信双方协商出一个跟没有被攻击时不一样的参数的。
  • 一个记录层协议(#5),用握手协议建立的参数来保护通信双方的流量。记录层协议将流量分割成一系列的record,每个record都由通信密钥单独保护。

TLS是跟应用协议无关的: 更高层的协议可以透明的建立在TLS协议之上。但TLS标准并没规定应用协议怎么用TLS来增加安全性: 怎么初始化TLS握手、怎么处理交换的鉴别证书这些问题,被留给那些设计和实现应用协议的人去权衡。

本文档定义了TLS1.3版本。尽管TLS1.3跟之前的版本没有直接兼容,但所有版本的TLS会用一个版本协商机制来协商出一个两端共同支持的版本。

本文档取代和废弃了之前的TLS版本,包括TLS1.2[RFC5246]。也废弃了[RFC5077]中定义的ticket机制,用#2.2中的新机制替代。TLS1.3调整了密钥推导的方式,因此在#7.5中更新了[RFC5705]。它也在#4.4.2.1中调整了OCSP消息的携带方式,因此更新了[RFC6066],废弃了[RFC7961]。

1.1 约定和术语

关键词*”MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECONMMENDED”, “NOT RECOMMENDED”, “MAY”, “OPTIONAL”*按照[RFC2119][RFC8174]中描述的解释,当且仅当他们是大写的时候。

本文档会用到以下术语:

  • client: 发起TLS连接的端点。
  • connection: 两个端点之间的传输层连接。
  • endpoint: connection中的client或server。
  • handshake: client和server之间为建立TLS后续使用的参数进行的初始化协商。
  • peer: 一个端点。当讨论一个具体端点的时候,”peer”指的是讨论中不是首要对象的另一个端点。
  • receiver: 接收record的端点。
  • sender: 发送record的端点。
  • server: 不是发起TLS连接的端点。

1.2. 跟TLS1.2的主要区别

以下列出的是TLS1.2和1.3之间主要功能上的不同点,没有包括完全,另外还有很多其他细小的差别。

  • 所有被认为过时的对称加密算法都从支持的列表中删除了,剩下的都是AEAD算法。加密套件的概念也变了,现在用来将认证和秘钥交换算法与记录层保护算法(包括秘钥长度)和一个在秘钥推导和MAC中用到的hash算法分开。
  • 增加了一个0-RTT模式,为某些应用数据在连接建立的时候节省了一个往返的时间,但是牺牲了一定的安全性。
  • 移除了静态RSA和DH算法套件;现在所有以公钥为基础的密钥交换机制都提供前向安全性。
  • ServerHello之后的所有握手消息都被加密起来了。新添加的EncryptedExtensions消息让之前在ServerHello中明文传输的扩展项都得以保护起来。
  • 重新设计了密钥推导函数。新设计的函数由于提高了密钥的独立性,更易于被密码学家分析。函数底层使用了HKDF算法。
  • 重新组织了握手状态机,更具有一致性,然后移除了像ChangeCipherSpec这样多余的消息(有时候为了中间件的兼容性,还需要保留)。
  • 现在椭圆曲线算法占主导地位,并加入了新的签名算法,比如EdDSA。TLS1.3移除了点格式的协商,对每种曲线,只取一种点格式。
  • 其他密码学上的改进,比如调整了RSA padding算法,使用RSASSA-PSS填充。溢出了压缩、DSA算法和自定义的DHE族。
  • 不推荐使用TLS1.2的版本协商机制,而是在扩展项中使用一个版本列表来做版本协商。这对现有的没有正确实现版本协商机制的server增加了兼容性。
  • 带或不带服务端状态的会话恢复机制,还有之前TLS版本中存在的PSK加密套件,都被合并进了一个新的PSK交换机制中。
  • 更新了引用,指向那些更新过版本的RFC(比如指向RFC 5280,而不是RFC 3280)。

1.3. 更新对TLS1.2的影响

本文档定义了几个可能会影响TLS1.2实现的改变,其中几项TLS1.3也不支持:

  • 4.1.3节中定义的一个防止版本降级的机制
  • 4.2.3中定义的RSASSA-PSS签名算法
  • ClientHello扩展中的 “supported_versions” 可以用来协商TLS使用的版本,替换之前ClientHello中的 legacy_version字段
  • “signature_algorithms_cert”扩展项允许client指明自己能哪些签名算法验证X.509证书。

另外,本文档阐述了几条对早期TLS版本的兼容性要求,见#9.3.

2. 协议总览

安全通道用到的加密参数由TLS握手协议产生。该TLS子协议由client和server首次通信的时候使用。握手协议允许两端协商协议版本、选择加密算法、可选进行双方认证和建立共享的密钥材料。一旦握手完成,两端就可以使用建立的key去保护应用层流量了。

握手失败或其他协议错误会导致连接中止,有时还可能先发送一个alert消息。

TLS支持的3个基本的密钥交换模式为:

  • (EC)DHE(基于有限域或椭圆曲线的Diffie-Hellman)
  • PSK-only
  • PSK with (EC)DHE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
      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]

+ : 表示消息中发送的重要的扩展项
* : 表示可选的或依据不同情况发送的、不是每次都必须发送的消息或扩展项
{}: 表示该消息用一个[sender]_handshake_traffic_secret中推导出来的key保护
[]: 表示该消息用一个[sender]_application_traffic_secret_N中推导出来的key保护

图1: 完整握手的消息流

可以认为握手有3个阶段(上图显示的那样):

  • 密钥交换: 建立共享密钥材料和选择加密参数。该阶段之后所有的消息都会被加密。
  • server参数: 建立其他握手参数(client是否要被认证,应用层协议支持等等…)。
  • 认证:认证server(和/或client),提供key的确认和握手完整性校验。

在密钥交换阶段,client发送ClientHello消息,该消息包含一个随机数(ClientHello.random),支持的协议版本,一个对称加密算法/HKDF hash算法对儿的列表;另外还有一个DH共享密钥材料集合(在key_share扩展项中)或者一个预共享密钥标签集合(在pre_shared_key扩展项中),或者两个都有;另外还可能有其他的扩展项。也可能因为为兼容中间件而存在其他字段和/或消息。

server处理ClientHello,决定连接使用的加密参数,然后在ServerHello中回复选择的连接参数。ClientHelloServerHello合起来决定共享密钥。如果用到了(EC)DHE密钥交换算法,ServerHello会在一个key_share扩展项中带上server的临时DH共享密钥;server的共享密钥必须属于client支持的组中的其中一个。如果用到了PSK密钥交换算法,ServerHello会包含一个pre_shared_key扩展项,用于指示选择用哪个client提供的PSK。注意具体实现可能会同时用(EC)DHE和PSK,这两个扩展此时都会提供。

然后server发送两条消息去建立server端参数:

EncryptedExtensions: 响应ClientHello中不是决定加密参数的其他扩展项(针对单独证书的那些要求也不在这里处理)。
CertificateRequest: 如果要用基于证书的认证方法来认证client,对证书要求的一些参数就在该消息中发送。如果不需要认证client,就不发送该消息。

最后,client和server交换认证信息。如果使用基于证书的认证方式,每次TLS都使用同样的一组消息。(基于PSK的认证属于密钥交换的一个附带效果)。特别的:
Certificate: 包含端点证书和每个证书的扩展。如果不使用证书认证,server不会发送该消息,如果server没发送CertificateRequest(表示client不用证书进行认证),client不会发送该消息。
CertificateVerify: 用Certifiacte消息中的公钥对应的私钥对整个握手进行的一个签名。如果端点不是基于证书进行的认证,就不会发送该消息。
Finished: 整个握手的一个MAC(Message Authentication Code)。该消息提供key的确认,将端点身份跟交换的密钥进行绑定,在PSK模式中也用来认证握手。

一旦受到server的消息,client会回应自己这边的认证消息,也就是自己的Certificate, CertificateVerifyFinished

这时握手已经完成了,client和server会推导出记录层需要的密钥材料,来保护应用数据。在发送Finished之前不能发送应用数据(除了#2.3描述的情况)。注意,尽管server可能在收到client的认证消息之前就能发送应用数据,但这时候client还没得到认证,是个未认证的对端。

2.1. 不正确的DHE共享

如果client没有提供合适的”key_share”扩展项信息(比如只包含server不接受或不支持的DHE或ECDHE族),server用HelloRetryRequest来修正这个问题,client需要用合适的”key_share”扩展项来重新握手,如图2。如果没有合适的参数,server必须用相应的告警中止握手。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Client                                               Server

ClientHello
+ key_share -------->
HelloRetryRequest
<-------- + key_share
ClientHello
+ key_share -------->
ServerHello
+ key_share
{EncryptedExtensions}
{CertificateRequest*}
{Certificate*}
{CertificateVerify*}
{Finished}
<-------- [Application Data*]
{Certificate*}
{CertificateVerify*}
{Finished} -------->
[Application Data] <-------> [Application Data]

Figure 2: Message Flow for a Full Handshake with
Mismatched Parameters

注意:握手副本(handshake transcript)包含开始的 ClientHello/HelloRetryRequest 消息,不会用新的 ClientHello 重置。

TLS也允许几种基本握手的优化变形,如下章节所述。

2.2. 会话恢复和预共享密钥(PSK)

尽管TLS的PSK可以从外部设置,也可以在前一个连接中建立,然后用于建立一个新的连接(“会话恢复”或用PSK进行”恢复”)。一旦握手完成,server可以发送给client一个PSK id,该PSK id对应从初始握手中导出的一个唯一密钥(#4.6.1)。client将来就可以用PSK id去协商新的握手了。如果server接受PSK,新连接的加密上下文就跟最初的连接关联起来了,并且从最初握手中推导出秘钥来初始化新的加密上下文,而不是再从完整握手推导。在TLS1.2及以下,该功能由”session IDs”和”session tickets”[RFC5077]提供,这两种机制在TLS1.3中都不再使用了。

PSK可以跟(EC)DHE秘钥交换一起使用,以此来提供前向安全性,也可以单独使用,代价就是应用数据丧失了前项安全性。

图3显示了先进行一次完整握手来建立一个PSK,再在第二次握手中使用该PSK:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
       Client                                               Server

Initial Handshake:
ClientHello
+ key_share -------->
ServerHello
+ key_share
{EncryptedExtensions}
{CertificateRequest*}
{Certificate*}
{CertificateVerify*}
{Finished}
<-------- [Application Data*]
{Certificate*}
{CertificateVerify*}
{Finished} -------->
<-------- [NewSessionTicket]
[Application Data] <-------> [Application Data]


Subsequent Handshake:
ClientHello
+ key_share*
+ pre_shared_key -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
{Finished}
<-------- [Application Data*]
{Finished} -------->
[Application Data] <-------> [Application Data]

Figure 3: Message Flow for Resumption and PSK

因为server通过PSK进行认证,就不需要再发送 Certificate 和 CertificateVerify消息了。当client要通过PSK恢复会话,它应该同时提供一个”key_share”扩展项,让server在需要的时候能够拒绝恢复会话而回退到完整握手。server回复”pre_shared_key”扩展项来协商是否使用PSK,并且可以同时回复”key_share”扩展来进行(EC)DHE秘钥交换,以此来提供前向安全性。

当PSK由外部提供,就需要同时提供PSK id和配套使用的KDF算法。

注意:当使用外部提供的PSK时,特别需要注意在秘钥生成的时候提供足够的熵,RFC4086 中有相关讨论。从一个密码或者其他低熵源推导秘钥是不安全的。密码或低熵源会受到基于PSK binder的字典攻击。即使同时使用DH秘钥交换算法,这样的PSK认证也不是一个基于密码的强认证秘钥交换机制。特别是它没有办法抵御攻击者基于密码/PSK的暴力攻击。

2.3 0-RTT 数据

当client和server使用PSK时(不管从外部获取还是从之前的握手),TLS1.3允许client在第一阶段就发送数据(“早期数据, early data”)。client用PSK去认证server并且加密早期数据。

如图4所示,0-RTT只是加在了1-RTT握手的第一阶段,握手的剩下部分跟用PSK进行恢复的1-RTT握手所用消息相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Client                                               Server

ClientHello
+ early_data
+ key_share*
+ psk_key_exchange_modes
+ pre_shared_key
(Application Data*) -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
+ early_data*
{Finished}
<-------- [Application Data*]
(EndOfEarlyData)
{Finished} -------->
[Application Data] <-------> [Application Data]

+ Indicates noteworthy extensions sent in the
previously noted message.

* Indicates optional or situation-dependent
messages/extensions that are not always sent.

() Indicates messages protected using keys
derived from a client_early_traffic_secret.

{} Indicates messages protected using keys
derived from a [sender]_handshake_traffic_secret.

[] Indicates messages protected using keys
derived from [sender]_application_traffic_secret_N.

Figure 4: Message Flow for a 0-RTT Handshake

重要提示: 0-RTT数据的加密性质弱于其他类型的TLS数据,具体来说:

  1. 早期数据不是前向安全的,因为它只是用提供的PSK推导出的密钥加密的。
  2. 不保证连接之间没有数据包重放。常规的TLS1.3 1-RTT数据的防重放由server的random保证,但0-RTT数据不依赖 ServerHello,就没有那么强的保证。如果早期数据由TLS client认证机制或在应用协议中进行认证,这就尤其重要了。任何对early_exporter_master_secret的使用也都有同样的注意事项。

0-RTT数据不能在一个连接中重复(比如server不会在同一个连接中处理同一个数据两次),攻击者也不可能让0-RTT数据作为1-RTT数据出现(因为早期数据由不同的密钥保护)。附录E5对潜在攻击进行了说明,#8 说了些server可以用来限制重放影响的一些机制。

3. 描述语言

4. 握手协议

握手协议用于协商一条连接的加密参数。握手消息交给TLS记录层,在记录层按照当前有效的连接状态上下文信息封装成一个或多个 TLSPlaintextTSLCiphertext 结构发出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;

协议消息必须按照 #4.4.1 中定义和 #2 中显示的顺序发送。收到不按照顺序的握手消息的话,必须中止握手并发送 “unexpected_message”告警。

新的握手消息类型由IANA分配,如 #11 所述。

4.1. 秘钥交换消息

秘钥交换信息用于确定client和server的加密能力,建立共享秘钥,该秘钥用于保护握手阶段剩下的消息和之后的应用数据。

4.1.1. 密码协商

在TLS中,client在 ClientHello 中提供了一下4组选项之后,密码参数协商就开始了:

  • client提供的指示 AEAD/HKDF hash算法对的一组加密套件
  • 一个”supported_groups”扩展项(#4.2.7),用于表示client支持的(EC)DHE族和一个”key_share”扩展项(#4.2.8),其中包含部分或全部族的(EC)DHE共享密钥
  • 一个”signature_algorithms”扩展项(#4.2.3),用于表示client接受的签名算法。也可能加上一个”signature_algorithms_cert”扩展项(#4.2.3)来表示支持的证书中使用的签名算法
  • 一个”pre_shared_key”扩展项(#4.2.11),包含client知道的一组对称加密密钥和一个”psk_key_exchange_modes”扩展项(#4.2.9)来指示可以跟PSK一起使用的密钥交换模式

如果server没选择PSK,那前3个选项就是完全正交的: server独立挑选一个加密套件,一个(EC)DHE族和相应的共享密钥,还有一个签名算法/证书对来向client认证自己。如果server收到的”supported_groups”和支持的算法族没有重叠的,就必须用”handshake_failure”或”insufficient_security”告警中止握手。

如果server选择了一个PSK,那就必须从client的”psk_key_exchange_modes”扩展项(单独PSK或和(EC)DHE一起两种模式)中选择一种密钥交换模式。注意如果PSK可以脱离(EC)DHE单独使用的话,那与”supported_groups”中参数没有重叠就不会是个严重错误,跟前边讨论的non-PSK情况一致。

如果server选择了一个(EC)DHE族,并且client没在最开始的ClientHello中提供对应的”key_share”扩展项参数,server必须回复个HelloRetryRequest消息(#4.1.4)。

如果server成功选择了相应参数,并且不要求返回HelloRetryRequest,就会在ServerHello中按照如下方式表示选择的参数:

  • 如果选择了PSK,server会发送一个”pre_shared_key”扩展项来表示选择的哪个key
  • 如果选择了(EC)DHE,server必须提供一个”key_share”扩展项。如果没用PSK,那就必须用到(EC)DHE和基于证书的认证方式了。
  • 当通过证书进行认证时,server会发送Certificate(#4.4.2)和CertificateVerify(#4.4.3)消息,在TLS1.3中,必须至少用PSK或证书中的一个进行认证,不能两个都不用。将来的文档可能会定义两个怎么一起用。

如果server没办法协商出一组支持的参数(比如client和server之间的参数没有重叠的),就必须用”handshake_failure”或”insufficient_security”严重告警来中止握手。

4.1.2. Client Hello

当client首次连接server,要求第一个发送的TLS消息是ClientHello。如果server用HelloRetryRequest回复第一个ClientHello,client还需要再发送一次ClientHello,这种情况下,client需要保证除了一下这些,其他字段保持不变:

  • 如果HelloRetryRequest中提供了”key_share”扩展项,需要换掉那组共享密钥,用一个只包含一个共享密钥的链表替换,这个共享密钥是协商出来的族中对应的
  • 如果之前有”early_data”扩展项(#4.2.10),需要移除,HelloRetryRequest中不允许出现早期数据
  • 如果HelloRetryRequest中有”cookie”扩展项,需要包含到第二个ClientHello
  • 如果有”pre_shared_key”扩展项,需要重新计算”obfuscated_ticket_age”和binder的值;并且如果有的话,移除跟server选择的加密套件不兼容的PSK
  • 可选添加、删除或调整”padding”扩展项的长度
  • 将来定义的其他出现在HelloRetryRequest中的扩展项需要的调整

因为TLS1.3禁止重新协商,如果server选择使用TLS1.3,并且在任何其他时候收到了ClientHello,必须用”unexpected_message”告警中止连接。

如果server使用之前的版本建立了TLS连接,然后收到了个TLS1.3的重新协商的ClientHello,必须维持之前的协议,也就是不准重新协商成TLS1.3。

该消息的结构是:

1
2
3
4
5
6
7
8
9
10
11
12
13
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支持的最高的版本。实践表明很多server没有正确实现这种版本协商机制,导致server收到了比它支持的版本更高的ClientHello版本号时,拒绝连接,造成”版本不兼容”。TLS1.3中,client在扩展项”supported_versions”中表明自己期望的版本,”legacy_version”字段必须填成0x0303,也就是TLS1.2的版本号的值。TLS1.3的ClientHello靠”legacy_version”是0x0303和”supported_versions”扩展项中最高支持版本是0x0304来确定。(向后兼容性的详细说明见附录D。)

random: 随机数生成器生成的32字节串。其他信息见附录C。

legacy_session_id: TLS1.3之前版本支持一个”会话恢复”的特性,TLS1.3中该特性并入了PSK机制中。如果client缓存了TLS1.3之前的server设置的session ID,就需要在这个字段设置该值。在兼容模式中(见附录D.4),该值必须非空,所以如果client没有使用TLS1.3之前版本的会话进行恢复,就需要生成一个新的32字节串。该值可以不是随机的,但应该是不可预测的,以避免实现的时候固定到某个特殊值上(也叫”钙化”)。否则就必须设置成0长向量(也就是1个字节的0值)。

cipher_suites: 一个client支持的对称加密套件的列表,按照client期望的顺序排列,用于指定记录层保护算法(包括密钥长度)和用于HKDF的hash算法。值在附录B.4中定义。如果列表中包含server不认识的套件,就继续正常处理下一个。如果client希望使用PSK建立会话,列表中就应该至少包含一个套件中的hash算法是跟PSK相匹配的。

legacy_compression_methods: TLS1.3之前的协议版本支持的压缩算法以列表形式存放在这里。对每个TLS1.3的ClientHello,该向量必须只包含1个字节,值是0,对应之前TLS版本中的”null”压缩方法。如果收到的TLS1.3的ClientHello的本字段有其他值,server必须发送”illegal_parameter”告警中止握手。注意TLS1.3的server可能会收到包含其他压缩算法值的TLS1.2或更早版本的ClientHello,就按照协商出来的之前的版本正常处理。

extensions: client可以在扩展项字段中要求server的扩展功能。扩展项格式的定义在 #4.2。TLS1.3中,为了保持ClientHello跟之前版本的兼容性,有些功能移入了扩展项中,因此有些扩展项是必须的。server必须忽略不认识的扩展项。

所有TLS版本都允许在comperssion_methods字段之后可选的跟上一个扩展项字段。TLS1.3的ClientHello消息总是包含扩展项(最少包含”supported_versions”,否则会被当做TLS1.2的ClientHello消息去处理)。但是TLS1.3的server也可能会收到不带扩展项的早期版本的ClientHello。可以通过检查ClientHello消息在compression_methods字段之后是否还有值来判断是否有扩展项。注意这种检查是否存在可选数据的方法跟通常TLS判断变长字段的方法不一样,主要是为了兼容之前没有定义扩展项的TLS版本。TLS1.3的server需要先检查是否有扩展项,如果存在“supported_version”扩展项,就尝试按照TLS1.3去协商。如果协商出TLS1.3之前的版本,server必须检查ClientHello消息,要么在legacy_compression_methods之后没有数据,要么只有一个有效的扩展项块,而没有其他额外数据;如果检查没通过,必发送”decode_errro”告警中止握手。

如果client用扩展项要求了额外的功能,但server不支持该功能,client可以终止握手。

发送ClientHello消息之后,client会等着ServerHello或者HelloRetryRequest消息。如果用了早期数据,client可能会在等下一个握手消息的时候发送早期应用数据(#2.3)。

4.1.3. Server Hello

如果server能根据ClientHello提供的握手参数中协商出可接受的参数,就会发送该消息以继续握手。

该消息的结构:

1
2
3
4
5
6
7
8
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版本中,该字段用于版本协商,表示选择出的该连接使用的版本号。不幸的是,如果该字段使用了新的值,有些中间件会失效。在TLS1.3中,server在“supporte_version”扩展项中指示自己选择的版本(#4.2.1),legacy_version字段必须设置成 0x0303,也就是TLS1.2的版本号的值。(关于向后兼容性的更详细的信息见附录D。)

random: 32字节随机串,见附录C。如果协商出TLS1.2或者TLS1.1,最后8字节必须按照如下规则设置,其他字节必须保持随机。该值由server生成,并且不能依赖ClientHello.random

legacy_session_id_echo: client legacy_sesion_id 字段的内容。注意即使该值表示一个缓存的TLS1.3之前版本的会话,但server选择不进行恢复,也要原样返回。如果client收到的legacy_session_id_echo的值跟它在ClientHello中发送出去的不一样,必须发送”illegal_parameter”告警来中止握手。

cihper_suite: server从ClientHello.cihper_suites列表中挑选的1个加密套件。如果client收到了不是自己提供的加密套件,必须发送”illegal_parameter”告警来中止握手。

legacy_compression_method: 1个字节,值必须是0。

extensions: 扩展项列表。ServerHello必须只能包含那些为了建立加密上下文和协商协议版本而必须的扩展项。所有的TLS1.3的ServerHello消息必须包含”supported_versions”扩展项。当前ServerHello消息还包含”pre_shared_key”或”key_share”扩展项,或者两个同时包含(当使用带(EC)DHE密钥的PSK建立握手的时候)。其他的扩展项(见#4.2)在单独的EncryptoedExtensions消息中单独发送。

为了向后兼容中间件(见#D.4), HelloRetryRequest消息的结构跟ServerHello一样,但随机数设置的是固定值,就是字符串”HelloRetryRequest”的SHA256:

1
2
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” 的消息,实现者必须先检查随机数的值,如果跟这个值相匹配,就要按照4.1.4节的处理。

TLS1.3在server的随机数值中有个内嵌的防降级攻击机制。TLS1.3的server如果跟TLS1.2或之前的版本协商,必须将ServerHello的”Random”值的最后8字节设置成特定的值。
如果跟TLS1.2协商,TLS1.3的server必须将随机数的最后8字节设置成:

1
44 4F 57 4E 47 52 44 01

如果跟TLS1.1或之前的版本协商,TLS1.3的server必须,TLS1.2的server应该,将随机数的最后8字节设置成:

1
44 4F 57 4E 47 52 44 00

TLS1.3的client收到表示TLS1.2或更早的ServerHello的时候,必须检查随机数最后8字节是否等于这些值。TLS1.2的client应该检查是否等于第二个值。如果匹配,client必须用发送”illegal_parameter”告警中止握手。该机制提供了除Finished交换之外的防降级攻击: 因为TLS1.2或之前的版本中出现的ServerKeyExchange消息中包含两个随机值的签名,只要使用了瞬时加密套件,主动攻击者就没办法更改随机数而不被两端发现。但使用静态RSA的时候,就不提供防降级保护了。

注意:这是跟[RFC5246]不一样,所以实际上很多TLS1.2的client和server不会按照上边描述的那样处理。

一个旧版本的client跟TLS1.2或之前的版本进行重新协商的时候,如果收到了TLS1.3的ServerHello,必须发送”protocol_version”告警中止握手。注意TLS1.3中没有重新协商的操作。

4.1.4. Hello Retry Request

如果server能协商出一套参数,但ClientHello没有提供足够的信息以使握手继续,server就会回复该消息。4.1.3节中说过,HelloRetryRequest消息跟ServerHello消息的结构是一样的,并且”legacy_version, legacy_session_id_echo, cipher_suite, and legacy_compression_method”字段都有相同的意思。但为了方便,本文将”HelloRetryRequest”认为是个单独的消息。

server的扩展项必须包含”supported_versions”。另外,应该包含让client生成正确的 ClientHello 对的最小必须扩展项集合。跟ServerHello一样,除了”cookie”扩展项,HelloRetryRequest不能包含任何第一个ClientHello中没有提供的扩展项。