TLS 1.3草案中文版(翻译进度:第4章)

时间:2022-09-11 11:58:19

1. 介绍

1.1公约和术语

略。

1.2变更记录

略。

1.3与TLS 1.2的主要差异

下面的列表描述了TLS 1.2和TLS 1.3的主要功能差异。这个列表并不是详尽无遗的,此外还有很多次要的差别。

   - 已支持的对称算法的列表已经剪除了所有被认为是“遗产”的算法。列表保留了所有使用“带关联数据的认证加密”(AEAD)算法。密码族的概念已经转变为将认证和密钥交换机制与记录保护算法(包括机密密钥长度)分离,一个hash会被用于密钥导出函数和HMAC。

    - 添加了一个0-RTT模式,为一些应用数据在连接建立阶段节省了一次往返,这是以牺牲一定的安全特性为代价的。

    - 静态RSA和Diffie-Hellman密码族已经被删除;所有基于公钥的密钥交换算法现在都能提供前向安全。

    -所有ServerHello之后的握手消息现在都已经加密。新引入的EncryptedExtension消息允许之前在ServerHello中以明文发送的各种扩展同样享有针对活动攻击者的机密保护。

    - 密钥导出函数被重新设计。新的设计使得密码学家能够通过改进的密钥分离特性进行更容易的分析。基于HMAC的提取-扩展密钥导出函数(HKDF)被用作一个基础的原始组件(primitive)。

    - Handshake状态机进行了引人注目的重组,以便更具一致性和删除多余的消息如ChangeCipherSpec。

    - 椭圆曲线算法已经属于基本的规范,且包含了新的签名算法,如ed25519和ed448。TLS 1.3删除了点格式协商以便于每个曲线使用单点格式。

    - 其它的密码学改进包括删除压缩和定制的DHE组,改变RSA填充以使用PSS,删除DSA。

    - TLS1.2的版本协商机制被废弃以便在一个扩展中添加一个版本列表。这增加了与不正确地实现版本协商的server的兼容性。

    - 带有和不带server端状态的会话恢复以及TLS早期版本的基于PSK密码族已经被一个单独的新PSK交换所取代。

    - 酌情更新引用以指向最新版本的RFC(例如,RFC 5280而不是RFC 3280)。

1.4 更新对TLS1.2的影响

    本文定义了几个选择性影响TLS 1.2实现的变化:

    - 一个在4.1.3节中描述的版本降级保护机制。

    - 在4.2.3节中定义的RSASSA-PSS签名方案。

    - ClientHello中“支持的版本”的扩展可以被用于协商TLS使用的版本,它优先于ClientHello中的legacy_version域。

    一个同时支持TLS 1.2的TLS 1.3的实现可能需要包含一些变更,即使TLS 1.3不使用时也需要支持这些变更。详细信息见索引章节。

    此外,本文澄清了一些需要服从的需求以支持TLS的早期版本;见9.3节。

2 协议概览

        安全通道所使用的密码参数由TLS握手协议生成。这个TLS的子协议在client和server第一次通信时使用。握手协议允许两端协商一个协议版本,选择密码算法,选择性互相认证,并建立共享的密钥数据。一旦握手完成,双方就会使用建立好的密钥保护应用层流量。
        一个失败的握手或其它的协议错误会触发连接的中止,在这之前可以有选择地发送一个警报消息(第6章)。
        TLS支持3种基本的密钥交换模式:
        - (EC)DHE (基于有限域或椭圆曲线的Eiffie-Hellman)
        - 单独的PSK
        -  PSK结合(EC)DHE
        下面的图1显示了基本TLS握手的全部流程:
TLS 1.3草案中文版(翻译进度:第4章)

                  +  标明是在以前标注的消息中发送的值得注意的扩展
                  *  表示可选的或者依赖一定条件的消息/扩展,它们不总是发送
                  () 表示消息由从client_early_traffic_secret导出的密钥保护
                  {} 表示消息使用从一个[sender]_handshake_traffic_secret导出的密钥保护
                  [] 表示消息使用从[sender]_application_traffic_secret_N导出的密钥保护

                    图1:完整握手消息流

        握手可以被认为有三个阶段(在上面的图中已表明):
        - 密钥交换:建立共享密钥数据并选择密码参数。在这个阶段之后所有的数据都会被加密。
        - Server参数:建立其它的握手参数(client是否被认证,应用层协议支持等)。
        - 认证:认证server(并且选择性认证client),提供密钥确认和握手完整性。

        在密钥交换阶段,client会发送ClientHello(4.1.1节)消息,其中包含了一个随机nonce(ClientHello.random);它提供了协议版本,一个对称密码/HKDF hash对的列表;一个Diffie-Hellman密钥共享集合或一个预共享密钥标签(在4.2.11节的"key_share"扩展中)集合,或二者都有;和可能的其它扩展。

        Server处理ClientHello并为连接确定合适的密码参数。然后它会以自己的ServerHello(4.1.3节)作为响应,其中表明了协商好的连接参数。ClientHello和ServerHello合在一起来确定共享密钥。如果已经建立的(EC)DHE密钥正在被使用,则ServerHello中会包含一个”key_share”扩展,和这个扩展一起的还有server的临时Diffie-Hellman共享参数,这个共享参数必须与client的一个共享参数在相同的组里。如果使用的是PSK密钥,则ServerHello中会包含一个"pre_shared_key"扩展以表明client提供的哪一个PSK被选中。需要注意的是实现上可以将(EC)DHE和PSK一起使用,这种情况下两种扩展都需要提供。
随后Server会发送两个消息来建立Server参数:
        EncryptedExtensions: 用来响应不用于决定密码参数的ClientHello扩展,除了针对用户证书的扩展。[4.3.1节]
        CertificateRequest: 如果被要求基于证书的client认证,则包含与证书相关的参数。如果client认证不被要求则此消息会被省略。
        最后,client和server交换认证消息。TLS在每次认证时使用相同的消息集,特别是:
        Certificate: 终端和任何每证书扩展的证明。如果不带证书认证则此消息会被server忽略;如果server没有发送CertificateRequest(因此表明client不使用证书认证),此消息会被client忽略。需要注意的是如果原始公钥[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来纠正这个不匹配问题,client需要使用一个合适的”key_share”扩展来重启握手,如图2所示。如果没有通用的密码参数能够协商,server必须使用一个适当的警报来中止握手。

TLS 1.3草案中文版(翻译进度:第4章)

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

注:这个握手过程包含初始的ClientHello/HelloRetryRequest交换;它不能被新的ClientHello重置。

TLS也支持几个基本握手中的优化变体,正如下面的章节中描述的那样。

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

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

        PSK可以与(EC)DHE密钥交换算法一同使用以便使共享密钥具备前向安全,或者PSK可以被单独使用,这样是以丢失了应用数据的前向安全为代价。

        图3显示了两次握手,第一次建立了一个PSK,第二次时使用它:

TLS 1.3草案中文版(翻译进度:第4章)

                                                                               图3: PKS和恢复消息流

        当server通过一个PSK进行认证时,它不会发送一个Certificate或一个CertificateVerify消息。当一个client通过PSK提供一个恢复时,它也应当提供一个"key_share"给server,以允许server拒绝恢复,如果需要的话回退到一个完整的握手。Server响应一个"pre_shared_key"扩展来协商建立PSK密钥的方法,并响应一个"key_share"扩展(如图所示)来进行(EC)DHE密钥建立,由此提供前向安全。

        当PKS在带外提供时,PSK身份和与PSK一起使用的KDF hash算法也必须被提供。

        注:当使用一个带外提供的预共享密钥时,一个关键的考虑是在密钥生成时使用足够的熵,就像[RFC4086]中讨论的那样。从一个口令或其它低熵源导出的一个共享密钥并不安全。一个低熵密码,或口令,易遭受基于PSK绑定器的字典攻击。指定的PSK认证并不是一个基于强口令的已认证的密钥交换,即使使用了Diffie-Hellman密钥建立方法。

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握手消息相同。

              TLS 1.3草案中文版(翻译进度:第4章)

                  +  标明是在以前标注的消息中发送的值得注意的扩展

                  *  表示可选的或者依赖一定条件的消息/扩展,它们不总是发送

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

                  {} 表示消息使用从一个[sender]_handshake_traffic_secret导出的密钥保护

                  []表示消息使用从[sender]_application_traffic_secret_N导出的密钥保护

                                                                   图4: 一个0往返握手的消息流

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

        1. 这类数据没有前向安全,它只使用了从被提供的PSK中导出的密钥进行加密。

        2. 不能保证在多条连接之间不会有重放。为普通的TLS 1.3.1-RTT数据提供抗重放的保护方法是使用server的随机数据,但0-RTT不依赖于ServerHello,因此只能得到更弱的保护。如果数据与TLS client认证或在应用协议里一起验证,这一点尤其重要。这个警告适用于任何使用early_exporter_master_secret的情况。

3. 陈述语言

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

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 向量

        一个向量(一维数组)是一个同类数据元素的流。向量的大小可能在编写文档时指定或留待运行时确定。在任何情况下,向量的长度都是指字节数而非元素数。定义一个新类型T'(是一个固定长度的类型T的向量)的语法是:
        T T'[n];
        这里,T'在数据流中占据了n个字节,而n是多个类型T的大小。向量的长度并不包含在编码流中。
        在下面的例子中,Datum被定义为协议不能理解的3个连续字节, 而Data是三个连续的Datum,共占据9个字节。
        opaque Datum[3];      /* 三个未知字节 */
        Datum Data[9];        /* 3个连续的3字节向量 */
        变长向量的定义是通过指定一个合法长度的子范围来实现(使用符号<floor..ceiling>)。当这些被编码时,在字节流中实际长度是在向量的内容之前。这个长度会以一个数字的形式出现,并消耗足够多的字节以便表示向量的最大长度(ceiling)。一个实际长度是0的变长向量会被当做一个空向量。
        T T'<floor..ceiling>;

        在下面的例子中会强制要求一个向量必须包含300-400个字节的opaque类型数据,它不能为空。实际长度域占用两个字节,一个uint16,这足以代表数值400(见3.4节)。相似地,更长的向量可以描述多达800字节的数据,或400个uint16类型的元素,这个向量可以为空。它的编码会包含一个两字节的实际长度域设置在向量前。一个编码向量的长度必须是单个元素长度的偶数倍(例如,一个17字节长的uint16类型的向量是非法的)。
      opaque mandatory<300..400>;  /*长度域是2字节,不能为空 */
      uint16 longer<0..800>;  /* 0-400 16-bit 无符号整数 */

3.4 数字

        基本数字数据类型是一个无符号字节(uint8)。所有更大的数字数据类型都被组织成固定长度的字节序列并如4.1节中所描述的那样被串联,且同样是无符号的。下面是预定义的数字类型。
        uint8 uint16[2];
        uint8 uint24[3];
        uint8 uint32[4];
        uint8 uint64[8];
        本篇规范中的所有的数值都是以网络字节序(大端)存储;一个uint32类型的十六进制字节01 02 03 04等于十进制数16909060。

3.5 枚举

        另外一种少见的数据类型是枚举。每个定义都是一个不同的类型。只有相同类型的枚举能被指定或比较。一个枚举的每个元素必须被指定一个值,就像下面的例子所表明的。既然枚举类型的元素并不是有序的,它们能够被以任意顺序指定任意独一的值。
        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;     /* 过度指定, 合法 */
        Color color = blue;           /* 正确, 类型隐藏 */
        指定给枚举的名字不需要是唯一的。数字的值可以描述一个相同名称枚举应用的范围。这个值包括取值范围中最小和最大的包含值,由两个周期字符分隔开。这是主要用于保留空间区域。
        enum { sad(0), meh(1..254), happy(255) } Mood;

3.6构造类型

      为了方便,结构体类型可以由原始类型构建。每个规范声明了一个新的、独特的类型。定义的语法很像C语言:
      struct {
          T1 f1;
          T2 f2;
          ...
          Tn fn;
      } T;
    固定长度和变长向量域被允许使用标准的向量语法。下面的变量示例中的结构体V1和V2表明了这一点。
        结构体内的域可以用类型的名字来描述,使用类似于枚举的语法。例如,T.f2引用了前面定义的结构的第二个域。

3.7 常量

      域和常量可以使用"="来赋予一个固定的值,像下面那样:
      struct {
          T1 f1 = 8;  /* T.f1 必须一直是8 */
          T2 f2;
      } T;

3.8 变量

    考虑到从环境中获得的一些信息是变化的,定义的结构体可以有一些变量。选择符必须是一个定义了结构体中变量取值范围的枚举类型。Select语句的case条件指定了变量域的类型和一个可选的域标签。通过这个机制变量可以在运行时被选择,这个机制不能被陈述语言所描述。
     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记录层,在那里他们被封装到一个或多个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;             /*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对;

    - 一个”支持的组”(4.2.7节)扩展——表明client支持的(EC)DHE组,和一个”key_share”(4.2.8)扩展——包含了一些货全部这些组所共享的(EC)DHE密钥。

   - 一个"signature_algorithms"(4.2.3节)扩展——表明client能接受的签名算法。
   - 一个"pre_shared_key" (4.2.11节)扩展——包含了一个client知晓的对称密钥,和一个"psk_key_exchange_modes" (4.2.9扩展)——表明与PSK一起使用的密钥交换模式。
      如果server没有选择PSK,则这些选项的前3个是完全正交的:server独立地选择一个密码族,一个(EC)DHE组和用于建立密钥的密钥共享,一个签名算法/证书对用于认证自己和client。如果接收到的"supported_groups"和server所支持的组之间没有重叠,则server必须用一个"handshake_failure" 或一个"insufficient_security"警报中止握手。

      如果server选择了一个PSK,则它也必须从client的"psk_key_exchange_modes"扩展(目前是仅有PSK或带(EC)DHE)所表明的集合中选择一个密钥建立模式。需要注意的是如果PSK可以不带(EC)DHE就能被使用,则"supported_groups"参数不重叠不一定是致命的,就像以前段落中讨论过的非PSK场景一样。

         如果server选择了一个(EC)DHE组并且client没有在初始ClientHello中提供一个兼容的"key_share"扩展,server必须响应一个HelloRetryRequest(4.1.4节)消息。

         如果server成功地选择了参数且没有发送HelloRetryRequest,表明ServerHello中所选的参数如下:

        -  如果PSK被使用,则server会发送一个”pre_shared_key”扩展表明所选的密钥。

        -  如果PSK没有被使用,则(EC)DHE和基于证书的认证会一直被使用。

        -  当使用(EC)DHE时,server将会提供一个” key_share”扩展。

        -  当通过证书进行验证时,server将会发送Certificate(4.4.2节)和CertificateVerify(4.4.3节)消息。在本文定义的TLS1.3中,一个PSK或一个证书会一直被使用,但不会同时使用。将来的文档可能会定义怎样同时使用它们。

      如果server不能协商一个可支持的参数集合(例如,client和server的参数集合没有重叠),它必须用一个"handshake_failure" 或一个"insufficient_security"警报(见第6章)中止握手。

4.1.2 Client Hello

    当一个client第一次连接一个server时,它需要发送ClientHello作为它的第一个消息。当server用HelloRetryRequest来响应client的ClientHello时,client也应当发送ClientHello。这种条件下,client必须发送相同的ClientHello(无修改),除非:

    -  如果HelloRetryRequest带有一个”key_share”扩展,则将共享的列表用一个列表取代,这个列表包含单个来自表明的组中的KeyShareEntry。

    -  如果存在”early_data”扩展则将其移除。早期数据(earlydata)不允许在HelloRetryRequest之后出现。

    -  如果HelloRetryRequest中提供了一个”cookie”扩展,则需要包含一个;

    - 如果需要重新计算"obfuscated_ticket_age" 和绑定值,并(可选地)删除任何不兼容server展示的密码族的PSK,则更新"pre_shared_key"扩展。

    -  选择性地增加,删除或更改”padding”扩展[RFC 7685]的长度。

    由于TLS 1.3禁止重协商,如果一个server已经协商完成了TLS 1.3并且在任何其它时间收到了一个ClientHello,它必须用一个"unexpected_message"警报中止连接。

    如果一个server用以前版本的TLS建立了一个TLS连接并在重协商时接收了一个TLS1.3的ClientHello,它必须保持以前的协议版本。特别是,它不能协商TLS 1.3。

    这个消息的结构:

      uint16 ProtocolVersion;

      opaque Random[32];

      uint8 CipherSuite[2];    /* 密码族选择器 */

      struct {

          ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */

          Random random;

          opaquelegacy_session_id<0..32>;

          CipherSuitecipher_suites<2..2^16-2>;

          opaquelegacy_compression_methods<1..2^8-1>;

          Extension extensions<8..2^16-1>;

      } ClientHello;

        legacy_version 在以前版本的TLS里,这个域被用于版本协商和表示由client所支持的最高版本号。经验表明很多server没有适当地实现版本协商,导致“版本容忍”会使server拒绝了版本号高于它支持的其它可接受的ClientHello。在TLS 1.3中,client在"supported_versions" 扩展(4.2.1节)中表明的它的版本偏好,且legacy_version域必须被设置为0x0303,这是TLS1.2的版本号。(关于后向兼容的细节见附录D)
        random  由一个安全随机数生成器产生的32字节随机数。额外信息见附录C。
        legacy_session_id  TLS 1.3之前的TLS版本支持一个"会话恢复"特性,在这个版本中此特性已经与预共享密钥合并了(见2.2节)。一个client需要将这个域设置为由一个TLS 1.3之前版本的server所设置的缓存的 session ID。在兼容模式下(见附录D.4)此域必须是非空,所以一个不能提供TLS 1.3之前版本会话的client必须产生一个32字节的新值。这个值不必是随机的但应该是不可预测的以避免实现上固定为一个具体的值(也被称为僵化)。否则,它必须被设置为一个0长度向量(例如,一个0字节长度域)。