TLS 1.3 Handshake Protocol (上)

握手协议用于协商链接的安全参数。握手消息被提供给 TLS 记录层,在记录层它们被封装到一个或多个 TLSPlaintext 或 TLSCiphertext 中,它们按照当前活动链接状态进行处理和传输。html

enum {
          client_hello(1),
          server_hello(2),
          new_session_ticket(4),
          end_of_early_data(5),
          encrypted_extensions(8),
          certificate(11),
          certificate_request(13),
          certificate_verify(15),
          finished(20),
          key_update(24),
          message_hash(254),
          (255)
      } HandshakeType;

      struct {
          HandshakeType msg_type;    /* handshake type */
          uint24 length;             /* remaining bytes in message */
          select (Handshake.msg_type) {
              case client_hello:          ClientHello;
              case server_hello:          ServerHello;
              case end_of_early_data:     EndOfEarlyData;
              case encrypted_extensions:  EncryptedExtensions;
              case certificate_request:   CertificateRequest;
              case certificate:           Certificate;
              case certificate_verify:    CertificateVerify;
              case finished:              Finished;
              case new_session_ticket:    NewSessionTicket;
              case key_update:            KeyUpdate;
          };
      } Handshake;
复制代码

协议消息必须按照必定顺序发送(顺序见下文)。若是对端发现收到的握手消息顺序不对,必须使用 “unexpected_message” alert 消息来停止握手。git

另外 IANA 分配了新的握手消息类型,见第 11 章github

一. Key Exchange Messages

密钥交换消息用于确保 Client 和 Server 的安全性和创建用于保护握手和数据的通讯密钥的安全性。算法

1. Cryptographic Negotiation

在 TLS 协议中,密钥协商的过程当中,Client 在 ClientHello 中能够提供如下 4 种 options。数据库

  • Client 支持的加密套件列表。密码套件里面中能体现出 Client 支持的 AEAD 算法或者 HKDF 哈希对。
  • “supported_groups” 的扩展 和 "key_share" 扩展。“supported_groups” 这个扩展代表了 Client 支持的 (EC)DHE groups,"key_share" 扩展代表了 Client 是否包含了一些或者所有的(EC)DHE共享。
  • "signature_algorithms" 签名算法和 "signature_algorithms_cert" 签名证书算法的扩展。"signature_algorithms" 这个扩展展现了 Client 能够支持了签名算法有哪些。"signature_algorithms_cert" 这个扩展展现了具体证书的签名算法。
  • "pre_shared_key" 预共享密钥和 "psk_key_exchange_modes" 扩展。预共享密钥扩展包含了 Client 能够识别的对称密钥标识。"psk_key_exchange_modes" 扩展代表了可能能够和 psk 一块儿使用的密钥交换模式。

若是 Server 不选择 PSK,那么上面 4 个 option 中的前 3 个是正交的, Server 独立的选择一个加密套件,独立的选择一个 (EC)DHE 组,独立的选择一个用于创建链接的密钥共享,独立的选择一个签名算法/证书对用于给 Client 验证 Server 。若是 Server 收到的 "supported_groups" 中没有 Server 能支持的算法,那么就必须返回 "handshake_failure" 或者 "insufficient_security" 的 alert 消息。缓存

若是 Server 选择了 PSK,它必须从 Client 的 "psk_key_exchange_modes" 扩展消息中选择一个密钥创建模式。这个时候 PSK 和 (EC)DHE 是分开的。在 PSK 和 (EC)DHE 分开的基础上,即便,"supported_groups" 中不存在 Server 和 Client 相同的算法,也不会终止握手。安全

若是 Server 选择了 (EC)DHE 组,而且 Client 在 ClientHello 中没有提供合适的 "key_share" 扩展, Server 必须用 HelloRetryRequest 消息做为回应。cookie

若是 Server 成功的选择了参数,也就不须要 HelloRetryRequest 消息了。 Server 将发送 ServerHello 消息,它包含如下几个参数:网络

  • 若是正在使用 PSK, Server 将发送 "pre_shared_key" 扩展,里面包含了选择的密钥。
  • 若是没有使用 PSK,选择的 (EC)DHE, Server 将会提供一个 "key_share" 扩展。一般,若是 PSK 没有使用,就会使用 (EC)DHE 和基于证书的认证。
  • 当经过证书进行认证的时候, Server 会发送 Certificate 和 CertificateVerify 消息。在 TLS 1.3 的官方规定中,PSK 和 证书一般被用到,可是不是一块儿使用,将来的文档可能会定义如何同时使用它们。

若是 Server 不能协商出可支持的参数集合,即在 Client 和 Server 各自支持的参数集合中没有重叠,那么 Server 必须发送 "handshake_failure" 或者 "insufficient_security" 消息来停止握手。session

2. Client Hello

当一个 Client 第一次链接一个 Server 时,它须要在发送第一条 TLS 消息的时候,发送 ClientHello 消息。当 Server 发送 HelloRetryRequest 消息的时候,Client 收到了之后也须要回应一条 ClientHello 消息。在这种状况下,Client 必须发送相同的无修改的 ClientHello 消息,除非如下几种状况:

  • 若是 HelloRetryRequest 消息中包含了 "key_share" 扩展,则将共享列表用包含了单个来自代表的组中的 KeyShareEntry 代替。
  • 若是存在 “early_data” 扩展则将其移除。 “early_data” 不容许出如今 HelloRetryRequest 以后。
  • 若是 HelloRetryRequest 中包含了 cookie 扩展,则须要包含一个。
  • 若是从新计算了 "obfuscated_ticket_age" 和绑定值,同时(可选地)删除了任何不兼容 Server 展现的密码族的 PSK,则更新 "pre_shared_key" 扩展。
  • 选择性地增长,删除或更改 ”padding” 扩展RFC 7685的长度。
  • 可能被容许的一些其余的修改。例如将来指定的一些扩展定义和 HelloRetryRequest 。

因为 TLS 1.3 严禁重协商,若是 Server 已经完成了 TLS 1.3 的协商了,在将来某一时刻又收到了 ClientHello ,Server 不该该理会这条消息,必须当即断开链接,并发送 "unexpected_message" alert 消息。

若是一个 Server 创建了一个 TLS 之前版本的 TLS 链接,并在重协商的时候收到了 TLS 1.3 的 ClientHello ,这个时候,Server 必须继续保持以前的版本,严禁协商 TLS 1.3 。

ClientHello 消息的结构是

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 所能支持的 TLS 最高版本号。经验代表,不少 Server 并无正确的实现版本协商,致使了 "version intolerance" —— Sever 拒绝了一些原本能够支持的 ClientHello 消息,只由于这些消息的版本号高于 Server 能支持的版本号。在 TLS 1.3 中,Client 在 "supported_versions" 扩展中代表了它的版本。而且 legacy_version 字段必须设置成 0x0303,这是 TLS 1.2 的版本号。在 TLS 1.3 中的 ClientHello 消息中的 legacy_version 都设置成 0x0303,supported_versions 扩展设置成 0x0304。更加详细的信息见附录 D。

  • random:
    由一个安全随机数生成器产生的32字节随机数。额外信息见附录 C。

  • legacy_session_id:
    TLS 1.3 版本以前的版本支持会话恢复的特性。在 TLS 1.3 的这个版本中,这一特性已经和预共享密钥 PSK 合并了。若是 Client 有 TLS 1.3 版本以前的 Server 设置的缓存 Session ID,那么这个字段要填上这个 ID 值。在兼容模式下,这个值必须是非空的,因此一个 Client 要是不能提供 TLS 1.3 版本以前的 Session 的话,就必须生成一个新的 32 字节的值。这个值不要求是随机值,但必须是一个不可预测的值,防止实现上固定成了一个固定的值了。不然,这个字段必须被设置成一个长度为 0 的向量。(例如,一个0字节长度域)

  • cipher_suites:
    这个列表是 Client 所支持对称加密选项的列表,特别是记录保护算法(包括密钥长度) 和 HKDF 一块儿使用的 hash 算法。以 Client 的偏好降序排列。若是列表包含的密码套件是 Server 不能识别的或者是不能支持的,或者是但愿使用的,Server 必须忽略这些密码套件,照常处理剩下来的密码套件。若是 Client 尝试创建 PSK 密钥,则它应该至少包含一个与 PSK 相关的哈希加密套件。

  • legacy_compression_methods:
    TLS 1.3 以前的 TLS 版本支持压缩,在这个字段中发送支持的压缩方法列表。对于每一个 ClientHello,该向量必须包含一个设置为 0 的一个字节,它对应着 TLS 以前版本中的 null 压缩方法。若是 TLS 1.3 中的 ClientHello 中这个字段包含有值,Server 必须当即发送 “illegal_parameter” alert 消息停止握手。注意,TLS 1.3 Server 可能接收到 TLS 1.2 或者以前更老版本的 ClientHellos,其中包含了其余压缩方法。若是正在协商这些以前的版本,那么必须遵循 TLS 以前版本的规定。

  • extensions:
    Client 经过在扩展字段中发送数据,向 Server 请求扩展功能。“Extension” 遵循格式定义。在 TLS 1.3 中,使用肯定的扩展项是强制的。由于功能被移动到了扩展中以保持和以前 TLS 版本的 ClientHello 消息的兼容性。Server 必须忽略不能识别的 extensions。

全部版本的 TLS 都容许可选的带上 compression_methods 这个扩展字段。TLS 1.3 ClientHello 消息一般包含扩展消息(至少包含 “supported_versions”,不然这条消息会被解读成 TLS 1.2 的 ClientHello 消息)然而,TLS 1.3 Server 也有可能收到以前 TLS 版本发来的不带扩展字段的 ClientHello 消息。扩展是否存在,能够经过检测 ClientHello 结尾的 compression_methods 字段内是否有字节来肯定。请注意,这种检测可选数据的方法与具备可变长度字段的普通 TLS 方法不一样,可是在扩展被定义以前,这种方法能够用来作兼容。TLS 1.3 Server 须要首先执行此项检查,而且仅当存在 “supported_versions” 扩展时才尝试协商 TLS 1.3。若是协商的是 TLS 1.3 以前的版本,Server 必须作 2 项检查:legacy_compression_methods 字段后面是否还有数据;有效的 extensions block 后没有数据跟随。若是上面这 2 项检查都不经过,须要当即发送 "decode_error" alert 消息停止握手。

若是 Client 经过扩展请求额外功能,可是这个功能 Server 并不提供,则 Client 能够停止握手。

发送 ClientHello 消息后,Client 等待 ServerHello 或者 HelloRetryRequest 消息。若是 early data 在使用中,Client 在等待下一条握手消息期间,能够先发送 early Application Data。

3. Server Hello

若是 Server 和 Client 能够在 ClientHello 消息中协商出一套双方均可以接受的握手参数的话,那么 Server 会发送 Server Hello 消息回应 ClientHello 消息。

消息的结构体是:

struct {
          ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
          Random random;
          opaque legacy_session_id_echo<0..32>;
          CipherSuite cipher_suite;
          uint8 legacy_compression_method = 0;
          Extension extensions<6..2^16-1>;
      } ServerHello;
复制代码
  • legacy_version:
    在 TLS 1.3 以前的版本,这个字段被用来版本协商和标识创建链接时候双方选择的版本号。不幸的是,一些中间件在给这个字段赋予新值的时候可能会失败。在 TLS 1.3 中,Server 用 "supported_versions" 扩展字段来标识它支持的版本,legacy_version 字段必须设置为 0x0303(这个值表明的 TLS 1.2)。(有关向后兼容性的详细信息,请参阅附录D.)

  • random:
    由安全随机数生成器生成的随机 32 字节。若是协商的是 TLS 1.1 或者 TLS 1.2 ,那么最后 8 字节必须被重写,其他的 24 字节必须是随机的。这个结构由 Server 生成而且必须独立于 ClientHello.random。

  • legacy_session_id_echo:
    Client 的 legacy_session_id 字段的内容。请注意,即便 Server 决定再也不恢复 TLS 1.3 以前的会话,Client 的 legacy_session_id 字段缓存的是 TLS 1.3 以前的值,这个时候 legacy_session_id_echo 字段也会被 echoed。Client 收到的 legacy_session_id_echo 值和它在 ClientHello 中发送的值不匹配的时候,必须当即用 "illegal_parameter" alert 消息停止握手。

  • cipher_suite:
    Server 从 ClientHello 中的 cipher_suites 列表中选择的一个加密套件。Client 若是接收到并无提供的密码套件,此时应该当即用 "illegal_parameter" alert 消息停止握手。

  • legacy_compression_method:
    必须有 0 值的单一字节。

  • extensions:
    扩展列表。ServerHello 中必须仅仅只能包括创建加密上下文和协商协议版本所需的扩展。全部 TLS 1.3 的 ServerHello 消息必须包含 "supported_versions" 扩展。当前的 ServerHello 消息还另外包含 "pre_shared_key" 扩展或者 "key_share" 扩展,或者两个扩展都有(当使用 PSK 和 (EC)DHE 创建链接的时候)。其余的扩展会在 EncryptedExtensions 消息中分别发送。

出于向后兼容中间件的缘由,HelloRetryRequest 消息和 ServerHello 消息采用相同的结构体,但须要随机设置 HelloRetryRequest 的 SHA-256 特定值:

CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
     C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
复制代码

当收到 server_hello 消息之后,实现必须首先检查这个随机值是否是和上面这个值匹配。若是和上面这个值是一致的,再继续处理。

TLS 1.3 具备降级保护机制,这种机制是经过嵌入在 Server 的随机值实现的。TLS 1.3 Server 协商 TLS 1.2 或者更老的版本,为了响应 ClientHello ,ServerHello 消息中必须在最后 8 个字节中填入特定的随机值。

若是协商的 TLS 1.2 ,TLS 1.3 Server 必须把 ServerHello 中的 Random 字段的最后 8 字节设置为:

44 4F 57 4E 47 52 44 01
D  O  W  N  G  R  D
复制代码

若是协商的 TLS 1.1 或者更老的版本,TLS 1.3 Server 和 TLS 1.2 Server 必须把 ServerHello 中的 Random 字段的最后 8 字节的值改成:

44 4F 57 4E 47 52 44 00
D  O  W  N  G  R  D
复制代码

TLS 1.3 Client 接收到 TLS 1.2 或者 TLS 更老的版本的 ServerHello 消息之后,必需要检查 ServerHello 中的 Random 字段的最后 8 字节不等于上面 2 个值才对。TLS 1.2 的 Client 也须要检查最后 8 个字节,若是协商的是 TLS 1.1 或者是更老的版本,那么 Random 值也不该该等于上面第二个值。若是都没有匹配上,那么 Client 必须用 "illegal_parameter" alert 消息停止握手。这种机制提供了有限的保护措施,抵御降级攻击。经过 Finished exchange ,能超越保护机制的保护范围:由于在 TLS 1.2 或更低的版本上,ServerKeyExchange 消息包含 2 个随机值的签名。只要使用了临时的加密方式,攻击者就不可能在不被发现的状况下,修改随机值。因此对于静态的 RSA,是没法提供降级攻击的保护。

请注意,上面这些改动在 RFC5246 中说明的,实际上许多 TLS 1.2 的 Client 和 Server 都没有按照上面的规定来实践。

若是 Client 在从新协商 TLS 1.2 或者更老的版本的时候,协商过程当中收到了 TLS 1.3 的 ServerHello,这个时候 Client 必须当即发送 “protocol_version” alert 停止握手。请注意,一旦 TLS 1.3 协商完成,就没法再从新协商了,由于 TLS 1.3 严禁从新协商

4. Hello Retry Request

若是在 Client 发来的 ClientHello 消息中可以找到一组能够相互支持的参数,可是 Client 又不能为接下来的握手提供足够的信息,这个时候 Server 就须要发送 HelloRetryRequest 消息来响应 ClientHello 消息。在上一节中,谈到 HelloRetryRequest 和 ServerHello 消息是有相同的数据结构,legacy_version, legacy_session_id_echo, cipher_suite, legacy_compression_method 这些字段的含义也是同样的。为了讨论的方便,下文中,咱们讨论 HelloRetryRequest 消息都当作不一样的消息来对待。

Server 的扩展集中必须包含 "supported_versions"。另外,它还须要包含最小的扩展集,能让 Client 生成正确的 ClientHello 对。相比 ServerHello 而言,HelloRetryRequest 只能包含任何在第一次 ClientHello 中出现过的扩展,除了可选的 "cookie" 之外。

Client 接收到 HelloRetryRequest 消息之后,必需要先校验 legacy_version, legacy_session_id_echo, cipher_suite, legacy_compression_method 这四个参数。先从 “supported_versions” 开始决定和 Server 创建链接的版本,而后再处理扩展。若是 HelloRetryRequest 不会致使 ClientHello 的任何更改,Client 必须用 “illegal_parameter” alert 消息停止握手。若是 Client 在一个链接中收到了第 2 个 HelloRetryRequest 消息( ClientHello 自己就是响应 HelloRetryRequest 的),那么必须用 “unexpected_message” alert 消息停止握手。

不然,Client 必须处理 HelloRetryRequest 中全部的扩展,而且发送第二个更新的 ClientHello。在本规范中定义的 HelloRetryRequest 扩展名是:

  • supported_versions
  • cookie
  • key_share

Client 在接收到本身并无提供的密码套件的时候必须当即停止握手。Server 必须确保在接收到合法而且更新过的 ClientHello 时,它们在协商相同的密码套件(若是 Server 把选择密码套件做为协商的第一步,那么这一步会自动发送)。Client 收到 ServerHello 后必须检查 ServerHello 中提供的密码套件是否与 HelloRetryRequest 中的密码套件相同,不然将以 “illegal_parameter” alert 消息停止握手。

此外,Client 在其更新的 ClientHello 中,Client 不能提供任何与所选密码套件之外的预共享密钥(与哈希相关联的)。这容许 Client 避免在第二个 ClientHello 中计算多个散列的部分哈希转录。

在 HelloRetryRequest 的 "support_versions" 扩展中的 selected_version 字段的值必须被保留在 ServerHello 中,若是这个值变了,Client 必须用 “illegal_parameter” alert 消息停止握手。

二. Extensions

许多 TLS 的消息都包含 tag-length-value 编码的扩展数据结构:

struct {
        ExtensionType extension_type;
        opaque extension_data<0..2^16-1>;
    } Extension;

    enum {
        server_name(0),                             /* RFC 6066 */
        max_fragment_length(1),                     /* RFC 6066 */
        status_request(5),                          /* RFC 6066 */
        supported_groups(10),                       /* RFC 8422, 7919 */
        signature_algorithms(13),                   /* RFC 8446 */
        use_srtp(14),                               /* RFC 5764 */
        heartbeat(15),                              /* RFC 6520 */
        application_layer_protocol_negotiation(16), /* RFC 7301 */
        signed_certificate_timestamp(18),           /* RFC 6962 */
        client_certificate_type(19),                /* RFC 7250 */
        server_certificate_type(20),                /* RFC 7250 */
        padding(21),                                /* RFC 7685 */
        pre_shared_key(41),                         /* RFC 8446 */
        early_data(42),                             /* RFC 8446 */
        supported_versions(43),                     /* RFC 8446 */
        cookie(44),                                 /* RFC 8446 */
        psk_key_exchange_modes(45),                 /* RFC 8446 */
        certificate_authorities(47),                /* RFC 8446 */
        oid_filters(48),                            /* RFC 8446 */
        post_handshake_auth(49),                    /* RFC 8446 */
        signature_algorithms_cert(50),              /* RFC 8446 */
        key_share(51),                              /* RFC 8446 */
        (65535)
    } ExtensionType;
复制代码

这里:

  • "extension_type" 标识特定的扩展状态。
  • "extension_data" 包含特定于该特定扩展类型的信息。

全部的扩展类型由 IANA 维护,具体的见附录。

扩展一般以请求/响应方式构建,虽然有些扩展只是一些标识,并不会有任何响应。Client 在 ClientHello 中发送其扩展请求,Server 在 ServerHello, EncryptedExtensions, HelloRetryRequest,和 Certificate 消息中发送对应的扩展响应。Server 在 CertificateRequest 消息中发送扩展请求,Client 可能回应 Certificate 消息。Server 也有可能不请自来的在 NewSessionTicket 消息中直接发送扩展请求,Client 能够不用直接响应这条消息。

若是远端没有发送相应的扩展请求,除了 HelloRetryRequest 消息中的 “cookie” 扩展之外,实现方不得发送扩展响应。在接收到这样的扩展之后,端点必须用 "unsupported_extension" alert 消息停止握手。

下表给出了可能出现的消息的扩展名,使用如下表示法:CH (ClientHello), SH (ServerHello), EE (EncryptedExtensions), CT (Certificate), CR (CertificateRequest), NST (NewSessionTicket), 和 HRR (HelloRetryRequest) 。当实现方在接收到它能识别的消息,而且并无为出现的消息作规定的话,它必须用 "illegal_parameter" alert 消息停止握手。

+--------------------------------------------------+-------------+
   | 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" 必须是 ClientHello 的最后一个扩展,其余的扩展间的顺序能够是任意的。("pre_shared_key" 能够出如今 ServerHello 中扩展块中的任何位置)。不能存在多个同一个类型的扩展。

在 TLS 1.3 中,与 TLS 1.2 不一样,即便是恢复 PSK 模式,每次握手都须要协商扩展。然而,0-RTT 的参数是在前一次握手中协商的。若是参数不匹配,须要拒绝 0-RTT。

在 TLS 1.3 中新特性和老特性之间存在微妙的交互,这可能会使得总体安全性显著降低。下面是设计新扩展的时候须要考虑的因素:

  • Server 不一样意扩展的某些状况是错误的(例如握手不能继续),有些状况只是简单的不支持特定的功能。通常来讲,前一种状况应该用错误的 alert,后一种状况应该用 Server 的扩展响应中的一个字段来处理。

  • 扩展应尽量设计为防止能经过人为操纵握手信息,从而强制使用(或不使用)特定功能的攻击。无论这个功能是否会引发安全问题,这个原则都必须遵照。一般,包含在 Finished 消息的哈希输入中的扩展字段是不用担忧的,可是在握手阶段,扩展试图改变了发送消息的含义,这种状况须要特别当心。设计者和实现者应该意识到,在握手完成身份认证以前,攻击者均可以修改消息,插入、删除或者替换扩展。

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” 对于 Client 来讲,Client 用它来标明它所能支持的 TLS 版本,对于 Server 来讲,Server 用它来标明正在使用的 TLS 版本。这个扩展包含一个按照优先顺序排列的,能支持的版本列表。最优先支持的版本放在第一个。TLS 1.3 这个版本的规范是必须在发送 ClientHello 消息时候带上这个扩展,扩展中包含全部准备协商的 TLS 版本。(对于这个规范来讲,这意味着最低是 0x0304,可是若是要协商 TLS 的之前的版本,那么这个扩展必需要带上)

若是不存在 “supported_versions” 扩展,知足 TLS 1.3 而且也兼容 TLS 1.2 规范的 Server 须要协商 TLS 1.2 或者以前的版本,即便 ClientHello.legacy_version 是 0x0304 或者更高的版本。Server 在接收到 ClientHello 中的 legacy_version 的值是 0x0304 或者更高的版本的时候,Server 可能须要马上停止握手。

若是 ClientHello 中存在 “supported_versions” 扩展,Server 禁止使用 ClientHello.legacy_version 的值做为版本协商的值,只能使用 "supported_versions" 决定 Client 的偏好。Server 必须只选择该扩展中存在的 TLS 版本,而且必需要忽略任何未知版本。注意,若是通讯的一方支持稀疏范围,这种机制使得能够在 TLS 1.2 以前的版本间进行协商。选择支持 TLS 的之前版本的 TLS 1.3 的实现应支持 TLS 1.2。Server 应准备好接收包含此扩展名的 ClientHellos 消息,但不要在 viersions 列表中包含 0x0304。

Server 在协商 TLS 1.3 以前的版本,必需要设置 ServerHello.version,不能发送 "supported_versions" 扩展。Server 在协商 TLS 1.3 版本时候,必须发送 "supported_versions" 扩展做为响应,而且扩展中要包含选择的 TLS 1.3 版本号(0x0304)。还要设置 ServerHello.legacy_version 为 0x0303(TLS 1.2)。Client 必须在处理 ServerHello 以前检查此扩展(尽管须要先解析 ServerHello 以便读取扩展名)。若是 "supported_versions" 扩展存在,Client 必须忽略 ServerHello.legacy_version 的值,只使用 "supported_versions" 中的值肯定选择的版本。若是 ServerHello 中的 "supported_versions" 扩展包含了 Client 没有提供的版本,或者是包含了 TLS 1.3 以前的版本(原本是协商 TLS 1.3 的,却又包含了 TLS 1.3 以前的版本),Client 必须当即发送 "illegal_parameter" alert 消息停止握手。

2. Cookie

struct {
          opaque cookie<1..2^16-1>;
      } Cookie;
复制代码

Cookies 有 2 大主要目的:

  • 容许 Server 强制 Client 展现网络地址的可达性(所以提供了一个保护 Dos 的度量方法),这主要是面向无链接的传输(参考 RFC 6347 中的例子)

  • 容许 Server 卸载状态。从而容许 Server 在向 Client 发送 HelloRetryRequest 消息的时候,不存储任何状态。为了实现这一点,能够经过 Server 把 ClientHello 的哈希存储在 HelloRetryRequest 的 cookie 中(用一些合适的完整性算法保护)。

当发送 HelloRetryRequest 消息时,Server 能够向 Client 提供 “cookie” 扩展(这是常规中的一个例外,常规约定是:只能是可能被发送的扩展才能够出如今 ClientHello 中)。当发送新的 ClientHello 消息时,Client 必须将 HelloRetryRequest 中收到的扩展的内容复制到新 ClientHello 中的 “cookie” 扩展中。Client 不得在后续链接中使用首次 ClientHello 中的 Cookie。

当 Server 在无状态运行的时候,在第一个和第二个 ClientHello 之间可能会收到不受保护的 change_cipher_spec 消息。因为 Server 没有存储任何状态,它会表现出像到达的第一条消息同样。无状态的 Server 必须忽略这些记录。

3. Signature Algorithms

TLS 1.3 提供了 2 种扩展来标明在数字签名中可能用到的签名算法。"signature_algorithms_cert" 扩展提供了证书里面的签名算法。"signature_algorithms" 扩展(TLS 1.2 中就有这个扩展了),提供了 CertificateVerify 消息中的签名算法。证书中的密钥必需要根据所用的签名算法匹配合适的类型。对于 RSA 密钥和 PSS 签名,这是一个特殊问题,描述以下:若是没有 "signature_algorithms_cert" 扩展,则 "signature_algorithms" 扩展一样适用于证书中的签名。Client 想要 Server 经过证书来认证本身,则必须发送 "signature_algorithms" 扩展。若是 Server 正在进行证书的认证,这个时候 Client 又没有提供 "signature_algorithms"扩展,Server 必须 发送 "missing_extension" 消息停止握手。

加入 "signature_algorithms_cert" 扩展的意图是为了让已经支持了证书的不一样算法集的实现方,能明确的标识他们的能力。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 的值是 Client 想要验证的单一签名算法。这些值按照优先级降序排列。请注意,签名算法以任意长度的消息做为输入,而不是摘要做为输入。传统上用于摘要的算法应该在 TLS 中定义,首先使用指定的哈希算法对输入进行哈希计算,而后再进行常规处理。上面列出的代码具备如下含义:

  • RSASSA-PKCS1-v1_5 algorithms:
    表示使用 RSASSA-PKCS1-v1_5 RFC8017 和定义在 SHS 中对应的哈希算法的签名算法。这些值仅指,出如今证书中又没有被定义用于签名 TLS 握手消息的签名。这些值会出如今 "signature_algorithms" 和 "signature_algorithms_cert" 中,由于须要向后兼容 TLS 1.2 。

  • ECDSA algorithms:
    表示签名算法使用 ECDSA,对应的曲线在 ANSI X9.62 ECDSA 和 FIPS 186-4 DSS 中定义了,对应的哈希算法在 SHS 中定义了。签名被表示为 DER 编码的 ECDSA-Sig-Value 结构。

  • RSASSA-PSS RSAE algorithms:
    表示使用带有掩码生成函数 1 的 RSASSA-PSS 签名算法。在掩码生成函数中使用的摘要和被签名的摘要都是在 SHS 中定义的相应的哈希算法。盐的长度必须等于摘要算法输出的长度。若是公钥在 X.509 证书中,则必须使用 rsaEncryption OID RFC5280

  • EdDSA algorithms:
    表示使用定义在 RFC 8032 中的 EdDSA 算法或者其后续改进算法。请注意,这些相应算法是 "PureEdDSA" 算法,而不是 "prehash" 变种算法。

  • RSASSA-PSS PSS algorithms:
    表示使用带有掩码生成函数 1 的 RSASSA-PSS RFC 8017 签名算法。在掩码生成函数中使用的摘要和被签名的摘要都是在 SHS 中定义的相应的哈希算法。盐的长度必须等于摘要算法的长度。若是公钥在 X.509 证书中,则必须使用 RSASSA-PSS OID RFC5756。当它被用在证书签名中,算法参数必须是 DER 编码。若是存在相应的公钥参数,则签名中的参数必须与公钥中的参数相同。

  • Legacy algorithms:
    表示使用正在被废弃中的算法,由于这些算法有已知的缺点。特别是 SHA-1 配合上文提到的 RSASSA-PKCS1-v1_5 和 ECDSA 算法一块儿使用。这些值仅指,出如今证书中又没有被定义用于签名 TLS 握手消息的签名。这些值会出如今 "signature_algorithms" 和 "signature_algorithms_cert" 中,由于须要向后兼容 TLS 1.2 。终端不该该协商这些算法,但容许这样作只是为了向后兼容。提供这些值的 Client 必须把他们列在最低优先级的位置上(在 SignatureSchemeList 中的全部其余算法以后列出)。TLS 1.3 Server 毫不能提供 SHA-1 签名证书,除非没有它就没法生成有效的证书链。

自签名证书上的签名或信任锚的证书不能经过校验,由于它们开始了一个认证路径(见 RFC 5280)。开始认证路径的证书可使用 "signature_algorithms" 扩展中不建议支持的签名算法。

请注意,TLS 1.2 中这个扩展的定义和 TLS 1.3 的定义不一样。在协商 TLS 1.2 版本时,愿意协商 TLS 1.2 的 TLS 1.3 实现必须符合 RFC5246 的要求,尤为是:

  • TLS 1.2 ClientHellos 能够忽略此扩展。

  • 在 TLS 1.2 中,扩展包含 hash/signature pairs。这些 pairs 被编码为两个八位字节,因此已经分配空间的 SignatureScheme 值与 TLS 1.2 的编码对齐。 一些传统的 pairs 保留未分配。这些算法已被 TLS 1.3 弃用。它们不得在任何实现中被提供或被协商。 特别是,不得使用 MD5 [SLOTH] 、SHA-224 和 DSA。

  • ECDSA 签名方案和 TLS 1.2 的 hash/signature pairs 一致。然而,旧的语义并无限制签名曲线。若是 TLS 1.2 被协商了,实现方必须准备接受在 "supported_groups" 扩展中使用任何曲线的签名。

  • 即便协商了 TLS 1.2,支持了 RSASSA-PSS(在TLS 1.3中是强制性的)的实现方也准备接受该方案的签名。在TLS 1.2中,RSASSA-PSS 与 RSA 密码套件一块儿使用。

4. Certificate Authorities

"certificate_authorities" 扩展用于表示终端支持的 CA, 而且接收的端点应该使用它来指导证书的选择。

"certificate_authorities" 扩展的主体包含了一个 CertificateAuthoritiesExtension 结构:

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

      struct {
          DistinguishedName authorities<3..2^16-1>;
      } CertificateAuthoritiesExtension;
复制代码
  • authorities:
    可接受证书颁发机构的一个可分辨名字 X501 的列表 ,这个列表是以 DER X690 编码格式表示的。这些可分辨的名称为,信任锚或从属的 CA 指定所需的可分辨的名称。所以,可使用此消息描述已知的信任锚以及所需的受权空间。

Client 可能会在 ClientHello 消息中发送 "certificate_authorities" 扩展,Server 可能会在 CertificateRequest 消息中发送 "certificate_authorities" 扩展。

"trusted_ca_keys" 扩展和 "certificate_authorities" 扩展有相同的目的,可是更加复杂。"trusted_ca_keys" 扩展不能在 TLS 1.3 中使用,可是它在 TLS 1.3 以前的版本中,可能出如今 Client 的 ClientHello 消息中。

5. OID Filters

"oid_filters" 扩展容许 Server 提供一组 OID/value 对,用来匹配 Client 的证书。若是 Server 想要发送这个扩展,有且仅有在 CertificateRequest 消息中才能发送。

struct {
          opaque certificate_extension_oid<1..2^8-1>;
          opaque certificate_extension_values<0..2^16-1>;
      } OIDFilter;

      struct {
          OIDFilter filters<0..2^16-1>;
      } OIDFilterExtension;
复制代码
  • filters:
    一个有容许值的证书扩展 OID RFC 5280 列表,以 DER 编码 X690 格式表示。一些证书扩展 OID 容许多个值(例如,Extended Key Usage)。若是 Server 包含非空的 filters 列表,则响应中包含的 Client 证书必须包含 Client 识别的全部指定的扩展 OID。对于 Client 识别的每一个扩展 OID,全部指定的值必须存在于 Client 证书中(可是证书也能够具备其余值)。然而,Client 必须忽略并跳过任何没法识别的证书扩展 OID。若是 Client 忽略了一些所需的证书扩展 OID 并提供了不知足请求的证书。Server 能够自行决定是继续与没有身份认证的 Client 保持链接,仍是用 "unsupported_certificate" alert 消息停止握手。任何给定的 OID 都不能在 filters 列表中出现屡次。

PKIX RFC 定义了各类证书扩展 OID 及其对应的值类型。根据类型,匹配的证书扩展值不必定是按位相等的。指望 TLS 实现将依靠它们的 PKI 库,使用证书扩展 OID 来作证书的选择。

本文档定义了 RFC5280 中定义的两个标准证书扩展的匹配规则:

  • 当请求中声明的全部 Key Usage 位也一样在 Key Usage 证书扩展声明了,那么证书中的 Key Usage 扩展匹配了请求。

  • 当请求中全部的密钥 OIDs 在 Extended Key Usage 证书扩展中也存在,那么证书中的 Extended Key Usage 匹配了请求。特殊的 anyExtendedKeyUsage OID 必定不能在请求中使用。

单独的规范能够为其余证书扩展的规则定义匹配规则。

6. Post-Handshake Client Authentication

"post_handshake_auth" 扩展用于代表 Client 愿意握手后再认证。Server 不能向没有提供此扩展的 Client 发送握手后再认证的 CertificateRequest 消息。Server 不能发送此扩展。

struct {} PostHandshakeAuth;
复制代码

"post_handshake_auth" 扩展名中的 "extension_data" 字段为零长度。

7. Supported Groups

当 Client 发送 "supported_groups" 扩展的时候,这个扩展代表了 Client 支持的用于密钥交换的命名组。按照优先级从高到低。

请注意:在 TLS 1.3 以前的版本中,这个扩展原来叫 "elliptic_curves",而且只包含椭圆曲线组。具体请参考 RFC8422RFC7919。这个扩展一样能够用来协商 ECDSA 曲线。签名算法如今独立协商了。

这个扩展中的 "extension_data" 字段包含一个 "NamedGroupList" 值:

enum {

          /* Elliptic Curve Groups (ECDHE) */
          secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
          x25519(0x001D), x448(0x001E),

          /* Finite Field Groups (DHE) */
          ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),
          ffdhe6144(0x0103), ffdhe8192(0x0104),

          /* Reserved Code Points */
          ffdhe_private_use(0x01FC..0x01FF),
          ecdhe_private_use(0xFE00..0xFEFF),
          (0xFFFF)
      } NamedGroup;

      struct {
          NamedGroup named_group_list<2..2^16-1>;
      } NamedGroupList;
复制代码
  • Elliptic Curve Groups (ECDHE):
    表示支持在 FIPS 186-4 [DSS] 或者 [RFC7748] 中定义的对应命名的曲线。0xFE00 到 0xFEFF 的值保留使用[RFC8126]

  • Finite Field Groups (DHE):
    表示支持相应的有限域组,相关定义能够参考 [RFC7919]。0x01FC 到 0x01FF 的值保留使用。

named_group_list 中的项根据发送者的优先级排序(最好是优先选择的)。

在 TLS 1.3 中,Server 容许向 Client 发送 "supported_groups" 扩展。Client 不能在成功完成握手以前,在 "supported_groups" 中找到的任何信息采起行动,但可使用从成功完成的握手中得到的信息来更改在后续链接中的 "key_share" 扩展中使用的组。若是 Server 中有一个组,它更想接受 "key_share" 扩展中的那些值,但仍然愿意接受 ClientHello 消息,这时候它应该发送 "supported_groups" 来更新 Client 的偏好视图。不管 Client 是否支持它,这个扩展名都应该包含 Server 支持的全部组。

8. Key Share

"key_share" 扩展包含终端的加密参数。

Client 可能会发送空的 client_shares 向量,以额外的往返代价,向 Server 请求选择的组。

struct {
          NamedGroup group;
          opaque key_exchange<1..2^16-1>;
      } KeyShareEntry;
复制代码
  • group:
    要交换的密钥的命名组。

  • key_exchange:
    密钥交换信息。这个字段的内容由特定的组和相应的定义肯定。有限域的 Diffie-Hellman 参数在下面会描述。椭圆曲线 Diffie-Hellman 参数也会下面会描述。

在 ClientHello 消息中,"key_share" 扩展中的 "extension_data" 包含 KeyShareClientHello 值:

struct {
          KeyShareEntry client_shares<0..2^16-1>;
      } KeyShareClientHello;
复制代码
  • client_shares:
    按照 Client 偏好降序顺序提供的 KeyShareEntry 值列表。

若是 Client 正在请求 HelloRetryRequest, 则这个向量能够为空。每一个 KeyShareEntry 值必须对应一个在 "supported_groups" 扩展中提供的组,而且出现的顺序必须相同。然而,当优先级排名第一的组合是新的,而且不足以提供预生成 key shares 的时候,那么值能够是 "supported_groups" 扩展的非连续子集,而且能够省略最优选的组,这种状况是可能会出现的。

Client 能够提供与其提供的 support groups 同样多数量的 KeyShareEntry 的值。每一个值都表明了一组密钥交换参数。例如,Client 可能会为多个椭圆曲线或者多个 FFDHE 组提供 shares。每一个 KeyShareEntry 中的 key_exchange 值必须独立生成。Client 不能为相同的 group 提供多个 KeyShareEntry 值。Client 不能为,没有出如今 Client 的 "supported_group" 扩展中列出的 group 提供任何 KeyShareEntry 值。Server 会检查这些规则,若是违反了规则,当即发送 "illegal_parameter" alert 消息停止握手。

在 HelloRetryRequest 消息中,"key_share" 扩展中的 "extension_data" 字段包含 KeyShareHelloRetryRequest 值。

struct {
          NamedGroup selected_group;
      } KeyShareHelloRetryRequest;
复制代码
  • selected_group:
    Server 打算协商的相互支持而且正在请求重试 ClientHello / KeyShare 的 group。

在 HelloRetryRequest 消息中收到此扩展后,Client 必需要验证 2 点。第一点,selected_group 必须在原始的 ClientHello 中的 "supported_groups" 中出现过。第二点,selected_group 没有在原始的 ClientHello 中的 "key_share" 中出现过。若是上面 2 点检查都失败了,那么 Client 必须经过 "illegal_parameter" alert 消息来停止握手。不然,在发送新的 ClientHello 时,Client 必须将原始的 "key_share" 扩展替换为仅包含触发 HelloRetryRequest 的 selected_group 字段中指示的组,这个组中只包含新的 KeyShareEntry。

在 ServerHello 消息中,"key_share" 扩展中的 "extension_data" 字段包含 KeyShareServerHello 值。

struct {
          KeyShareEntry server_share;
      } KeyShareServerHello;
复制代码
  • server_share:
    与 Client 共享的位于同一组的单个 KeyShareEntry 值。

若是使用 (EC)DHE 密钥创建连接,Server 在 ServerHello 中只提供了一个 KeyShareEntry。这个值必须与,Server 为了协商密钥交换在 Client 提供的 KeyShareEntry 值中选择的值,在同一组中。Server 不能为 Client 的 "supported_groups" 扩展中指定的任何 group 发送 KeyShareEntry 值。Server 也不能在使用 "psk_ke" PskKeyExchangeMode 时候发送 KeyShareEntry 值。若是使用 (EC)DHE 创建连接,Client 收到了包含在 "key_share" 扩展中的 HelloRetryRequest 消息,Client 必须验证在 ServerHello 中选择的 NameGroup 与 HelloRetryRequest 中是否相同。若是不相同,Client 必须当即发送 "illegal_parameter" alert 消息停止握手。

(1) Diffie-Hellman Parameters

Client 和 Server 二者的 Diffie-Hellman [DH76] 参数都编码在 KeyShareEntry 中的 KeyShare 数据结构中 opaque 类型的 key_exchange 字段中。opaque 类型的值包含指定 group 的 Diffie-Hellman 公钥(Y = g^X mod p),是用大端整数编码的。这个值大小为 p 字节,若是字节不够,须要在其左边添加 0 。

请注意:对于给定的 Diffie-Hellman 组,填充会致使全部的公钥具备相同的长度。

对端必需要相互验证对方的公钥,确保 1 < Y < p-1。此检查确保远程对端正常运行,也使得本地系统不会强制进入进入更小的 subgroup。

(2) ECDHE Parameters

Client 和 Server 二者的 ECDHE 参数都编码在 KeyShareEntry 中的 KeyShare 数据结构中 opaque 类型的 key_exchange 字段中。

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

struct {
          uint8 legacy_form = 4;
          opaque X[coordinate_length];
          opaque Y[coordinate_length];
      } UncompressedPointRepresentation;
复制代码

X 和 Y 分别是网络字节顺序中 X 和 Y 值的二进制表示。因为没有内部长度标记,因此每一个数字占用曲线参数隐含的 8 位字节数。对于 P-256,这意味着 X 和 Y 中的每个占用 32 个八位字节,若是须要,则在左侧填充零。对于 P-384,它们分别占用 48 个八位字节,对于 P-521,它们各占用 66 个八位字节。

对于曲线 secp256r1, secp384r1, 和 secp521r1,对端必须验证对方的的公钥 Q,以保证这个点是椭圆曲线上有效的点。合适的验证方法定义在 [ECDSA] 中或者 [KEYAGREEMENT]。这个处理包括了 3 步。第一步:验证 Q 不是无穷大的点 (O)。第二步,验证 Q = (x, y) 中的两个整数 x,y 有正确的间隔。第三步,验证 (x, y) 是椭圆曲线方程的正确的解。对于这些曲线,实现方不须要再验证正确的 subgroup 中的成员身份。

对于 X25519 和 X448 来讲,公共值的内容是 [RFC7748] 中定义的相应函数的字节串输入和输出,X25519 的是 32 个字节, X448 的是 56 个字节。

请注意:TLS 1.3 以前的版本容许 point format 协商,TLS 1.3 移除了这个功能,以利于每一个曲线的单独 point format

9. Pre-Shared Key Exchange Modes

为了使用 PSK,Client 还必须发送一个 "psk_key_exchange_modes" 扩展。这个扩展语意是 Client 仅支持使用具备这些模式的 PSK。这就限制了在这个 ClientHello 中提供的 PSK 的使用,也限制了 Server 经过 NewSessionTicket 提供的 PSK 的使用。

若是 Client 提供了 "pre_shared_key" 扩展,那么它必须也要提供 "psk_key_exchange_modes" 扩展。若是 Client 发送不带 "psk_key_exchange_modes" 扩展名的 "pre_shared_key",Server 必须当即停止握手。Server 不能选择一个 Client 没有列出的密钥交换模式。此扩展还限制了与 PSK 恢复使用的模式。Server 也不能发送与建议的 modes 不兼容的 NewSessionTicket。不过若是 Server 必定要这样作,影响的只是 Client 在尝试恢复会话的时候会失败。

Server 不能发送 "psk_key_exchange_modes" 扩展:

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

      struct {
          PskKeyExchangeMode ke_modes<1..255>;
      } PskKeyExchangeModes;
复制代码
  • psk_ke:
    仅 PSK 密钥创建。在这种模式下,Server 不能提供 "key_share" 值。

  • psk_dhe_ke:
    PSK 和 (EC)DHE 创建。在这种模式下,Client 和 Server 必须提供 "key_share" 值。

将来分配的任何值都必需要能保证传输的协议消息能够明确的标识 Server 选择的模式。目前 Server 选择的值由 ServerHello 中存在的 "key_share" 表示。

10. Early Data Indication

当使用 PSK 而且 PSK 容许使用 early_data 的时候,Client 能够在其第一个消息中发送应用数据。若是 Client 选择这么作,则必须发送 "pre_shared_key" 和 "early_data" 扩展。

Early Data Indication 扩展中的 "extension_data" 字段包含了一个 EarlyDataIndication 值。

struct {} Empty;

      struct {
          select (Handshake.msg_type) {
              case new_session_ticket:   uint32 max_early_data_size;
              case client_hello:         Empty;
              case encrypted_extensions: Empty;
          };
      } EarlyDataIndication;
复制代码

有关 max_early_data_size 字段的使用请看 New Session Ticket Message 章节。

0-RTT 数据(版本,对称加密套件,应用层协议协商协议[RFC7301],等等)的参数与使用中的 PSK 参数相关。对于外部配置的 PSK,关联值是由密钥提供的。对于经过 NewSessionTicket 消息创建的 PSK,关联值是在创建 PSK 链接时协商的值。PSK 用来加密 early data 必须是 Client 在 "pre_shared_key" 扩展中列出的第一个 PSK。

对于经过 NewSessionTicket 提供的 PSK,Server 必须验证所选 PSK 标识中的 ticket age(从 PskIdentity.obfuscated_ticket_age 取 2^32 模中减去 ticket_age_add)距离 ticket 发出的时间是否有一个很小的公差。若是相差的时间不少,那么 Server 应该继续握手,可是要拒绝 0-RTT,而且还要假定这条 ClientHello 是新的,也不能采起任何其余措施。

在第一次 flight 中发送的 0-RTT 消息与其余 flight (握手和应用数据)中发送的相同类型的消息具备相同(加密)的内容类型,但受到不一样密钥的保护。若是 Server 已经接收了 early data,Client 在收到 Server 的 Finished 消息之后,Client 则会发送 EndOfEarlyData 消息表示密钥更改。这条消息将会使用 0-RTT 的 traffic 密钥进行加密。

Server 接收 "early_data" 扩展必须如下面三种方式之一操做:

  • 忽略 "early_data" 扩展,并返回常规的 1-RTT 响应。Server 尝试经过用握手中的流量密钥(traffic key)解密收到的记录,并忽略掉 early data。丢弃解密失败的记录(取决于配置的 max_early_data_size)。一旦一个记录被解密成功,它将会被 Server 看作 Client 第二次 flight 的开始而且 Server 会把它当作普通的 1-RTT 来处理。

  • 经过回应 HelloRetryRequest 来请求 Client 发送另一个 ClientHello。Client 不能在这个 ClientHello 中包含 "early_data" 扩展。Server 经过跳过具备外部内容类型的 "application_data"(说明他们被加密了) 的全部记录来忽略 early data(一样取决于配置的 max_early_data_size)。

  • 在 EncryptedExtensions 中返回本身的 "early_data" 扩展,代表它准备处理 early data。Server 不可能只接受 early data 消息中的一部分。即便 Server 发送了一条接收 early data 的消息,可是实际上 early data 可能在 Server 生成这条消息的时候已经在 flight 了。

为了接受 early data,Server 必须已经接受了 PSK 密码套件而且选择了 Client 的 "pre_shared_key" 扩展中提供的第一个密钥。此外,Server 还须要验证如下的值和选择的 PSK 关联值同样:

  • TLS 版本号
  • 选择的密码套件
  • 选择的 ALPN 协议,若是选择了的话

这些要求是使用相关 PSK 执行 1-RTT 握手所需的超集。对于外部创建的 PSK,关联值是与密钥一块儿提供的值。对于经过 NewSessionTicket 消息创建的 PSK,关联值是在链接中协商的值,在这期间 ticket 被创建了。

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

若是任何检查失败了,Server 不得在响应中附带扩展,而且必须使用上面列出的前两种机制中的一个,丢弃全部 first-flight 数据(所以回落到 1-RTT 或者 2-RTT)。若是 Client 尝试 0-RTT 握手但 Server 拒绝了它,则 Server 一般不会有 0-RTT 记录保护密钥,而必须使用试用解密(使用 1-RTT 握手密钥或者经过在有 HelloRetryRequest 消息的状况下查找明文 ClientHello)找到第一个非 0-RTT 消息。

若是 Server 选择接受 early_data 扩展,那么在处理 early data 记录的时候,Server 必须遵照用相同的标准(指定的相同错误处理要求)来处理全部记录。具体来讲,若是 Server 没法解密已经接受的 "early_data" 扩展中的记录,则它必须发送 "bad_record_mac" alert 消息停止握手。

若是 Server 拒绝 "early_data" 扩展,则 Client 应用程序能够选择在握手完成后从新发送先前在 early data 中发送的应用数据。请注意,early data 的自动重传可能会致使关于链接状态的误判。例如,当协商链接从用于 early data 的协议中选择不一样的 ALPN 协议时,应用程序可能须要构造不一样的消息。一样,若是 early data 假定包含有关链接状态的任何内容,则在握手完成后可能会错误地发送这些内容。

TLS 的实现不该该自动从新发送 early data;应用程序能够很好的决定什么时候重传。除非协商链接选择相同的 ALPN 协议,不然 TLS 实现毫不能自动从新发送 early data。

11. Pre-Shared Key Extension

"pre_shared_key" 扩展用来协商标识的,这个标识是与 PSK 密钥相关联的给定握手所使用的预共享密钥的标识。

这个扩展中的 "extension_data" 字段包含一个 PreSharedKeyExtension 值:

struct {
          opaque identity<1..2^16-1>;
          uint32 obfuscated_ticket_age;
      } PskIdentity;

      opaque PskBinderEntry<32..255>;

      struct {
          PskIdentity identities<7..2^16-1>;
          PskBinderEntry binders<33..2^16-1>;
      } OfferedPsks;

      struct {
          select (Handshake.msg_type) {
              case client_hello: OfferedPsks;
              case server_hello: uint16 selected_identity;
          };
      } PreSharedKeyExtension;
复制代码
  • identity:
    key 的标签。例如,一个 ticket 或者是一个外部创建的预共享密钥的标签。

  • obfuscated_ticket_age:
    age of the key 的混淆版本。这一章节描述了经过 NewSessionTicket 消息创建,如何为标识(identities)生成这个值。对于外部创建的标识(identities),应该使用 0 的 obfuscated_ticket_age,而且 Server 也必须忽略这个值。

  • identities:
    Client 愿意和 Server 协商的 identities 列表。若是和 "early_data" 一块儿发送,第一个标识被用来标识 0-RTT 的。

  • binders:
    一系列的 HMAC 值。和 identities 列表中的每个值都一一对应,而且顺序一致。

  • selected_identity:
    Server 选择的标识,这个标识是以 Client 列表中标识表示为基于 0 的索引。

每个 PSK 都和单个哈希算法相关联。对于经过 ticket 创建的 PSK,当 ticket 在链接中被创建,这时候用的哈希算法是 KDF 哈希算法。对于外部创建的 PSK,当 PSK 创建的时候,哈希算法必须设置,若是没有设置,默认算法是 SHA-256。Server 必须确保它选择的是兼容的 PSK (若是有的话) 和密钥套件。

在 TLS 1.3 以前的版本中,Server Name Identification (SNI) 的值旨在与会话相关联。Server 被强制要求,与会话关联的 SNI 值要和恢复握手中指定的 SNI 值相互匹配。然而事实上,实现方和他们使用的两个提供的 SNI 值是不一致的,这样就会致使 Client 须要执行一致性的要求。在 TLS 1.3 版本中,SNI 的值始终在恢复握手中被明确的指出,而且 Server 不须要将 SNI 值和 ticket 相关联。不过 Client 须要将 SNI 和 PSK 一块儿存储,以知足 [4.6.1 章节] 的要求。

实现者请注意:会话恢复是 PSK 最主要的用途,实现 PSK/密钥套件 匹配要求的最直接的方法是先协商密码套件,而后再排除任何不兼容的 PSK。任何未知的 PSK (例如:不在 PSK 数据库中,或者用未知的 key 进行编码的)都必须忽略。若是找不到可接受的 PSK,若是可能,Server 应该执行 non-PSK 握手。若是向后兼容性很重要,Client 提供的,外部创建的 PSK 应该影响密码套件的选择。

在接受PSK密钥创建以前,Server 必须先验证相应的 binder 值(见 [4.2.11.2 节])。若是这个值不存在或者未验证,则 Server 必须当即停止握手。Server 不该该尝试去验证多个 binder,而应该选择单个 PSK 而且仅验证对应于该 PSK 的 binder。见 Appendix E.6[8.2 节] 描述了针对这个要求的安全性解释。为了接受 PSK 密钥创建链接,Server 发送 "pre_shared_key" 扩展,标明它所选择的 identity。

Client 必须验证 Server 的 selected_identity 是否在 Client 提供的范围以内。Server 选择的加密套件标明了与 PSK 关联的哈希算法,若是 ClientHello "psk_key_exchange_modes" 有须要,Server 还应该发送 "key_share" 扩展。若是这些值不一致,Client 必须当即用 "illegal_parameter" alert 消息停止握手。

若是 Server 提供了 "early_data" 扩展,Client 必须验证 Server 的 selected_identity 是否为 0。若是返回任何其余值,Client 必须使用 "illegal_parameter" alert 消息停止握手。

"pre_shared_key" 扩展必须是 ClientHello 中的最后一个扩展(这有利于下面的描述的实现)。Server 必须检查它是最后一个扩展,不然用 "illegal_parameter" alert 消息停止握手。

(1) Ticket Age

从 Client 的角度来看,ticket 的时间指的是,收到 NewSessionTicket 消息开始到当前时刻的这段时间。Client 决不能使用时间大于 ticket 本身标明的 "ticket_lifetime" 这个时间的 ticket。每一个 PskIdentity 中的 "obfuscated_ticket_age" 字段都必须包含 ticket 时间的混淆版本,混淆方法是用 ticket 时间(毫秒为单位)加上 "ticket_age_add" 字段,最后对 2^32 取模。除非这个 ticket 被重用了,不然这个混淆就能够防止一些相关联链接的被动观察者。注意,NewSessionTicket 消息中的 "ticket_lifetime" 字段是秒为单位,可是 "obfuscated_ticket_age" 是毫秒为单位。由于 ticke lifetime 限制为一周,32 位就足够去表示任何合理的时间,即便是以毫秒为单位也能够表示。

(2) PSK Binder

PSK binder 的值造成了 2 种绑定关系,一种是 PSK 和当前握手的绑定,另一种是 PSK 产生之后(若是是经过 NewSessionTicket 消息)的握手和当前握手的绑定。每个在 binder 列表中的条目都会根据有一部分 ClientHello 的哈希副本计算 HMAC,最终 HMAC 会包含 PreSharedKeyExtension.identities 字段。也就是说,HMAC 包含全部的 ClientHello,可是不包含 binder list 。若是存在正确长度的 binders,消息的长度字段(包括总长度,扩展块的长度和 "pre_shared_key" 扩展的长度)都被设置。

PskBinderEntry 的计算方法和 Finished 消息同样。可是 BaseKey 是派生的 binder_key,派生方式是经过提供的相应的 PSK 的密钥派生出来的。

若是握手包括 HelloRetryRequest 消息,则初始的 ClientHello 和 HelloRetryRequest 随着新的 ClientHello 一块儿被包含在副本中。例如,若是 Client 发送 ClientHello,则其 binder 将经过如下方式计算:

Transcript-Hash(Truncate(ClientHello1))
复制代码

Truncate() 函数的做用是把 ClientHello 中的 binders list 移除。

若是 Server 响应了 HelloRetryRequest,那么 Client 会发送 ClientHello2,它的 binder 会经过如下方式计算:

Transcript-Hash(ClientHello1,
                      HelloRetryRequest,
                      Truncate(ClientHello2))
复制代码

完整的 ClientHello1/ClientHello2 都会包含在其余的握手哈希计算中。请注意,在第一次发送中,Truncate(ClientHello1) 是直接计算哈希的,可是在第二次发送中,ClientHello1 计算哈希,而且还会再注入一条 "message_hash" 消息。

(3) Processing Order

Client 被容许流式的发送 0-RTT 数据,直到它收到 Server 的 Finished 消息。Client 收到 Finished 消息之后,须要在握手的末尾,发送 EndOfEarlyData 消息。为了防止死锁,当 Server 接收 "early_data" 消息的时候,Server 必须当即处理 Client 的 ClientHello 消息,而后当即回应 ServerHello,而不是等待收到 Client 的 EndOfEarlyData 消息之后再发送 ServerHello。

三. Server Parameters

Server 接下来的 2 条消息,EncryptedExtensions 和 CertificateRequest 消息,包含来自 Server 的消息,这个 Server 肯定了握手的其他部分。这些消息是加密的,经过从 server_handshake_traffic_secret 中派生的密钥加密的。

1. Encrypted Extensions

在全部的握手中,Server 必须在 ServerHello 消息以后当即发送 EncryptedExtensions 消息。这是在从 server_handshake_traffic_secret 派生的密钥下加密的第一条消息。

EncryptedExtensions 消息包含应该被保护的扩展。即,任何不须要创建加密上下文但不与各个证书相互关联的扩展。Client 必须检查 EncryptedExtensions 消息中是否存在任何禁止的扩展,若是有发现禁止的扩展,必须当即用 "illegal_parameter" alert 消息停止握手。

Structure of this message:

      struct {
          Extension extensions<0..2^16-1>;
      } EncryptedExtensions;
复制代码
  • extensions:
    扩展列表。

2. Certificate Request

使用证书进行身份验证的 Server 能够选择性的向 Client 请求证书,这条请求消息(若是发送了)要跟在 EncryptedExtensions 消息后面。

消息的结构体:

struct {
          opaque certificate_request_context<0..2^8-1>;
          Extension extensions<2..2^16-1>;
      } CertificateRequest;
复制代码
  • certificate_request_context:
    一个不透明的字符串,这个字符串用来标识证书请求,并在 Client 的 Certificate 消息中回显。certificate_request_context 必须在本次链接中必须是惟一的(从而防止 Client 的 CertificateVerify 重放攻击)。这个字段通常状况下都是 0 长度,除非用于 [4.6.2] 中描述的握手后身份验证交换。当请求握手后身份验证之后,Server 应该发送不可预测的上下文给 Client (例如,用随机数生成),这样是为了防止攻击者破解。攻击者能够预先计算有效的 CertificateVerify 消息,从而获取临时的 Client 私钥的权限。

  • extensions:
    一组描述正在请求的证书须要的参数扩展集。"signature_algorithms" 扩展必须是特定的,若是其余的扩展被这个消息所定义,那么其余扩展也可能可选的被包含进来。Client 必须忽略不能识别的扩展。

在 TLS 1.3 以前的版本中,CertificateRequest 消息携带了签名算法列表和 Server 可接受的证书受权列表。在 TLS 1.3 中,签名算法列表能够经过 "signature_algorithms" 和可选的 "signature_algorithms_cert" 扩展来表示。然后者证书受权列表能够经过发送 "certificate_authorities" 扩展来表示。

经过 PSK 进行验证的 Server 不能在主握手中发送 CertificateRequest 消息,不过它们可能能够在握手后身份验证中发送 CertificateRequest 消息,前提是 Client 已经发送了 "post_handshake_auth" 扩展名。


Reference:

RFC 8446

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: halfrost.com/TLS_1.3_Han…

相关文章
相关标签/搜索