Discover more content...

Discover more content...

Enter some keywords in the search box above, we will do our best to offer you relevant results.

Results

We're sorry!

Sorry about that!

We couldn't find any results for your search. Please try again with another keywords.

TLS 1.3 Record Protocol

TLS 记录协议接收要传输的消息,将数据分段为可管理的块,保护记录并传输结果。收到的数据经过验证,解密,重新组装,然后交付给更上层的协议。

TLS 记录允许同一层记录层上复用多个更高层的协议。本文档指定了 4 种内容类型:handshake,application_data,alert 和 change_cipher_spec。change_cipher_spec 记录仅用于兼容性目的。

实现方可能会在发送或接收第一个 ClientHello 消息之后,和,在接收到对等方的 Finished 消息之前的任何时间接收由单字节值 0x01 组成的未加密的类型 change_cipher_spec 的记录,如果接收到了这种记录,则必须简单地丢弃它而不进行进一步处理。请注意,此记录可能出现在握手中,这时候实现方是期望保护记录的,因此有必要在尝试对记录进行去除保护之前检测此情况。接收到任何其他 change_cipher_spec 值或接收到受保护的 change_cipher_spec 记录的实现方必须使用 "unexpected_message" alert 消息中止握手。如果实现方检测到在第一个 ClientHello 消息之前或在对等方的 Finished 消息之后收到的 change_cipher_spec 记录,则必须将其视为意外记录类型(尽管无状态的 Server 可能无法将这些情况与允许的情况区分开)

除非协商了某些扩展,否则实现方绝不能发送本文档中未定义的记录类型。如果 TLS 实现方收到意外的记录类型,它必须使用 "unexpected_message" alert 消息终止连接。新记录内容类型值由 IANA 在 TLS ContentType 注册表中分配,具体见第 11 节。

一. Record Layer

记录层将信息块分段为 TLSPlaintext 记录,TLSPlaintext 中包含 2^14 字节或更少字节块的数据。根据底层 ContentType 的不同,消息边界的处理方式也不同。任何未来的新增的内容类型必须指定适当的规则。请注意,这些规则比 TLS 1.2 中强制执行的规则更加严格。

握手消息可以合并为单个 TLSPlaintext 记录,或者在几个记录中分段,前提是:

  • 握手消息不得与其他记录类型交错。也就是说,如果握手消息被分成两个或多个记录,则它们之间不能有任何其他记录。

  • 握手消息绝不能跨越密钥更改。实现方必须验证密钥更改之前的所有消息是否与记录边界对齐; 如果没有,那么他们必须用 "unexpected_message" alert 消息终止连接。因为 ClientHello,EndOfEarlyData,ServerHello,Finished 和 KeyUpdate 消息可以在密钥更改之前立即发生,所以实现方必须将这些消息与记录边界对齐。

实现方绝不能发送握手类型的零长度片段,即使这些片段包含填充。

Alert 消息禁止在记录之间进行分段,并且多条 alert 消息不得合并为单个 TLSPlaintext 记录。换句话说,具有 alert 类型的记录必须只包含一条消息。

应用数据消息包含对 TLS 不透明的数据。应用数据消息始终受到保护。可以发送应用数据的零长度片段,因为它们可能作为流量分析对策使用。应用数据片段可以拆分为多个记录,也可以合并为一个记录。

      enum {
          invalid(0),
          change_cipher_spec(20),
          alert(21),
          handshake(22),
          application_data(23),
          (255)
      } ContentType;

      struct {
          ContentType type;
          ProtocolVersion legacy_record_version;
          uint16 length;
          opaque fragment[TLSPlaintext.length];
      } TLSPlaintext;
  • type:
    用于处理封闭片段的更高级协议。

  • legacy_record_version:
    对于除初始 ClientHello 之外的 TLS 1.3 实现生成的所有记录(即,在 HelloRetryRequest 之后未生成的记录),必须将其设置为 0x0303,其中出于兼容性目的,它也可以是0x0301。该字段已弃用,必须忽略它。在某些情况下,以前版本的 TLS 将在此字段中使用其他值。

  • length:
    TLSPlaintext.fragment 的长度(以字节为单位)。长度不得超过 2 ^ 14 字节。接收超过此长度的记录的端点必须使用 "record_overflow" alert 消息终止连接。

  • fragment:
    正在传输的数据。此字段的值是透明的,它并被视为一个独立的块,由类型字段指定的更高级别协议处理。

本文档描述了使用版本 0x0304 的 TLS 1.3。此版本的值是历史的,源自对 TLS 1.0 使用 0x0301 和对 SSL 3.0 使用 0x0300。为了最大化向后兼容性,包含初始 ClientHello 的记录应该具有版本 0x0301(对应 TLS 1.0),包含第二个 ClientHello 或 ServerHello 的记录必须具有版本 0x0303(对应 TLS 1.2)。在协商 TLS 的早期版本时,端点需要遵循附录 D 中提供的过程和要求。

当尚未使用记录保护时,TLSPlaintext 结构是直接写入传输线路中的。一旦记录保护开始,TLSPlaintext 记录将受到保护并按照下节部分描述的那样进行发送。请注意,应用数据记录不得写入未受保护的连接中。(有关详细信息,请参阅第 2 节)

二. Record Payload Protection

记录保护功能将 TLSPlaintext 结构转换为 TLSCiphertext 结构。去除保护功能和保护功能互为逆过程。在 TLS 1.3 中,与先前版本的 TLS 相反,所有密码都是“具有关联数据的认证加密”(AEAD)[RFC5116]。 AEAD 功能提供统一的加密和认证操作,将明文转换为经过认证的密文,然后再返回。每个加密记录由一个明文标题后跟一个加密的主体组成,该主体本身包含一个类型和可选的填充。

      struct {
          opaque content[TLSPlaintext.length];
          ContentType type;
          uint8 zeros[length_of_padding];
      } TLSInnerPlaintext;

      struct {
          ContentType opaque_type = application_data; /* 23 */
          ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
          uint16 length;
          opaque encrypted_record[TLSCiphertext.length];
      } TLSCiphertext;
  • content:
    TLSPlaintext.fragment 值,包含握手或警报消息的字节编码,或要发送的应用数据的原始字节。

  • type:
    TLSPlaintext.type 值,包含记录的内容类型。

  • zeros:
    在类型字段之后的明文中可以出现任意长度的零值字节。只要总数保持在记录大小限制范围内,这个字段为发件人提供了按所选的量去填充任何 TLS 记录的机会。更多详细信息,请参见[第 5.4 节]

  • opaque_type:
    TLSCiphertext 记录外部的 opaque_type 字段始终设置为值23(application_data),以便与解析以前版本的 TLS 的中间件向外兼容。解密后,在 TLSInnerPlaintext.type 中找到记录的实际内容类型。

  • legacy_record_version:
    legacy_record_version 字段始终为 0x0303。TLS 1.3 TLSCiphertexts 在协商 TLS 1.3 之后才生成,因此没有历史兼容性问题可能会收到其他值。请注意,握手协议(包括 ClientHello 和 ServerHello 消息)会对协议版本进行身份验证,因此该值是多余的。

  • length:
    TLSCiphertext.encrypted_record 的长度(以字节为单位),它是内容和填充的长度之和,加上内部内容类型的长度加上 AEAD 算法添加的任何扩展。长度不得超过 2 ^ 14 + 256 字节。接收超过此长度的记录的端点必须使用 "record_overflow" alert 消息终止连接。

  • encrypted_record:
    AEAD 加密形式的序列化 TLSInnerPlaintext 结构。

AEAD 算法将单个密钥,随机数,明文和要包含在认证检查中的“附加数据”作为输入,如[RFC5116的第2.1节]所述。key 是 client_write_key 或 server_write_key,nonce 是从序列号和 client_write_iv 或 server_write_iv 中派生出来的(参见[第5.3节]),附加数据的输入是记录头,例如:

      additional_data = TLSCiphertext.opaque_type ||
                        TLSCiphertext.legacy_record_version ||
                        TLSCiphertext.length

作为输入 AEAD 算法的明文是编码后的 TLSInnerPlaintext 结构。第7.3节定义了流量密钥的派生。

AEAD 输出包括 AEAD 加密操作的密文输出。由于包含 TLSInnerPlaintext.type 和发送方提供的任何填充,明文的长度大于相应的 TLSPlaintext.length。AEAD 输出的长度通常大于明文,但是其中一部分也会随着 AEAD 算法的变化而变化。

由于密码可能包含填充,因此开销量可能随着明文的不同长度而变化。

      AEADEncrypted =
          AEAD-Encrypt(write_key, nonce, additional_data, plaintext)

TLSCiphertext 的 encrypted_record 字段设置为 AEADEncrypted。

为了解密和验证,密码会把密钥,随机数,附加数据和 AEADEncrypted 值作为输入。输出的是明文或表示解密失败的错误。这里没有单独的完整性检查。

      plaintext of encrypted_record =
          AEAD-Decrypt(peer_write_key, nonce,
                       additional_data, AEADEncrypted)

如果解密失败,接收方必须使用 "bad_record_mac" alert 消息终止连接。

TLS 1.3 中使用的 AEAD 算法不得产生大于 255 个八位字节的扩展。从对端的 TLSCiphertext.length 接收记录,如果 TLSCiphertext.length 大于 2 ^ 14 + 256 个八位字节,则必须用 "record_overflow" 消息终止连接。这个限制源自于:TLSInnerPlaintext 长度的最大值是 2 ^ 14 个八位字节 + 1 个八字节的 ContentType + 255 个八位字节的最大 AEAD 扩展。

三. Per-Record Nonce

分别维护 64 位序列号以读取和写入记录。在读取或写入每个记录之后,适当的序列号加 1。每个序列号在连接开始时和每次更改密钥时都设置为零; 在特定流量密钥下传输的第一条记录必须使用序列号 0。

因为序列号的大小是 64 位,所以它们不应该 wrap。如果 TLS 实现方需要 wrap 序列号,它必须重新生成密钥(第4.6.3节)或终止连接。

每个 AEAD 算法将为 per-record 的随机数指定一系列可能的长度,从 N_MIN 字节到输入的 N_MAX 字节[RFC5116]。 对于 AEAD 算法,TLS 的 per-record 随机数(iv_length)的长度设置为 8 字节和 N_MIN 中的较大者(参见[RFC5116],第4节)。 其中 N_MAX 小于 8 个字节的 AEAD 算法不得与 TLS 一起使用。AEAD 结构的 per-record 随机数形成如下:

  1. 64 位的记录序列号是按网络字节顺序编码,并在左边用零填充到 iv_length。
  2. 填充的序列号要和 client_write_iv 或者 server_write_iv 进行异或(取决于角色)

得到的值(长度为 iv_length)被用作 per-record 的随机数。

注意:这与 TLS 1.2 中的结构不同了,TLS 1.2 指定了部分显式的随机数。

四. Record Padding

所有加密的 TLS 记录都可以被填充,从而扩大 TLSCiphertext 的大小。这种做法允许发送者隐藏来自观察者的流量大小。

生成 TLSCiphertext 记录时,实现方可以选择填充。未填充的记录只是填充长度为零的记录。填充是在加密之前附加到 ContentType 字段的一串零值字节。实现方必须在加密之前将填充的八位字节全部设置为零。

如果发送者需要,应用数据记录可以包含零长度 TLSInnerPlaintext.content。 这允许在对 activity 存在或者不存在敏感的情况下,产生合理大小的覆盖流量。实现方绝不能发送具有零长度 TLSInnerPlaintext.content 的握手和 alert 记录; 如果收到这样的消息,接收实现方必须用 "unexpected_message" alert 消息终止连接。

发送的填充由记录保护机制自动验证;在成功解密 TLSCiphertext.encrypted_record后,接收实现方从末端向前开始扫描字段,直到找到非零八位字节。该非零八位字节是消息的内容类型 type 字段。选择此填充方案是因为它允许以任意大小(从零到 TLS 记录大小限制)填充任何加密的 TLS 记录,而不引入新的内容类型。该设计还强制执行全零填充八位字节,以便快速检测填充错误。

实现方必须将扫描范围限制为只扫描从 AEAD 解密返回的明文。如果接收实现方在明文中没有找到非零八位字节,它必须以 "unexpected_message" alert 消息终止连接。

填充并不会改变整体记录大小限制:完整编码的 TLSInnerPlaintext 不得超过 2 ^ 14 + 1 个八位字节。如果最大片段长度减少 - 例如,来自 [RFC8449] 的 record_size_limit 扩展 - 那么减少的限制适用于完整的纯文本,包括内容类型和填充。

选择一个合理的填充策略,比如何时填充,填充多少,这是一个复杂的主题,超出了本规范的范围。如果 TLS 之上的应用层协议具有自己的填充,则最好在应用层内填充应用数据 TLS 记录。但是,加密握手或 alert 记录的填充仍必须在 TLS 层处理。稍后的文档可以定义填充选择算法或通过 TLS 扩展或一些其他手段定义填充策略的请求机制。

五. Limits on Key Usage

明文数量存在一些加密限制,这些限制可以在给定的一组密钥下安全加密。[AEAD-LIMITS] 在假设基础原语(AES 或 ChaCha20)没有弱点的情况下提供了对这些限制的分析。在达到这些限制之前,实现方应该按照第 4.6.3 节中的描述进行密钥更新。

对于 AES-GCM,在给定连接上可以加密多达 2 ^ 24.5 个全尺寸记录(大约 2400 万个),同时为认证加密(AE)安全性保持大约 2 ^ -57 的安全余量。对于 ChaCha20 / Poly1305,记录序列号将在达到安全限制之前就会 wrap。


Reference:

RFC 8446

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: https://halfrost.com/TLS_1.3_Record_Protocol/