TLS1.3 8446 RFC翻译

探测矛

Posted by 大狗 on April 2, 2021

TLS1.3 8446 RFC翻译

先说点东西,RFC8446里面过于基础的东西,和术语等东西我是不会翻译的,没什么价值,安全从业者都懂。不翻译的东西我会留着原文,翻译的地方只会为中文。如果想看英文版本直接看这个网址:https://tools.ietf.org/html/rfc8446

0 前言和目录

0.1 摘要

这篇文档定义了1.3版本的传输层安全协议 (以下简称为TLS协议)。TLS协议允许客户端和服务端在互联网上通信,并免受偷听,篡改和伪造消息的风险。

这篇文章更新了RFC5705与RFC6066,废除了RFC5077,RFC5246和RFC6961。这篇文档同样给TLS1.2的实现提了新要求。

0.2 本备忘录的状况

这是一份互联网标准跟踪文件。

本文件是互联网工程任务组 (IETF)的产品。它代表了IETF界(IETF社区)的共识。该文档已收到公开审查意见,并已被互联网工程指导小组(IESG)批准发布。更多互联网标准的相关信息可在RFC 7841的第2节中找到。

关于本文档当前状态的信息,任何勘误以及如何提供反馈意见的信息,可通过以下途径获得:https://www.rfc-editor.org/info/rfc8446。

0.3 目录

1 引论

TLS的主要目标是在两个通信段之间提供一个安全信道,对底层的唯一要求是下层协议提供可靠的、有序的数据流。 具体而言,该安全信道应该提供以下属性:

  • 可认证: 通讯讯道的服务端必须可认证,客户端可选择性的被认证。认证的方式可以使用非对称算法(即RSA/ECDSA/EdDSA/PSK方式)

  • 保密性: 通讯讯道建立后,传输数据只能终端可见。TLS并不隐藏传输数据的长度,尽管中断可以添加padding来混淆数据长度并对抗讯道分析技术
  • 一致性: 讯道建立后,传输的数据,不可以在不被察觉下篡改

即使攻击者可以完整控制通讯网络,如同[RFC3552],以上特性也必须被提供。附录Appendix E提供了更纤细的信息。

TLS主要由两部分组成。

  • 握手协议(第4节),用于验证通讯对端的身份。协商密码模式和参数,建立共享的秘钥信息。 握手协议的设计应当能够抵御篡改;主动攻击者不能强迫对端协商其未选择的握手参数。
  • 记录协议(第5节),使用握手协议中协商的参数,保护通信方之间的流量。记录协议将流量拆分为一系列记录,每个记录都使用traffic key独立地保护。

TLS独立于应用协议,TLS协议透明地为高层协议提供服务 但是,TLS标准并没有规定协议如何用TLS增加安全性;如何启动TLS握手,如何实现证书交换流程,都是由运行在TLS之上的协议的设计者和实现者来判断。

本文档定义了TLS 1.3版本标准行为。 虽然TLS 1.3与以前的版本并不直接兼容,但所有版本的TLS都包含了版本(回退)机制,允许客户和服务器在双方共享某一支持版本的情况下下协商一个可用的TLS版本。

本文档取代并废除了之前的TLS版本,包括1.2版本[RFC5246]。 它还删除了[RFC5077]中定义的TLS票据机制,并用第2.2节中定义的机制代替。 由于 TLS 1.3 改变了密钥的推导方式,它更新了第 7.5 节中描述的 [RFC5705],同时也改变了在线证书状态协议 [RFC5077] 中定义的机制。 它还改变了在线证书状态协议(OCSP)消息的传输方式,因此更新了[RFC6066],取消了[RFC6961],详见第4.4.2.1节。

1.1 公约和术语

1.2 与TLS1.2的差别

下面是TLS1.2和TLS1.3主要的功能差异,尽管并没有巨大的变化,但是细节的变动很多。

  • TLS1.3支持的对称加密算法列表中,移除了过去版本合法的加密算法。 剩下的都是带有关联数据的认证加密(AEAD)算法。 密码套件(ciphersuite)的概念已经改变,拆分认证,密钥交换机制与记录保护算法(包括秘钥长度)和哈希算法。哈希算法用于密钥派生功能和握手信息认证码(MAC)一起使用。
  • 增加了零往返时间(0-RTT)模式,在握手阶段就发送应用数据,节省了一个往返时间,但代价是牺牲了一定的安全性能。
  • 静态RSA和静态Diffie-Hellman密码套件已被删除;所有基于公钥的密钥交换机制现在都提供前向保密功能。(实际上就是使用随机产生的临时私钥)
  • TLS1.3中ServerHello之后的所有握手消息都是加密的。新引入的EncryptedExtensions消息允许曾经在ServerHello中以明文方式发送的各种扩展也被秘钥保护。
  • 秘钥衍生算法重新设计。因为降低了新秘钥和旧秘钥内容和统计上面的关联, 新的设计使密码学家更容易分析密码强度。 基于HMAC的提取和扩展密钥衍生函数(HKDF)是该功能的基本单元(就是得用HKDF来做秘钥衍生)。
  • 握手状态机进行了重大调整,使其设计原则更加一致,并删除了ChangeCipherSpec等多余的消息(除非网络上中间设备由于兼容性需要)。
  • 椭圆曲线算法现在已经被列入基本规范,并且包含了新的签名算法,如EdDSA。 TLS 1.3 取消了基点协商功能,每条曲线采用固定的基点。
  • 其他密码学方面的改进,包括将RSA填充改为使用RSA概率签名方案(RSASSA-PSS),以及去除压缩、数字签名算法(DSA)和自定义的Ephemeral Diffie-Hellman(DHE)组。
  • TLS 1.2版本协商机制已经被废止,改为在扩展标记可使用版本得列表。 这增加了与现有服务器的兼容性,因为这些服务器的版本协商实现是不正确的。
  • 基于服务器端状态的会话恢复,以及早期TLS版本中基于PSK的密码套件都由单独的PSK所取代。
  • 参考文献跟随RFC同步更新,请酌情参考RFCs的更新版本(例如,RFC 5280而不是RFC 3280)。

1.3 影响TLS 1.2的更新

本文档定义了一些行为标准,这些标准影响TLS 1.2实现的变化,即使那些不支持TLS 1.3的机器也需要遵守这些行为。

  • 版本降级保护机制在4.1.3节中描述。
  • RSASSA-PSS签名方案在4.2.3节中定义。
  • ClientHello中的”supported_versions “扩展用来协商使用的TLS的版本,在TLS1.2和之前的版本使用ClientHello的legacy_version字段。
  • “signature_algorithms_cert “扩展允许客户端指明它可以使用哪些签名算法验证服务端的X.509证书。

此外,本文档还澄清了早期版本的TLS的一些合规性要求;见第9.3节。

2 协议总览

安全通道使用的加密参数是由TLS握手协议产生的,该TLS子协议在客户端和服务器第一次相互通信时使用的。 握手协议允许对通信端协商协议版本,选择加密算法,认证对方(可选),并建立共享的秘密密钥材料。 一旦握手完成,对等体使用已协商的密钥来保护应用层的流量。

握手失败或其他协议错误会触发连接的终止,可以在连接终止前发出警报消息(第6节)。

TLS支持三种基本的密钥交换模式:

  • (EC)DHE(有限域或椭圆曲线上的Diffie-Hellman算法)
  • PSK-only(仅PSK)
  • PSK with (EC)DHE(PSK+(EC)DHE)

图1展示了一个基础的完整TLS握手流程:

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衍生的秘钥保护
              

              [] 代表该消息(报文)由[sender]_application_traffic_secret_N衍生的秘钥保护

               Figure 1: Message Flow for Full TLS Handshake

握手可以认为有三个阶段(如上图所示)。

  • 密钥交换阶段。建立共享的密钥材料,选择加密参数。 此阶段之后的一切消息都会被加密。
  • 服务器参数。建立其他握手参数(根据客户端是否需要认证、应用层协议支持等情况变化)。
  • 认证。验证服务器(以及客户端),并确认秘钥正确,并保证握手完整性。

在密钥交换阶段,客户端发送ClientHello(4.1.2节)消息,其包含:一个随机数nonce(ClientHello.random);支持的协议版本;对称密码/HKDF散列对的列表;一组Diffie-Hellman key_shares(在 “key_share”(4. 2.8节)扩展中),一组psk标签(在 “pre_shared_key”(4.2.11节)扩展中),或者两者兼而有之;以及一些可能出现的的附加扩展,处于中间设备的兼容性考虑,还可能存在额外的字段和/或信息。

然后,服务器会发送两条消息来建立服务器参数:

  • EncryptedExtensions:该消息回复上面的ClientHello中和确定加密参数无关扩展的响应,但个别证书所特有的参数除外。[第4.3.1节]
  • CertificateRequest:如果需要使用证书认证客户端,则需要该证书的相关信息。 如果不需要客户端认证,则省略该消息。(第4.3.2节)

最后,客户端和服务器交换认证消息。 TLS每次需要基于证书的认证时,都会使用同一组消息。 (基于PSK的认证是作为密钥交换的补充发生的),这些消息,具体来说是下面几种:

  • Certificate。 通讯终端的证书和证书相关扩展。 如果无需使用证书认证,那么,服务器会省略这条消息。如果服务端没法送CertificateRequest消息,客户端同样省略此消息。 请注意,如果原始公钥[RFC7250]或缓存信息扩展部分[RFC7924]也在生效,那么这个消息将不包含一个证书,而是其他一些对应于服务器的长期密钥。 [第4.4.2节]
  • CertificateVerify。 使用与证书信息中的公钥相对应的私钥对整个握手报文计算一个签名。 如果通信终端不使用证书进行认证,则省略该消息。 第 4.4.3 节)。
  • Finished。 对整个握手过程中计算MAC(消息认证码)。 该消息提供密钥确认,将终端的身份与交换的密钥绑定,在PSK模式下还对握手进行认证等功能。 [第4.4.4节]

在收到服务器的消息后,客户端会响应其认证消息,即证书和CertificateVerify(如果需要的话),以及Finished。

此时,握手完成,客户端和服务器衍生出记录层所需的密钥材料,使用该衍生秘钥材料来加解密应用层数据。除第2.3节规定的情况外,在发送Finished消息之前,不得发送应用数据。 请注意,虽然服务器可以在收到客户端的认证消息之前发送应用数据,但在这一点上发送的任何数据当然都是发送给未经认证的对等体。

2.1 不正确的DHE交换流程

如果客户端没有 在”key_share “扩展中没有提供足够的秘钥信息(例如,它只包括服务器不能接受或不支持的DHE或ECDHE组),服务器用HelloRetryRequest纠正该秘钥的不匹配,客户端需要用适当的 “key_share “扩展重新开始握手,如图2所示。 如果不能协商出共同的加密参数,服务器必须发送正确的alert报文中止握手。

		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

注意:握手抄本(transcript)包含了最初的ClientHello/HelloRetryRequest交换;它不会随着新的ClientHello而重置。

TLS也允许多种基本握手的优化方案,如下描述:

2.2 复用和Pre-Shared Key(PSK)

尽管TLS PSK可以通过带外数据建立,但PSK也可以在先前的连接中建立,然后用来建立新的连接(”会话恢复 “或 “使用PSK恢复”)。 一旦握手完成,服务器可以向客户端发送一个PSK身份,该身份对应于从初始握手流程中导出的唯一密钥(见4.6.1节)。 然后,客户端可以在未来的握手中使用该PSK身份来协商相关PSK的使用。 如果服务器接受了PSK,那么新连接的安全上下文就会与原始连接进行加密绑定,并且从初始握手中得到的密钥会被用来加速加密的计算过程,从而免于完全握手。 在TLS 1.2及早期版本中,这个功能由 “会话ID “和 “会话票据”[RFC5077]提供。 在TLS 1.3中,这两种机制都被淘汰了。

PSK可以与(EC)DHE密钥交换一起使用,以便与shared keys拓展提供的共享秘钥相结合提供前向保密,也可以单独使用,但代价是失去应用数据的前向保密性。

图三展示了PSK的颁发和PSK的使用。

	 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

由于服务器是通过PSK进行认证的,所以它不会发送证书或证书验证消息。 当客户机通过PSK尝试复用连接时,它还应该向服务器提供一个 “key_share “扩展,以允许服务器拒绝复用,从而能够在需要时回退到完整握手。 服务器用回复”pre_shared_key “扩展来协商使用PSK机制,并可以(如这上所示)用 “key_share “扩展来响应,从而结合(EC)DHE密钥协商,提供前向保密。

当使用带外数据分发PSK时,PSK的身份和秘钥衍生算法必须兼容。

注意:当使用带外提供的预共享秘密时,一个重要的考虑因素是在密钥生成过程中使用足够的熵,如[RFC4086]中所讨论的那样。 从密码或其他低熵来源导出共享秘密是不安全的。 低熵秘密或密码会受到基于PSK binder的字典攻击。 指定的PSK认证即使与Diffie-Hellman密钥建立一起使用,也不是一种基于密码的强认证密钥交换。 具体来说,它不能阻止能够观察到握手的攻击者对密码/预共享密钥进行蛮力攻击。

2.3 0-RTT数据

当客户端和服务器通过共享一个PSK(从外部获得或通过之前的握手)建立连接时,TLS 1.3允许客户端在第一次飞行时发送数据(”early data”)。 客户端使用PSK对服务器进行认证,并对early data进行加密。

如图4所示,0-RTT数据直接附加到第一趟发送的1-RTT握手中。 握手的其余部分与PSK复用的1-RTT握手相同的消息。

		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数据要弱。 具体来说,0-RTT数据的安全属性要弱于其他类型的TLS数据:

  • 这些数据不提供前向安全的保障,因为它只使用所提供的PSK衍生的秘钥进行加密。
  • 0-RTT并不抗虫方。对于普通的TLS 1.3 1-RTT数据的重放保护是通过服务器发送的Random值提供的,但是0-RTT数据不依赖于ServerHello,因此无法抗重放。如果数据是和TLS客户端认证或在应用协议内部认证相关,这一点就变得至关重要。 同样的警告适用于任何使用early_exporter_master_secret的情况。

0-RTT数据不能在一个连接内重放(即服务器不会对同一个连接处理两次相同的数据),攻击者也无法使0-RTT数据看起来是1-RTT数据(因为它受到不同密钥的保护)。 附录E.5包含了对潜在攻击的描述,第8节描述了服务器可以用来限制重放影响的机制。

3

这部分我推荐不要看了,我没好好翻译,随便找了一个工具机器翻译的,没做校对,我把第四章翻译完了再回来改

该部分重点介绍如何组织(格式化)数据,组织数据的过程可以视为一些基本的关键名词,讲解协议的时候会使用这些基本的关键名词。(可以视为函数)

3.1 基本数据块大小

所有数据项的表示形式都是明确指定的。基本的数据块大小为一个字节(即8位)。多字节数据项是从左到右、从上到下的字节串联。从字节流中,多字节项(以下示例中的数字)通过以下方式形成(使用C语言表示法):

value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) | ... | byte[n-1];

这种多字节值的字节顺序是通常的网络字节顺序或大端序列。

3.2 杂项

这里给出一些相关的解释,

  • 注释以“/*”开始并以“*/”结束,这个和C语言是一样的。
  • 可选组件用“[[ ]]”(双括号)括起来表示。
  • 单字节实体的类型为opaque。

3.3. 数字

最基本的数字数据类型是无符号字节(uint8)。

所有更大的数字数据类型都是无符号类型,且都由uint8拼接构成的,如3.1节所述。预定义以下数字类型:

  uint8 uint16[2];
  uint8 uint24[3];
  uint8 uint32[4];
  uint8 uint64[8];

RFC里面所有的数字都以网络字节(大端)顺序传输;例如由十六进制字节01 02 03 04表示的uint32等于十进制值16909060。

3.4. 向量

向量(或者说单维数组)是一串同类数据元素。向量的大小可以在文档编写时指定,或在运行时才确定。请注意,无论哪种情况,生命的长度都是向量的长度,而不是向量里面存储的元素数。指定一个新的类型T’作为固定长度的类型T的向量的语法是:

  T T'[n];

这里,T’在数据流中占用n个字节,其中n是T的大小的倍数。向量的长度不包括在编码流中。

在以下示例中,Datum被定义为协议不解释的三个连续字节,而Data是三个连续的Datum,总共消耗九个字节。

  opaque Datum[3];      /* 三个未解释的字节 */
  Datum Data[9];        /* 三个连续的3字节向量 */

变长向量通过指定一个合法长度的子范围(包括两端),使用符号来定义。在编码时,实际长度位于向量内容之前。长度将采用一个数字形式,消耗所需的字节数,以容纳向量的指定最大(上限)长度。实际长度字段为零的变长向量称为空向量。

  T T'<floor..ceiling>;

在以下示例中,“mandatory”是一个必须包含300到400字节的类型为opaque的向量。它永远不能为空。实际长度字段占用两个字节,即uint16,这足以表示400值(见3.3节)。同样,“longer”最多可以表示800字节的数据,或400个uint16元素,它可以为空。其编码将包括一个前置于向量的两个字节实际长度字段。编码的向量长度必须是单个元素长度的整数倍(例如,17字节的uint16向量是非法的)。

  opaque mandatory<300..400>;
        /* 长度字段是两个字节,不能为空 */
  uint16 longer<0..800>;
        /* 零到400个16位无符号整数 */

3.5. 枚举

一种额外的稀疏数据类型称为“枚举”或“enumerated”。每个定义都是一个不同的类型。只有相同类型的枚举才能分配或比较。枚举的每个元素必须赋予一个值,如以下示例所示。由于枚举的元素没有顺序,可以按任何顺序分配任何唯一值。

  enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;

协议的未来扩展或补充可以定义新的值。实现需要能够解析和忽略未知的值,除非字段的定义另有说明。

一个枚举在字节流中占据的空间与其最大定义的序数值相同。以下定义将导致使用一个字节来承载类型Color的字段。

  enum { red(3), blue(5), white(7) } Color;

可以选择指定一个没有关联标签的值,以在不定义多余元素的情况下强制宽度定义。

在以下示例中,Taste将在数据流中占用两个字节,但在当前版本的协议中只能取值1、2或4。

  enum { sweet(1), sour(2), bitter(4), (32000) } Taste;

枚举元素的名称在定义的类型内限定。在第一个示例中,对枚举第二个元素的完全限定引用将是Color.blue。如果赋值目标明确,则不需要这种限定。

  Color color = Color.blue;     /* 过度指定,合法 */
  Color color = blue;           /* 正确,类型隐式 */

分配给枚举的名称不需要是唯一的。数值可以描述同一名称适用的范围。值包括该范围的最小和最大包含值,由两个句点字符分隔。这主要用于保留空间区域。

  enum { sad(0), meh(1..254), happy(255) } Mood;

3.6. 结构类型

结构类型可以从基本类型构造,以方便使用。每个规范声明一种新的、唯一的类型。定义所用的语法类似于C语言。

  struct {
      T1 f1;
      T2 f2;
      ...
      Tn fn;
  } T;

使用标准向量语法允许固定和变长向量字段。变体示例中(3.8节)的结构V1和V2示范了这一点。

结构中的字段可以使用类型名称限定,其语法类似于枚举。例如,T.f2指的是前述声明中的第二个字段。

3.7. 常量

字段和变量可以用“=”分配一个固定值,如:

  struct {
      T1 f1 = 8;  /* T.f1必须始终为8 */
      T2 f2;
  } T;

3.8. 变体

定义的结构可以基于环境中可用的某些知识具有变体。选择器必须是一个枚举类型,定义结构所定义的可能变体。选择的每个分支(如下)指定该变体字段的类型和一个可选的字段标签。变体在运行时的选择机制不由表示语言规定。

  struct {
      T1 f1;
      T2 f2;
      ....
      Tn fn;
      select (E) {
          case e1: Te1 [[fe1]];
          case e2: Te2 [[fe2]];
          ....
          case en: Ten [[fen]];
      };
  } Tv;

例如:

  enum { apple(0), orange(1) } VariantTag;

  struct {
      uint16 number;
      opaque string<0..10>; /* 变长 */
  } V1;

  struct {
      uint32 number;
      opaque string[10];    /* 固定长度 */
  } V2;

  struct {
      VariantTag type;
      select (VariantRecord.type) {
          case apple:  V1;
          case orange: V2;
      };
  } VariantRecord;

4 握手协议

握手协议用于协商连接的安全参数。握手消息基于TLS Recordlayer,Record Layer将握手协议封装为一个或多个TLS明文或TLS秘文报文(结构)。这些报文(结构)如何处理,传输需要根据当前握手协议的状态确定

 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 密钥交换消息

密钥交换消息用于确定客户端和服务端的安全能力,从而建立共享的密钥信息,这里包括用于保护握手过程和数据通信的通信密钥。(除了这个密钥还有复用密钥啥的)

4.1.1. 密码协商

在TLS中,密码协商的过程需要客户端在其ClientHello消息中提供以下四组信息(选项):

  • 一系列密码套件组成的列表,指示客户端支持的AEAD算法/HKDF密钥延生算法。
  • 一个“supported_groups”(第4.2.7节)扩展,指示客户端支持的(EC)DHE组,以及一个“key_share”(第4.2.8节)扩展,包含这些组中一些或全部的计算得出的(EC)DHE共享密钥信息(对ECDHE可以理解为公钥乘以极点,就是大质数乘以基点)。
  • 一个“signature_algorithms”(第4.2.3节)扩展,指示客户端可以接受的签名算法(用于certificate verify消息)。还可以添加一个“signature_algorithms_cert”扩展(第4.2.3节),指示特定支持的证书的签名算法。
  • 一个“pre_shared_key”(第4.2.11节)扩展,包含客户端已知的对称密钥标识列表,以及一个“psk_key_exchange_modes”(第4.2.9节)扩展,指示可与PSK一起使用的密钥交换模式。

如果服务器不选择PSK,那么这前三个选项是完全独立的:服务器独立地选择一个密码套件、一个(EC)DHE组和用于密钥协商的共享密钥(key share),以及一个签名算法/证书对用于向客户端验证其身份。如果接收到的“supported_groups”和服务器支持的组之间没有重叠,服务器必须用“handshake_failure”或“insufficient_security”警报中止握手。

如果服务器选择了PSK,则它还必须从客户端的“psk_key_exchange_modes”扩展中指示的集合中选择一个密钥建立模式(目前仅支持只使用PSK或将PSK与(EC)DHE一起使用)。请注意,如果PSK可以在没有(EC)DHE的情况下使用,那么服务端“supported_groups”参数中和客户端不重叠并不致命,这与前一段讨论的非PSK情况即(EC)DHE握手的情况不同。

如果服务器选择了一个(EC)DHE组,而客户端在初始的ClientHello中没有提供兼容的“key_share”扩展,服务器必须响应一个HelloRetryRequest(第4.1.4节)消息。如果服务器成功选择参数并且不需要HelloRetryRequest,它将在ServerHello中指示所选参数如下:

  • 如果使用了PSK,服务器将发送一个“pre_shared_key”扩展,指示所选的密钥。
  • 当使用(EC)DHE时,服务器还将提供一个“key_share”扩展。如果未使用PSK,则(EC)DHE和基于证书的身份验证就会被采用。
  • 在通过证书进行身份验证时,服务器将发送Certificate(第4.4.2节)和CertificateVerify(第4.4.3节)消息。在本文档定义的TLS 1.3中,要么使用PSK或证书来进行认证,但两者不同时使用。将来的文档可能会定义如何同时使用它们。

如果服务器无法协商一组支持的参数(比方说,客户端和服务器参数之间没有重叠),它必须用“handshake_failure”或“insufficient_security”的致命警报中止握手(见第6节)。

4.1.2 Client Hello

当客户端首次连接到服务器时,必须将 ClientHello 作为其第一条 TLS 消息发送。当服务器对其 ClientHello 响应为 HelloRetryRequest 时,客户端也会发送 ClientHello。在这种情况下,客户端必须发送相同的未修改的 ClientHello,除非出现如下情况:

  • 如果在 HelloRetryRequest 中提供了“key_share”扩展,则应使用指定组的单个 KeyShareEntry 来替换原本的共享列表。
  • 如果存在“early_data”扩展(第 4.2.10 节),则将其删除。在收到HelloRetryRequest 之后不允许发送early data。
  • 倘若HelloRetryRequest里存在cookie拓展
  • 如果存在“pre_shared_key”扩展,则需要重新计算“obfuscated_ticket_age”和binder的值来更新它,并(可选)删除与服务器指示的密码套件不兼容的任何 PSK。
  • (可选)添加、删除或更改“padding”扩展的长度[RFC7685]。
  • 在 HelloRetryRequest 中定义且存在的扩展可能允许的其他修改。

因为 TLS 1.3 禁止重新协商,如果服务器已协商 TLS 1.3 并在任何其他时间收到 ClientHello,必须使用“意外消息”警报终止连接。

如果服务器使用之前版本的 TLS 建立了 TLS 连接,并在重新协商中收到 TLS 1.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 版本中,此字段用于版本协商,并表示客户端支持的最高版本号。经验表明,许多服务器并不能正确实现版本协商,导致“版本零容忍”BUG,即服务器拒绝版本号比它高的 ClientHello。在 TLS 1.3 中,客户端在“supported_versions”扩展(第 4.2.1 节)中表明其版本偏好,并且遗留版本字段必须设置为 0x0303,这是 TLS 1.2 的版本号。TLS 1.3 的 ClientHellos 必须具有 0x0303 的遗留版本字段,且携带 supported_versions 扩展,拓展里指示的最高版本为 0x0304。(有关向后兼容性的详细信息,请参阅附录 D)

随机数:由安全随机数生成器生成的 32 字节。 有关其他信息,请参阅附录 C

遗留会话 ID:TLS 1.3 之前的版本支持“会话恢复”功能,此功能在该版本中已与预共享密钥机制(PSK)合并(见第 2.2 节)。由 TLS 1.3 之前的服务器设置了缓存会话 ID 的客户端应将此字段设置为该值。在兼容模式下(见附录 D.4),此字段不能为空,因此不提供 TLS 1.3 之前会话的客户端必须生成一个新的 32 字节值。此值不必是随机的,但应是不可预测的,以避免实现固定在特定值上(也称为僵化)。否则,它必须设置为零长度向量(即,零值的单字节长度字段)。 密码套件:客户端支持的对称密码选项列表,特别是record layer保护算法(包括密钥长度)和用于 HKDF 的哈希算法,按照客户端偏好降序排列。值在附录 B.4 中定义。如果列表包含服务器不识别、不支持或不想使用的密码套件,服务器必须忽略这些密码套件,并像往常一样处理其余的套件。如果客户端正在尝试 PSK 密钥建立,它应至少提供一个包含可以与 PSK 可以关联使用哈希算法的密码套件。

遗留压缩方法:TLS 1.3 之前的版本支持压缩,支持的压缩方法列表在此字段中发送。对于每个 TLS 1.3 的 ClientHello,此向量必须恰好包含一个设置为零的字节,对应于之前版本 TLS 中的“空”压缩方法,或者说不压缩。如果收到的 TLS 1.3 的 ClientHello 在此字段中有任何其他值,服务器必须使用“非法参数”警报中止握手。请注意,TLS 1.3 服务器可能会收到包含其他压缩方法的 TLS 1.2 或之前的 ClientHellos,并且(如果协商这样的先前版本)必须遵循相应的先前版本 TLS 的程序。

扩展:客户端通过在扩展字段中发送数据向服务器请求扩展功能。实际的“扩展”格式在第 4.2 节中定义。在 TLS 1.3 中,某些扩展的使用是强制性的,一些功能通过移至扩展来使得ClientHello可以与历史版本 TLS 兼容性(从而老版本的TLS服务器也可以正确协商出老版本TLS)。服务器必须忽略无法识别的扩展。

TLS 的所有版本都允许扩展字段可选地跟随压缩方法字段。TLS 1.3 的 ClientHello 消息始终包含扩展(起码包括“支持的版本supported_versions”,否则,它们将被解释为 TLS 1.2 的 ClientHello 消息)。然而,TLS 1.3 服务器可能会收到来自之前版本 TLS 的没有扩展字段的 ClientHello 消息。扩展的存在可以通过确定在 ClientHello 末尾的压缩方法字段之后检查是否还有字节来检测。请注意,这种检测可选数据的方法不同于具有可变长度字段的正常 TLS 方法,但它用于在定义扩展之前与 TLS 兼容。TLS 1.3 服务器首先需要执行此检查,并且只有在“supported_versions”扩展存在时才尝试协商 TLS 1.3。如果协商的是 TLS 1.3 之前的版本,服务器必须检查消息在legacy_compression_methods字段之后要么不包含数据,要么只包含一个有效的扩展块且之后没有数据。如果不是,则必须使用“解码错误”警报中止握手。

如果客户端使用扩展请求额外的功能,而服务器未提供此功能,客户端可以中止握手。

发送 ClientHello 消息后,客户端等待 ServerHello 或 HelloRetryRequest 消息。如果客户端选用early data功能,客户端在等待下一个握手消息时可以传输early data(第 2.3 节)。

4.1.3 Server Hello

如果可以和客户端的ClientHello协商出一组可接受的握手参数,服务器将发送ServerHello消息以响应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;

遗留版本字段:在早期的 TLS 版本中,此字段用于版本协商:表示服务端接受客户端ClientHello之后所选的版本号。不幸的是,在TLS1.3之后,某些网络中间设备(路由器啦,交换机啦)并不支持新的Value值。在 TLS 1.3 中,TLS 服务器使用“supported_versions”扩展(第 4.2.1 节)指示其版本,遗留版本字段不在真正生效,但是必须设置为 0x0303,这是 TLS 1.2 的版本号。(有关向后兼容性的详细信息,请参阅附录 D

随机数字段:由安全随机数生成器生成的 32 字节随机数。 请参阅附录 C获取更多信息。 如果协商 TLS 1.2 或 TLS 1.1,最后 8 字节必须按下面所述的内容进行覆盖,但其余字节必须是随机的。 这部分数据由服务器生成,并且必须独立于 ClientHello.random 生成。

遗留session_id回显字段:兼容性角度出发,用于兼容客户端遗留session_id 字段。请注意,即使服务器选择不恢复的 TLS 1.3 之前的会话,客户端的值也应该被回显。如果客户端收到与在 ClientHello 中发送的不匹配的session_id,客户端必须使用“非法参数”警报中止握手。

密码套件:服务器从 ClientHello.cipher_suites 列表中选择的单个密码套件。收到未提供的密码套件的客户端必须使用“非法参数”警报中止握手。

遗留压缩方法字段:单个字节,内容为0x00

扩展:扩展列表。ServerHello必须仅包含建立加密上下文和协商协议版本所需的扩展。所有 TLS 1.3 服务器问候消息必须包含“supported_versions”(支持的版本)扩展。当前ServerHello消息可以另外包含“pre_shared_key”(预共享密钥)扩展或“key_share”(密钥共享)扩展,或者两者都有(当使用带有 (EC)DHE 密钥建立的 PSK 时)。其他扩展(见第 4.2 节)在EncryptedExtensions消息中单独发送。

出于与网络中间设备的后向兼容性原因(见附录 D.4),HelloRetryRequest 的结构和 ServerHello 一致,唯一不同点是必须将 Random 设置为“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 值,如果它与此值匹配,则按照第 4.1.4 节中的描述进行处理。

TLS 1.3 在服务器的随机值中嵌入了降级保护机制。TLS1.3服务器,如果和客户端协商 TLS 1.2 或更低版本的 TLS 1.3 服务器必须在其 ServerHello 中特别设置其 Random 值的最后 8 个字节为特定的内容:

如果协商 TLS 1.2,TLS 1.3 服务器必须将其 Random 值的最后 8 个字节设置为:

44 4F 57 4E 47 52 44 01

如果协商 TLS 1.1 或更低版本,TLS 1.3 服务器必须将其 ServerHello.Random 值的最后 8 个字节设置为:

44 4F 57 4E 47 52 44 00

TLS 1.3 客户端和TLS1.3服务器协商时,必须检查最后 8 个字节不等于这两个值中的任何一个。如果 ServerHello 指示 TLS 1.1 或更低版本,TLS 1.2 客户端也应该检查最后 8 个字节不等于第二个值。如果发现匹配,客户端必须使用“illegal_parameter”警报中止握手。

此机制提供了针对降级攻击的些许保护能力:因为在 TLS 1.2 及更低版本中存在的 ServerKeyExchange 消息包含对两个随机值的签名,只要使用临时密钥协商的技术(就是(EC)DHE每次交换不用固定密钥,用随机生成的),主动攻击者就不可能在未被检测到的情况下修改随机值。但在使用静态 RSA 时,它不提供降级保护。

注意:这与[RFC5246]并不完全一致,因此实际上许多 TLS 1.2 客户端和服务器不会按照上述规定行事。

使用 TLS 1.2 或更早版本进行重新协商的TLS 客户端,如果在重新协商期间收到 TLS 1.3 ServerHello,则必须使用“protocol_version”警报中止握手。这里之所以没提TLS1.3,是因为协商了 TLS 1.3 时,重新协商是不可能的。

4.1.4. Hello Retry Request

如果服务器在客户端的ClientHello能够找到双方共享的通信算法和密钥之类的数据,但 ClientHello 不包含足够的信息来继续握手,则服务器将响应 Hello Retry Request 消息发送此消息。如第 4.1.3 节所述,HelloRetryRequest 具有与 ServerHello 消息相同的格式,并且 legacy_version、legacy_session_id_echo、cipher_suite 和 legacy_compression_method 字段具有相同的含义。但是,为了方便起见,在本RFC中我们将“HelloRetryRequest”视为一个不同的消息进行讨论。

… (未完成,感觉不是特别重要,先省略)

4.2 拓展

(这部分是个大头)一系列的TLS消息包含拓展,拓展的结构和类型如下。这里注意:

  • “extension_type”(扩展类型)标识特定的扩展类型。
  • “extension_data”(扩展数据)包含特定于该特定扩展类型的信息。
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;

尽管有些扩展只是指示,并不需要相应的响应,扩展扔通常以请求/响应的方式构建。客户端在 ClientHello 消息中发送其扩展请求,服务器在ServerHello、EncryptedExtensions、HelloRetryRequest 和 Certificate消息中发送这些扩展的响应。服务器在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”(第 4.2.11 节)除外,它必须是 ClientHello 中的最后一个扩展(但可以出现在 ServerHello 扩展块中的任何位置)。在给定的扩展块中,同一类型的扩展绝不能出现多次

和TLS 1.2 不同,在 TLS 1.3 中,即使在复用PSK 模式下,每次握手都会重新协商扩展。当然,0-RTT 参数是在上一次握手中协商的;如果发现本次握手的信息和上次0-RTT不匹配,服务端可能会拒绝 0-RTT(见第 4.2.10 节)。

在TLS1.3中,新特性和已有特性之间可能会发生微妙(当然影响可能是巨大的,也不是多么微妙)的相互作用,这可能会导致整体安全性显著降低。所以在设计新扩展时,应考虑以下设计原则:

  • 有时,服务器不同意某个扩展的某些情况(比方说导致握手无法继续)应当视为出现错误,另外一些时候,服务端应该只是简单地拒绝支持特定功能。一般来说,对于前者应使用错误警报终止连接,对于后者应使用服务器扩展响应中的字段。
  • 扩展应尽可能设计为不能通过操纵握手消息来强制使用(或不使用)特定功能的任何攻击。无论该功能是否被认为会导致安全问题,新拓展的设计者都应遵循这一原则。通常,只要计算Finished 报文哈希的时候会将扩展字段包含在输入里就够了,但当扩展改变握手阶段发送的消息的含义时,需要格外小心。设计者和实现者应意识到,在握手经过严格的认证之前(这里可不只是身份认证),主动攻击者可以修改消息、插入、删除或替换扩展。

(我理解最后这两个是针对协议实现者和设计者,避免出现中间件兼容性问题考虑的)

4.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 版本,服务器发送“supported_versions”扩展用于指示本次会话要使用的TLS版本。该扩展内容为按优先顺序排列的支持版本列表,最优先的版本排在首位。任何实现本RFC的TLS协议栈必须在“ClientHello”中发送包含它准备协商的所有 TLS 版本信息(这意味着至少提供版本号为0x0304,但如果客户端支持协商之前的 TLS 版本,它们也必须一并发送在该拓展的内容里)。

如果该拓展没发送,即使客户端的ClientHello在遗留历史字段里面指明它想协商TLS1.3(0x0304),如果服务器支持TLS1.3标准同时支持TLS1.2协议,那么该服务器必须参考RFC5246协商版本为TLS1.2或者之前的版本。

如果 ClientHello 中携带supported_versions扩展,则服务器不得使用ClientHello.legacy_version(历史遗留版本字段)值用于版本协商,而必须仅参考“supported_versions”扩展来确定客户端对版本的偏好设置。服务器必须仅选择supported_versions”里包含的 TLS 版本,并且忽略任何服务器不认识的版本。请注意,此机制使得如果双方支持多版本的TLS的话,则有可能会协商 TLS 1.2或之前的版本,服务器必须准备好接收客户端的ClientHello包含supported_versions,但版本列表中不包括 0x0304。

如果服务器选择 TLS 1.3 之前的 TLS 版本握手,那么服务器必须设置其ServerHello.version,并且绝不能发送“supported_versions”扩展。服务器如果协商 TLS 1.3则必须通过发送包含所选版本值(0x0304)的“supported_versions”扩展来响应CLientHello,且必须将 ServerHello.legacy_version 字段设置为 0x0303(TLS 1.2)。客户端在处理 ServerHello 的其余部分之前必须检查此扩展(尽管他们将不得不解析 ServerHello 以读取扩展)。如果此扩展存在,客户端必须忽略 ServerHello.legacy_version 值,并且必须仅使用“supported_versions”扩展来确定所选版本。如果 ServerHello 中的“supported_versions”扩展包含客户端未提供的版本或包含 TLS 1.3 之前的版本,客户端必须使用“illegal_parameter”警报中止握手。

			struct {
          opaque cookie<1..2^16-1>;
      } Cookie;

Cookie 有两个主要用途:

  • 允许服务器强制客户端在其明显的网络地址上证明可达性(从而提供一定程度的拒绝服务保护)。对于非面向连接的传输起作用(有关此示例,请参见[RFC6347])。
  • 允许服务器将状态卸载到客户端,从而允许其在不存储任何状态的情况下发送 HelloRetryRequest(这样子服务端完全可以啥都不存储了)。服务器可以通过在 HelloRetryRequest Cookie 中存储 ClientHello 的哈希值(使用某些完整性保护算法进行保护)来实现此目的。

当发送 HelloRetryRequest 时,服务器可以向客户端提供“cookie”扩展(注意哈,通常是只有在 ClientHello 中出现的扩展才服务端回应时才可以发送,这是个例外,)。客户端发送新的 ClientHello 时,必须将在 HelloRetryRequest 中接收到的cookie扩展内容复制到新的 ClientHello 中的“cookie”扩展中。后面如果还有握手,客户端发送的新握手的 ClientHello 中不得包含 cookie。

当服务器以无状态方式运行时,它可能会在第一个和第二个 ClientHello 之间收到未受保护的 change_cipher_spec 类型的记录(见第 5 节)。由于服务器未存储任何状态,这将看起来像是收到的第一条消息。以无状态方式运行的服务器必须忽略这些记录。

4.2.3 Signature Algorithms

TLS 1.3 提供了两个扩展来指示涉及到签名验签中可以使用的签名算法。“signature_algorithms_cert”扩展指明适用于证书中的签名算法,而最初出现在 TLS 1.2 中的“signature_algorithms”扩展适用于 CertificateVerify 消息中的签名算法。证书中的密钥还必须与它们所使用的签名算法的类型相匹配。对于 RSA 密钥和 PSS 签名,有个特殊事项要注意,下面会描述。如果不存在“signature_algorithms_cert”扩展,则“signature_algorithms”扩展里面的算法同时指代适用于证书中出现的签名算法。希望服务器通过证书进行身份验证的客户端必须发送“signature_algorithms”扩展。如果服务器通过证书进行身份验证而客户端未发送“signature_algorithms”扩展,则服务器必须使用“missing_extension”警报中止握手(见第 9.2 节)。

添加“signature_algorithms_cert”扩展是为了两个目的:1 TLS协议本身清晰地表明其能力 2 允许证书支持和signature_algorithms不同的算法集。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

每个 SignatureScheme 值列出了客户端支持验证的单个签名算法。这些值按偏好降序排列。请注意,签名算法接收的输入是任意长度的消息,而不是摘要。传统上对摘要起作用的算法应在 TLS 先使用指定的哈希算法对输入进行哈希处理,然后再输入到对摘要做签名的算法里。具体的签名算法参考下面的内容:

RSASSA-PKCS1-v1_5 算法:表示使用 RSASSA-PKCS1-v1_5 [RFC8017] 的签名算法,其对应的哈希算法如 [SHS] 中所定义。仅适用于证书中的签名(见 4.4.2.2 节),无法用于签名的 TLS 握手消息,即使它们可能出于兼容 TLS 1.2 而出现在“signature_algorithms”和“signature_algorithms_cert”中。

ECDSA 算法:表示使用 ECDSA [ECDSA] 的签名算法,签名用到的曲线参考如 ANSI X9.62 [ECDSA] 和 FIPS 186-4 [DSS] 中所定义的相应曲线,哈希算法参考以及如 [SHS] 。签名编码为 DER 编码的 [X690] ECDSA-Sig-Value 结构。

RSASSA-PSS RSAE 算法:(省略,过于琐碎,后面再翻译)

遗留算法字段:具有已知弱点的算法(尤其是 RSASSA-PKCS1-v1_5 的 RSA算法或和ECDSA 一起使用的 SHA-1),所以被弃用。尽管它们可能出于 兼容TLS 1.2 的目的出现在“signature_algorithms”和“signature_algorithms_cert”中,但只能将这些算法用于证书里中的签名(见 4.4.2.2 节),而不能用来做TLS握手签名。简单来说就不要协商这些算法,客户端仅出于兼容性可以发送这些算法,但必须将它们列为最低优先级(在 SignatureSchemeList 中的所有其他算法之后列出)。除非不使用这些弱算法,就无法生成有效证书链,TLS 1.3 服务器绝不能提供 SHA-1 签名的证书(见 4.4.2.2 节)。

自签名证书或作为信任锚点的证书上的签名无法验证,因为它们自己是一个全新信任链(certification path)(见 [RFC5280],3.2 节)。全新信任链的证书可以使用未在“signature_algorithms”扩展中公开定义的算法,作为验证的签名算法。

请注意,TLS 1.2 对该扩展的定义和TLS1.3不同。如果协商 TLS 1.2 的话,该过站实现必须按照 [RFC5246] 的要求行事。特别是:

  • TLS 1.2 的 ClientHellos 可能省略此扩展
  • 在 TLS 1.2 中,该扩展包含哈希/签名对。长度为两个字节编码,因此TLS1.3的SignatureScheme内容可以与 TLS 1.2 的编码一致(兼容)。一些遗留算法对不再保留,这些算法自 TLS 1.3 起被弃用。任何实现都不得提供或协商它们,尤其是MD5 [SLOTH]、SHA-224 和 DSA。
  • ECDSA 签名方案与 TLS 1.2 的 ECDSA 哈希/签名对对齐。然而,旧的语义并不包含签名曲线信息。如果协商 TLS 1.2,实现方必须接受使用“supported_groups”扩展涉及到的任何曲线的签名
  • 宣称支持 RSASSA-PSS(这在 TLS 1.3 中是强制的)的实现方,必须可以接受协商的是 TLS 1.2版本。在 TLS 1.2 中,RSASSA-PSS 与 RSA 密码套件一起使用。

4.2.4 Certificate Authorities

“证书颁发机构”扩展用于指示端点支持的证书颁发机构(CAs),接收端应参考这些机构来证书选择。

“证书颁发机构”扩展的主体由 CertificateAuthoritiesExtension 结构组成。

			opaque DistinguishedName<1..2^16-1>;

      struct {
          DistinguishedName authorities<3..2^16-1>;
      } CertificateAuthoritiesExtension;

authorities:一堆可明确分辨(是谁的)CA名称[X501],以 DER 编码[X690]格式表示。这些可分辨名称指定了信任锚或CA的具体名称;因此,此消息可用于描述已知的信任锚以及所需的授权空间。

客户端可以在 ClientHello 消息中发送“Certificate Authorities”扩展。服务器可以在 CertificateRequest 消息中发送它。

“可信 CA 密钥”(trusted_ca_keys)扩展[RFC6066],有相同的目的但更复杂,在 TLS 1.3 中未使用(尽管它可能出现在提供TLS1.3之前版本的客户端ClientHello 消息中出现)。

4.2.5 OID Filters

感觉意义不大,先跳

4.2.6 Post-Handshake Client Authentication

“post_handshake_auth”扩展用于表明客户端愿意执行握手后认证(第 4.6.2 节)(举个简单例子单向认证变双向认证)。服务器不得向未提供此扩展的客户端发送握手后证书请求(post-handshake CertificateRequest)。服务器不得发送此扩展。该拓展结果为

      struct {} PostHandshakeAuth;

该扩展的“extension_data”字段长度为零,实际上这就是个没内容的标识

4.2.7 Supported Groups

客户端发送“supported_groups”扩展时,该拓展表明客户端支持的key share拓展里面的密钥协商方法(实际上也可能包含椭圆曲线类型),按照从最优先到最不优先的顺序排列。

注意:在 TLS 1.3 之前的版本中,此扩展名为“elliptic_curves”,并且仅包含椭圆曲线组。 请参阅[RFC8422]和[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;

椭圆曲线组(ECDHE):表示支持相应命名曲线(命名曲线可以理解为基点确定,且明确名字的曲线),在 FIPS 186-4 [DSS] 或 [RFC7748]明确定义。曲线的值如果在0xFE00 至 0xFEFF之间则被保留为供个人使用 [RFC8126]。

有限域组(DHE):表示支持相应有限域组,参考 [RFC7919] 。值 0x01FC 至 0x01FF 被保留供个人使用。

named_group_list中的项目按照发送方的偏好排序(最优先的选择在前)。

自 TLS 1.3 起,服务器被允许向客户端发送“supported_groups”扩展。客户端在成功完成握手之前,服务端“supported_groups”的内容不该对本次握手有任何影响,但客户端参考来更改其后续新连接中的“key_share”扩展中使用的组。如果服务器有一个比客户端“key_share”扩展中的组更偏好的组,但仍然愿意接受 ClientHello,就应该发送“supported_groups”以更新客户端对其偏好的看法;服务端发送的supported_groups扩展应包含服务器支持的所有组,不用考虑论客户端是否支持。

4.2.8 Key Share

“key_share”扩展包含端点的加密参数。

客户端可以发送一个空的客户端共享向量,以便向服务器请求组选择,但会导致多个RTT(见第 4.1.4 节)。该拓展结构为

			struct {
          NamedGroup group;
          opaque key_exchange<1..2^16-1>;
      } KeyShareEntry;

group:密钥对应的命名组。

key_exchange:密钥交换信息。该字段的内容由命名组及计算方法确定。有限域Diffie-Hellman[DH76]参数在第 4.2.8.1 节中描述;椭圆曲线Diffie-Hellman参数在第 4.2.8.2 节中描述。

在 ClientHello 消息中,此扩展的“extension_data”字段包含一个“KeyShareClientHello”值:

			struct {
          KeyShareEntry client_shares<0..2^16-1>;
      } KeyShareClientHello;

client_shares: 按客户端偏好降序排列的KeyShareEntry 值列表。

如果客户端期望收到HelloRetryRequest,则此拓展可能为空。每个 KeyShareEntry 值必须对应于“supported_groups”扩展中提供的一个gourp,并且必须保持相同的顺序。然而,这些值可以是“supported_groups”扩展里group们的非连续子集,并且可能省略最优先握手的组的key。这种情况一般出现在,客户端支持的最高优先group比较新,好多地方(中间设备)还不支持

客户端可以提供与supported groups数量相同的 KeyShareEntry 值,每个值代表一组单独的密钥交换参数。例如,客户端可能为多个椭圆曲线或多个有限域Diffie-Hellman组提供keyshare。每个 KeyShareEntry 的 key_exchange 值必须独立生成。客户端不得为同一组提供多个 KeyShareEntry 值。客户端不得为“supported_groups”扩展中未列出的组提供KeyShareEntry 值。服务器可以检查是否违反这些规则,如果违反,则使用“illegal_parameter”警报中止握手。

在 HelloRetryRequest 消息中,此扩展的“extension_data”字段包含一个 KeyShareHelloRetryRequest 值:

			struct {
          NamedGroup selected_group;
      } KeyShareHelloRetryRequest;

selected_group:服务器打算协商的selected_group。

收到 HelloRetryRequest 中的此扩展后,客户端必须验证(1)selected_group 字段对应于原始 ClientHello 中的“supported_groups”扩展中提供的一个组,并且(2)selected_group 字段不对应于原始 ClientHello 中的“key_share”扩展中提供的组(这个是确保是真的需要发送selected_group,而不是瞎发)。如果这两个检查中的任何一个失败,则客户端必须使用“illegal_parameter”警报中止握手。要是没出现刚才的错误,在发送新的 ClientHello 时,客户端必须用仅包含HelloRetryRequest 的 selected_group指示的group的新 KeyShareEntry 的扩展来替换原始的“key_share”扩展

在 ServerHello 消息中,此扩展的“extension_data”字段包含一个 KeyShareServerHello 值:

 			struct {
          KeyShareEntry server_share;
      } KeyShareServerHello;

server_share:服务端从客户端的key share里面挑选出来的单个 KeyShareEntry 值。

如果使用(EC)DHE 密钥建立,服务器在 ServerHello 中只选一个 KeyShareEntry。此值必须和客户端提供的 KeyShareEntry 值处于同一命名组(named group)。服务器不得用客户端的“supported_groups”扩展中未指示的任何组发送 KeyShareEntry,并且在使用“psk_ke” PskKeyExchangeMode 时不得发送 KeyShareEntry。如果使用(EC)DHE 密钥建立并且客户端收到包含“key_share”扩展的 HelloRetryRequest,则客户端必须验证 ServerHello 中选择的 NamedGroup 与 HelloRetryRequest 中的相同。如果此检查失败,客户端必须使用“illegal_parameter”警报中止握手。

4.2.8.1 Diffie-Hellman Parameters

客户端和服务器的Diffie-Hellman[DH76]参数在KeyShare拓展的KeyShareEntry的key_exchange字段编码。内容为Diffie-Hellman group的公开传递的内容(Y = g^X mod p)(Diffie-Hellman group定义,请参见[RFC7919])。将公开的整数编码为大端或者说网络字节序,左侧补零至 p (就是那个被mod的质数)的字节大小。

注意:对于给定的Diffie-Hellman group,填充导致所有公共密钥具有相同的长度。

接收端必须通过确保 1 < Y < p - 1。此检查确保远程对等方行为正常,别最后group是一个很容易碰撞的group。

4.2.8.2 ECDHE Parameters

客户端和服务器的 ECDHE 参数都在 KeyShare 结构中 KeyShareEntry 的不透明 key_exchange 字段中进行编码。

对于 secp256r1、secp384r1 和 secp521r1,其内容是以下结构体的序列化值:

   		struct {
          uint8 legacy_form = 4;
          opaque X[coordinate_length];
          opaque Y[coordinate_length];
      } UncompressedPointRepresentation;

X 和 Y 分别是网络字节序中 x 和 y 值的二进制表示(x,y是点的坐标)。由于没有长度标记,因此每个数字表示占用的字节数量由曲线参数决定。对于 P-256曲线,这意味着 X 和 Y 各自使用 32 个字节,必要时在左侧用零填充。对于 P-384,它们各自占用 48 个字节。对于 P-521,它们各自占用 66 个字节。

对于 secp256r1、secp384r1 和 secp521r1 曲线,对端必须验证公共密钥点Q再椭圆曲线上。验证程序在 [ECDSA RFC] 的 4.3.7 节和 [KEYAGREEMENT] 的 5.6.2.3 节中有定义。此过程包括三个步骤:(1)验证 Q 不是无穷远点(0),(2)验证对于 Q = (x, y),整数 x 和 y 都在正确的区间内,(3)确保 (x, y) 是椭圆曲线方程的正确解。对于这些曲线,实现者不需要验证其在正确的子群中。

对于 X25519 和 X448曲线,公共密钥点Q的内容是在 [RFC7748] 中定义的字节串输入和输出:X25519 为 32 个字节,X448 为 56 个字节。 注意:TLS 1.3 之前的版本允许协商椭圆曲线的基点;TLS 1.3 取消了此功能,改为每个曲线采用固定的基点。

4.2.9 Pre-Shared Key Exchange Modes

为了使用预共享密钥(PSKs),客户端还必须发送一个“psk_key_exchange_modes”扩展。这个扩展的语义是客户端仅支持PSKs使用这些模式,这限制了在此 ClientHello 中提供的 PSKs 的如何使用,同时限制了服务器可能通过 NewSessionTicket 提供的 PSKs 如何使用。

如果客户端提供“预共享密钥(PSK)”扩展,则必须提供“PSK 密钥交换模式(Pre-Shared Key Exchange Modes)”扩展。如果客户端提供“预共享密钥”但没有“PSK 密钥交换模式”扩展,服务器必须中止握手。服务器不得选择客户端未列出的密钥交换模式。此扩展还限制了用于 PSK 会话复用的模式。服务器不应发送与所宣传的模式不兼容的 NewSessionTicket;但是,如果服务器这样做,倒也没啥事,其影响仅仅是客户端的恢复尝试失败。

			enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;

      struct {
          PskKeyExchangeMode ke_modes<1..255>;
      } PskKeyExchangeModes;

psk_ke:仅 PSK 密钥建立。在此模式下,服务器不得提供“key_share”值(因为key share涉及到了(EC)DHE的握手流程,客户端不支持)

psk_dhe_ke:带有(EC)DHE 的 PSK 密钥建立。在此模式下,客户端和服务器必须按照第 4.2.8 节中的描述提供“key_share”值。

任何未来新添加的PSK模式都必须明确标识服务器选择的模式;目前,这通过 ServerHello 中“key_share”的存在来表明。

4.2.10 Early Data Indication

当使用预共享密钥(PSK)并且该 PSK 允许发送early data时,客户端可以在第一趟发送数据的时候中发送应用数据(这句话是指发送ClientHello了,就发送earlydata)。如果客户端选择这样做,则必须同时提供“pre_shared_key”和“early_data”扩展。

此扩展的“扩展数据(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;

0-RTT 数据的参数(版本、对称密码套件、应用层协议协商 (ALPN) [RFC7301] 协议等)和PSK是相关的。对于外部提供的 PSK,这些参数与PSK一起提供。对于通过 NewSessionTicket 消息建立的 PSK,这些参数是在建立 PSK 的连接中协商得出的。用于加密eayly data的 PSK 必须是客户端“pre_shared_key”扩展中列出的第一个 PSK。

对于通过 NewSessionTicket 提供的 PSK,服务器必须验证所选 PSK 标识的ticket age(通过从 PskIdentity.obfuscated_ticket_age 减去 ticket_age_add 并对 2^32 取模计算)和ticket颁发的时间的只有小范围的差值(见第 8 节)。如果不在,服务器应该继续握手但拒绝 0-RTT报文,因为这可能意味着此 ClientHello 是一个历史重放报文。

在0-RTT 消息同其他握手消息,使用相同类型的加密算法,但使用不同的密钥保护。在收到服务器的 Finished 消息后,如果服务器接受early data,将发送 EndOfEarlyData 消息以指示密钥更改。这条消息将使用 0-RTT 流量密钥进行加密。

收到“early_data”扩展的服务器必须以以下三种方式之一行事:

  • 忽略该扩展并返回常规的 1-RTT 响应。然后,服务器通过尝试使用握手流量密钥对接收的记录进行解密来跳过early data,丢弃解密失败的记录(最多丢弃max_early_data_size的报文)。一旦记录成功解密,它将被视为客户端传达的第二个报文,服务器按照普通的 1-RTT 握手进行处理。
  • 通过发送HelloRetryRequest 要求客户端发送新的ClientHello。客户端在后续的 ClientHello 中不得包含“early data”扩展。然后,服务器通过跳过所有外部内容类型为“application_data”(表示它们已加密,这个类型在record layer里面显示)的记录来忽略early data,最多达到配置的 max_early_data_size。
  • 在 EncryptedExtensions 中返回其自身的“early_data”扩展,表示它打算处理early data。稍微注意,服务器需要接受early data全部数据消息的一部分。即使服务器发送接受early data的消息,实际的early data在服务器生成此回应时可能已经在网路上传递。

为了接受early data,服务器必须接受 PSK 密码套件,并选择客户端“pre_shared_key”扩展中提供的第一个密钥。此外,它必须验证以下新与所选 PSK 相关的信息相同:

  • TLS 版本号
  • 所选的密码套件
  • 所选的 ALPN [RFC7301] 协议(如果有的话)

这些限制是使用相关 PSK 执行 1-RTT 握手所需要求的超集。对于外部建立的 PSK,这些信息与PSK一起提供。对于通过 NewSessionTicket 消息建立的 PSK,这些信息是在颁发ticket的连接中协商得出的。

未来的扩展必须定义它们与 0-RTT 如何交互。

如果这些检查中的任何一项失败,服务器不得使用该扩展进行响应,并且必须使用上述前两种机制之一丢弃所有第一次传来的数据(从而回退到 1-RTT 或 2-RTT)。如果客户端尝试进行 0-RTT 握手但服务器拒绝,服务器通常不会产生 0-RTT record layer加密密钥,而必须使用握手密钥(使用 1-RTT 握手密钥或在 HelloRetryRequest 的情况下查找明文 ClientHello)来查找第一个非 0-RTT 消息。

如果服务器选择接受“early data”扩展,那么在处理early data记录时,如果无法解密 0-RTT 记录,它必须按照第 5.2 节的规定以“bad_record_mac”警报终止连接。

如果服务器拒绝“early data”扩展,客户端应用程序可以选择在握手完成后重新传输之前在early data中发送的应用数据。请注意,自动重新传输early data可能导致关于连接协议自动机状态转换错误。例如,当协商的连接选择与early data使用的不同的 ALPN 协议时,应用程序可能需要构建不同的消息。同样,如果early data假设了任何关于连接状态的内容,握手完成后再次发送,状态就可能发生了变化。

TLS 实现不应自动重新发送early data;交给应用程序决定决定何时重新传输更为合适。TLS实现者绝不能自动重新发送early data,除非协商连接时依然选择相同的 ALPN 协议。

4.2.11 Pre-Shared Key Extension

“预共享密钥”扩展用于协商与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:psk的标识。例如,tieckt(如在附录 B.3.4中所定义)或外部建立的PSK的标签。 obfuscated_ticket_age:混淆后的ticket age。第 4.2.11.1 节描述了如何通过 NewSessionTicket 消息声称此值。对于外部提供的SPK,应使用 0 的混淆票龄,服务器必须忽略该值。 identities:客户端愿意与服务器协商的SPK列表。如果与“early_data”扩展一起发送(见第 4.2.10 节),第一个PSK用于 0-RTT 传输。 binders:一系列 HMAC 值,与PSK列表中的每个PSK一一对应且顺序相同,计算方式下面会描述。 selected_identity:服务器选择的身份,表示为客户端列表中PSK的(基于 0 的)索引(就是个数字)

每个预共享密钥(PSK)都和一个哈希算法相关联。对于通过NST报文建立的 PSK(第 4.6.1 节),算法就是颁发Ticket时候用的 KDF 哈希算法。对于外部建立的 PSK,哈希算法是颁发PSK的时候建立的

如果未提供HASH算法,则 PSK默认用SHA-256。服务器必须确保选择兼容的 PSK(如果有)和密码套件。

在 TLS 1.3 之前的版本中,服务器名称标识(SNI)值旨在与会话相关联([RFC6066]的第 3 节),要求服务器检查SNI 值与复用会话Session中中指定的SNI值匹配。然而,实际实现的时候,对于提供的两个 SNI 值是否一致,到底用哪一个并不一致,导致一致性检查实际上由客户端自己。在 TLS 1.3 中,SNI 值始终在恢复握手的报文中明确指定,服务器无需将 SNI 值与PSK的内容强关联。但是,客户端应将 SNI 与 PSK 一起存储以满足第 4.6.1 节的要求。

协议实现方需注意:当恢复会话是 PSK 的主要目的时,最简单实现 PSK/密码套件匹配要求的方法是首先协商密码套件,然后排除任何不兼容的 PSK。任何未知的 PSK(例如,不在 PSK 数据库中或使用未知密钥加密的 PSK)应直接忽略。如果未找到可接受的 PSK,服务器应在可能的情况下执行非 PSK 握手(就是完整握手)。如果后向兼容性很重要,客户端提供的、外部建立的 PSK 应影响密码套件的选择。

在接受 PSK 密钥建立之前,服务器必须验证相应的binder 值(见下面的第 4.2.11.2 节)。如果此值不存在或未通过验证,服务器必须中止握手。服务器不应尝试验证多个绑定器;相反,它们应选择单个 PSK 并仅验证与该 PSK 对应的绑定器。有关此要求的安全原理,请参见第 8.2 节和附录 E.6。为了向客户端表示接受 PSK 密钥,服务器需要发送一个“pre_shared_key”扩展,指示所选的标识。

客户端必须验证服务器的所选标识在客户端提供的范围内,服务器选择的密码套件的HASH算法也与 PSK 的HASH算法匹配。此外,如果 ClientHello 的“psk_key_exchange_modes”扩展要求提供非仅PSK模式的握手,则服务器的“key_share”扩展也必须存在。如果这些值不一致,客户端必须使用“illegal_parameter”警报中止握手。

如果服务器提供“early_data”扩展,客户端必须验证服务器的所选标识为 0。如果返回任何其他值,客户端必须使用“illegal_parameter”警报中止握手。

“pre_shared_key”扩展必须是 ClientHello 中的最后一个扩展(这有助于如下所述的实现)。服务器必须检查它是否是最后一个扩展,否则使用“illegal_parameter”警报中止握手。

4.2.11.1. Ticket 年龄

客户端的Ticket Age的看法是自收到 NewSessionTicket 消息以来的duration。客户端不得尝试使用ticket age大于随ticket提供的“ticket_lifetime”值的ticket。每个 Psk Identity 的“obfuscated_ticket_age”字段都是混淆后的版本,计算方法为,以毫秒为单位的ticket age加上ticket包含的“ticket_age_add”值(见第 4.6.1 节),取模 2^32 形成。除非在不同连接重复使用PSK,此加法可防止监听者将Ticket和连接关联。请注意,NewSessionTicket 消息中的“ticket_lifetime”字段以秒为单位,而“obfuscated_ticket_age”以毫秒为单位。由于票证的有效期限制为一周,即使以毫秒为单位,32 位也足以表示任何合理的年龄。

4.2.11.2. PSK Binder

PSK Binder将PSK 与当本次握手关联起来,当然也关联了颁发PSK的时候(就是上次握手)。binder列表中的每个entry都采用一种方法计算得出,对从头到包含PreSharedKeyExtension.identities 字段的部分 ClientHello 做transcript hash,再(见第 4.4.1 节)进行 HMAC 计算得出。所以会除了Binders部分,包括本次所有的ClientHello。消息的长度字段(包括总长度、扩展块的长度和“pre_shared_key”扩展的长度)都设置为好像存在binder。

PskBinderEntry 的计算方式与 Finished 消息(第 4.4.4 节)相同,只不过把BaseKey换为binder_key,binder_key从PSK 派生得出(见第 7.1 节)。

如果握手时发送了HelloRetryRequest,计算transcript hash是,第一个ClientHello 和 HelloRetryRequest 会与新的 ClientHello 一起包含在计算中。例如,如果客户端发送 ClientHello1,其绑定器将通过以下方式计算:

Transcript-Hash(Truncate(ClientHello1))

其中,Truncate() 会从 ClientHello 中删除binders列表。 如果服务器用 HelloRetryRequest 响应,然后客户端发送 ClientHello2,其binders将通过以下方式计算:

Transcript-Hash(ClientHello1, HelloRetryRequest, Truncate(ClientHello2))

完整的 ClientHello1/ClientHello2 包含在所有其他握手哈希计算中。请注意,在第一次传输中,Truncate(ClientHello1) 直接进行哈希处理,但在第二次传输中,ClientHello1 先进行哈希处理,然后作为“message_hash”消息重新注入,如[第 4.4.1 节][0]所述。

4.2.11.3 Processing Order

客户端在收到服务器的“Finished”之前可以一致“流式传输” 0-RTT 数据,然后在发送“EndOfEarlyData”消息,和其他握手报文。为了避免死锁,当接受“early_data”时,服务器必须尽快处理客户端的“ClientHello”,然后立即发送其消息序列,而不是在发送其“ServerHello”之前等待客户端的“EndOfEarlyData”消息。

结尾

唉,尴尬

狗头的赞赏码.jpg