RFC8446 TLS1.3中文版(1-3)

摘要

本文规定了TLS协议的1.3版本。TLS为CS模式应用提供了一种设计为防止监听、篡改和伪造的Internet通信方式

本文为RFC5705和RFC6066的更新版,淘汰了RFC5077、RFC5246和RFC6961。本文也规定了实现TLS1.2的新要求。

1. 介绍

TLS 的主要目标是为通信双方提供一个安全的通道;对下层传输的唯一要求是提供可靠保序的数据流。安全通道尤其应该提供如下属性:

  • 认证(Authentication):server端应该总是需要认证的;client端可以选择性的认证。认证可以通过非对称算法完成(如RSA、椭圆曲线数字签名算法(ECDSA)、或Edwards曲线数字签名算法(EdDSA))完成,或通过对称预共享密钥(PSK)。
  • 保密(Confidentiality):在建立好的通道上发送的数据只能终端可见。TLS不隐藏传输数据的长度,但终端可以填充TLS记录(pad)来隐藏真实长度,从而提升安全性。
  • 完整性(Integrity):在建立好的通道上发送的数据不能被攻击者修改(如果修改了一定会被发现)。

即使出现像RFC3552那样已经完全控制网络的攻击者,这些属性也应该保证。更完整的相关安全属性清单详见附录E。


TLS由两个主要部分构成:

  • 握手协议(第4节),认证通信双方,协商加密模式和参数,并确定共享密钥。握手协议可以防止篡改,如果连接没有受到攻击,攻击者就不能强制两端协商不同的参数。
  • 记录协议(第5节),使用由握手协议协商出的参数来保护通信双方的流量。记录协议将流量分为一系列记录,每个记录独立地使用流量密钥保护。

TLS是一个独立的应用协议;上层协议可以透明地运行于TLS之上。然而,TLS标准并未指定怎样使用TLS保证安全、怎样发起TLS握手以及怎样理解认证证书交换,这些留给运行在TLS之上的协议的设计者和实现者来判断。


本文定义了TLS 1.3. 虽然TLS 1.3与以前的版本不直接兼容,但所有TLS版本都包含一个版本控制机制,可以在客户端和服务器都支持的情况下协商出一个公共版本。

本文取代和废除了以前版本的TLS,包括1.2版本[RFC5246]。也废除了在[RFC5077]里面定义的TLS ticket机制,并用定义在第2.2节中的机制取代它。由于TLS 1.3改变了密钥的产生方式,它更新了[RFC5705]。正如7.5节描述的那样。它也改变了在线证书状态协议(OCSP)消息的传输方式,因此更新了[RFC6066],废除了[RFC6961],如4.4.2.1所述。

1.1. 约定和术语

使用了以下术语:
客户端(client): 发起TLS连接的端点。
连接(connection): 两端之间的传输层连接。
端点(endpoint): 连接的客户端或者服务器。
握手(handshake): 客户端和服务器之间协商TLS交互的后续参数的出示协商。
对端(peer): 一个端点,当说到一个特定端点,对端指的是非当前讨论的端点。
接收端(receiver): 接收记录的端点。
发送者(sender): 发送记录的端点。
服务器(server): 没有发起TLS连接的端点。

1.2. 跟TLS 1.2的主要区别

以下是TLS 1.2和TLS 1.3的主要差异。这并不是全部差异,还有很多次要的差别。

  • 支持的对称算法列表已经删除了所有被认为是遗留问题的算法。列表保留了所有使用“关联数据的认证加密”(AEAD)算法。密码套件的概念已经改变,将认证和密钥交换机制与记录保护算法(包括密钥长度)和Hash(用于秘钥导出函数和握手消息认证码MAC)分离。
  • 增加0-RTT模式,为一些应用数据在连接建立阶段节省了一次往返,这是以牺牲一定的安全特性为代价的。
  • 静态RSA和Diffie-Hellman密码套件已经被删除;所有基于公钥的密钥交换算法现在都能提供前向安全。
  • 所有ServerHello之后的握手消息现在都已经加密。扩展之前在ServerHello中以明文发送,新引入的EncryptedExtension消息可以保证扩展以加密方式传输。
  • 重新设计了密钥导出函数。新的设计使得密码学家能够通过改进的密钥分离特性进行更容易的分析。基于HMAC的提取-扩展密钥导出函数(HKDF)被用作一个基础的原始组件。
  • Handshake状态机进行了重大重构,以便更具一致性和删除多余的消息如ChangeCipherSpec(除了中间件兼容性需要)。
  • 椭圆曲线算法已经属于基本的规范,且包含了新的签名算法,如EdDSA。TLS 1.3删除了点格式协商以便于每个曲线使用单点格式。
  • 其它的密码学改进包括改变RSA填充以使用RSA概率签名方案(RSASSA-PSS),删除压缩,数字签名算法DSA,和定制DHE组(Ephemeral Diffie-Hellman)。
  • 废弃了TLS1.2的版本协商机制,以便在扩展中添加版本列表。这增加了不支持版本协商的服务端的兼容性。
  • 之前版本中会话恢复(根据或不根据服务端状态)和基于PSK的密码族已经被一个单独的新PSK交换所取代。
  • 引用已更新至最新版本的RFC(例如,RFC 5280而不是RFC 3280)。

1.3. 更新影响TLS1.2

本文提出了几个可能影响TLS 1.2实现的变化,包括那些不支持TLS 1.3的实现:

  • 4.1.3中的版本降级保护机制。
  • 4.2.3中的RSASSA-PSS签名方案。
  • ClientHello中“supported_versions”扩展可以用于协商TLS使用的版本,优先于ClientHello中的legacy_version字段。
  • "signature_algorithms_cert"扩展允许一个客户端声明它使用哪种签名算法验证X.509证书。
  • 此外,本文提出了支持TLS的早期版本需要实现的部分,见9.3。

2. 协议概览

安全通道使用的密码参数由TLS握手协议生成。这个TLS的子协议在client和server第一次通信时使用。握手协议使两端协商协议版本、选择密码算法、选择性互相认证,并得到共享的密钥数据。一旦握手完成,双方就会使用得出的密钥保护应用层流量。
握手失败或其它协议错误会触发连接中止,在这之前可以有选择地发送一个警报消息(第6章)。
TLS支持3种基本的密钥交换模式:

  • (EC)DHE (基于有限域或椭圆曲线的Diffie-Hellman)
  • PSK-only
  • 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 导出的秘钥加密的消息。

                                                      图1: TLS握手的消息流

握手可以分为三个阶段(上图中已表明):

  • 密钥交换:确定共享密钥材料并选择加密参数,在这个阶段之后所有的数据都会被加密。
  • Server参数:确定其它的握手参数(客户端是否被认证,应用层协议支持等)。
  • 认证:认证server(并且选择性认证客户端),提供密钥确认和握手完整性。

       在密钥交换阶段,client会发送ClientHello(4.1.1)消息,其中包含了一个随机nonce(ClientHello.random)、协议版本、对称密码或HKDF hash对的列表、Diffie-Hellman共享密钥列表(在4.2.8中的key_share扩展中)或预共享密钥标签列表(4.2.11中的pre_shared_key扩展中),或二者都有、和其它额外扩展。还可能存在其他字段或消息,以实现中间件的兼容性。


       服务端处理ClientHello并为连接确定合适的加密参数,然后以ServerHello(4.1.3)响应(其中携带了协商好的连接参数)。ClientHello和ServerHello一起确定共享密钥。如果使用的是(EC)DHE密钥,则ServerHello中会包含携带临时Diffie-Hellman共享参数的key_share扩展,这个共享参数必须与客户端的在相同的组里;如果使用的是PSK密钥,则ServerHello中会包含pre_shared_key扩展以表明客户端提供的哪一个PSK被选中。需要注意的是实现上可以将(EC)DHE和PSK一起使用,这种情况下两种扩展都需要提供。


       随后服务端会发送两个消息来确定Server参数:

  • EncryptedExtensions:用来响应跟密码参数无关的ClientHello扩展,除了针对用户证书的扩展。[4.3.1]
  • CertificateRequest: 如果需要基于证书的客户端认证,则包含与证书相关的参数。如果不需要客户端认证则省略此消息。[4.3.2]

最后,client和server交换认证消息。TLS在每次认证时使用相同的消息集,基于PSK的认证随密钥交换进行。特别是:

  • Certificate: 终端和任何每证书扩展。如果不带证书认证则此消息会被服务端忽略;如果服务端没有发送CertificateRequest(这表明客户端不使用证书认证),此消息会被客户端忽略。需要注意的是如果原始公钥[RFC 7250]或缓存信息扩展[RFC 7924]正在被使用,则此消息不会包含证书而是包含一些与server的长期密钥相关的其它值。[4.4.2]
  • CertificateVerify: 使用与证书消息中的公钥配对的私钥对整个握手消息进行签名。如果终端没有使用证书进行验证则此消息会被忽略。
  • Finished: 对整个握手消息的MAC(消息认证码)。这个消息提供了密钥确认,将终端身份与交换的密钥绑定在一起,这样在PSK模式下也能认证握手。[4.4.4]

接收到server的消息之后,client会响应认证消息,即Certificate,CertificateVerify (如果需要), 和Finished。
这时握手已经完成,client和server会提取出密钥材料用于记录层交换应用层数据,这些数据需要通过认证的加密来保护。应用层数据一定不能在Finished消息之前发送,必须等到记录层开始使用加密密钥之后才可以发送。需要注意的是server可以在收到client的认证消息之前发送应用数据,任何在这个时间点发送的数据,当然都是在发送给一个未被认证的对端。

2.1. 不正确的DHE共享

如果client没有提供足够的key_share扩展(例如,只包含server不接受或不支持的DHE或ECDHE组),server会使用HelloRetryRequest来纠正这个不匹配问题,客户端需要使用一个合适的key_share扩展来重启握手,如图2所示。如果没有通用的密码参数能够协商,server必须使用一个适当的警报来中止握手。

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]

图 2:带有不匹配参数的完整握手过程的消息流程

注:这个握手过程包含初始的ClientHello/HelloRetryRequest交换,不能被新的ClientHello重置。
TLS也支持几个基本握手中的优化变体,下面的章节将描述。

2.2. 恢复和预共享密钥(Pre-Shared Key)

虽然TLS预共享密钥(PSK)能够在带外建立,PSK也能在之前的连接中确定然后用来建立新连接(会话恢复或使用PSK恢复)。一旦握手完成,server就能给client发送与来自初次握手的唯一密钥对应的PSK标识(见4.6.1)。然后client能够使用这个PSK标识在将来的握手中协商相关PSK的使用。如果server接受PSK,新连接的安全上下文在密码学上就与初始连接关联在一起,从初次握手中得到的密钥就会用于装载密码状态来替代完整的握手。在TLS 1.2以及更低的版本中,这个功能由session ID和session ticket [RFC5077]来提供。这两个机制在TLS 1.3中都被废除。

PSK可以与(EC)DHE密钥交换算法一同使用以便使共享密钥具备前向安全,PSK也可以单独使用,这样以丢失了应用数据的前向安全为代价。
图3显示了两次握手,第一次建立了一个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]
               图 3: PSK和恢复消息流

当server通过PSK进行认证时,它不会发送Certificate或CertificateVerify消息。当client通过PSK恢复时,它也应当提供一个key_share给server,以允许server在需要的情况下拒绝恢复并回退到完整的握手。Server响应一个pre_shared_key扩展来协商确定PSK密钥的使用方法,并响应一个key_share扩展(如图所示)来进行(EC)DHE密钥确定,由此提供前向安全。 当PKS在带外提供时,必须一起提供PSK标识和与PSK一起使用的KDF hash算法。

注:当使用带外提供的PSK时,一个关键的考虑是在密钥生成时使用足够的熵,就像[RFC4086]中讨论的那样。从口令或其它低熵源导出的共享密钥并不安全。一个低熵密码或口令,容易受到基于PSK绑定器(binder)的字典攻击。就算使用了Diffie-Hellman密钥建立方法,这种PSK认证也不是强口令认证的密钥交换。它不能防止可以观察到握手的攻击者对密码/PSK执行暴力攻击。

2.3. 0-RTT数据

当client和server共享一个PSK(从外部获得或通过以前的握手获得)时,TLS 1.3允许client在第一轮发送出去的消息(early data)中携带数据。Client使用这个PSK来认证server并加密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]

+ 表明是在之前提到的消息中发送的重要扩展

* 表明可选的或者特定条件下发送的消息或扩展

() 表示消息由client_early_traffic_secret导出的密钥保护

{} 表示消息由 [sender]_handshake_traffic_secret导出的密钥保护

[] 表示消息由[sender]_application_traffic_secret_N导出的密钥保护

图4: 0-RTT握手的消息流

注意:0-RTT数据的安全属性比其它类型的TLS数据弱,特别是:

  1. 这类数据没有前向安全,它只使用了从提供的PSK中导出的密钥进行加密。
  2. 不能保证在多条连接之间不会重放。为普通的TLS 1.3 1-RTT数据提供抗重放的保护方法是使用server的Random值,但0-RTT不依赖于ServerHello,因此只能得到更弱的保护。如果数据使用TLS client认证或在应用协议认证,这一点尤其重要。这个警告适用于任何使用early_exporter_master_secret的情况。

0-RTT数据不能在一个连接内复制(如server在同一连接内不会处理相同数据两次),攻击者不能使0-RTT数据看起来像1-RTT数据(因为是用不同秘钥保护的)。附录E.5包含了潜在攻击的描述,第8章描述了server可以用来限制重放影响的机制。

3. 表示语言(Presentation Language)

本文使用另外的表示方法处理数据格式。下面会用到非常基础甚至是有些随便定义的表示语法。

3.1. 基本块大小

所有数据条目的描述都是被显示指定的。基本数据块大小是1字节(即8位)。多字节数据条目是字节的串联,从左到右,从上到下。从字节流的角度看,一个多字节条目(在例子中是一个数值)的组织方式(使用C记法)如下:

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

多字节数值字节序是普通的网络字节序或大端格式。

3.2. 其他

注释以/" 开始,以 "/结束。

可选组件使用 [[ ]]括起来(两个中括号)。

包含未解释数据的单字节实体属于opaque类型。

 可以为一个现有类型T定义一个别名T':
  T T';

3.3. 数字(Number)

基本数字数据类型是一个无符号字节(uint8)。所有更大的数据类型都是由固定长度的字节序列构成的,如3.1所述,这些字节串接在一起,并且也是无符号的。以下数字类型是预定义的:

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

规范中的所有值都以网络序(大端序)传输,由十六进制字节01 02 03 04表示的uint32相当于十进制值16909060

3.4. 向量(Vector)

一个向量(一维数组)是同类数据元素的流。向量的大小可能在编写文档时指定或留待运行时确定。在任何情况下,向量的长度都是指字节数而非元素数。定义一个新类型T’(是一个固定长度的类型T的向量)的语法是:

  T T'[n];

这里T’在数据流中占据了n个字节,而n是多个T的大小。向量的长度并不包含在编码流中。
在下面的例子中,Datum被定义为协议不能理解的3个连续字节, 而Data是三个连续的Datum,共占据9个字节。

  opaque Datum[3];      /* three uninterpreted bytes */
  Datum Data[9];        /* three consecutive 3-byte vectors */

变长向量的定义是通过指定一个合法长度的子范围来实现(使用符号<floor…ceiling>)。当这些被编码时,在字节流中实际长度是在向量的内容之前。这个长度会以一个数字的形式出现,并使用足够多的字节以便表示向量的最大长度(ceiling)。一个实际长度是0的变长向量会被当做一个空向量。

  T T'<floor..ceiling>;

在下面的例子中会强制要求一个向量必须包含300-400个字节的opaque类型数据,不能为空。实际长度字段占用两个字节,为一个uint16,这足以代表数值400(见3.4)。相似地,longer可以描述多达800字节的数据,或400个uint16类型的元素,可以为空。它的编码会在向量前包含一个两字节的实际长度字段。一个编码向量的长度必须是单个元素长度的偶数倍(例如,一个17字节长的uint16类型的向量是非法的)。

  opaque mandatory<300..400>; /* length field is two bytes, cannot be empty */
  uint16 longer<0..800>; /* zero to 400 16-bit unsigned integers */

3.5. 枚举

另外一种稀疏(sparse)的数据类型是枚举。每个定义都是一个不同的类型。只有相同类型的枚举能被赋值或比较。枚举的每个元素都必须指定一个值,如下所示。因为枚举类型的元素并不是有序的,所以它们能够以任意顺序指定任意唯一值。

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

将来对协议的扩展或添加会定义新的值。实现需要能够解析或者忽略未知的值,除非字段的定义另有说明。
一个枚举在字节流中占据的空间需要足够存储其定义的最大有序数值。下面的定义会使用1个字节来表示Color类型的字段。

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

一个选择是指定一个值但不关联标记以强制定义枚举的大小,这样无需定义一个多余的元素。
在下面这个例子中,Taste在字节流中会消耗2个字节, 但在当前的协议版本中只能表示数值1,2,或4。

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

一个枚举类型的元素名称被局限于定义的类型。在第一个例子中,对枚举的第二个元素的完全合格的引用是Color.blue,如果能很好的定义赋值目标,就不需要这样的格式。

  Color color = Color.blue;     /* overspecified, legal */
  Color color = blue;           /* correct, type implicit */

指定给枚举的名字不需要是唯一的。数字可以描述使用相同名字的一个范围。这个值包括取值范围中最小和最大值,由两个点分隔开。主要用于保留空间区域。

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 must always be 8 */
      T2 f2;
  } T;

3.8. 变量

考虑到从环境中得到的一些信息是变化的,定义的结构体可以有一些变量。选择符必须是一个定义了结构体中变量取值的枚举类型。Select语句的每个分支指定了变量字段的类型和一个可选的字段标签。通过这个机制变量可以在运行时被选择,而不是被表示语言描述。

  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>; /* variable length */
  } V1;

  struct {
      uint32 number;
      opaque string[10];    /* fixed length */
  } V2;

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

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值