4. 承载 TLS 消息 (Carrying TLS Messages)
QUIC 在 CRYPTO 帧 (CRYPTO Frames) 中承载 TLS 握手数据,每个 CRYPTO 帧由一个连续的握手数据块组成,该数据块由偏移量 (Offset) 和长度 (Length) 标识. 这些帧被打包到 QUIC 数据包中,并在当前加密级别下进行加密. 与 TCP 上的 TLS 一样,一旦 TLS 握手数据已交付给 QUIC,QUIC 就负责可靠地传递它. TLS 生成的每个数据块都与 TLS 当前使用的密钥集相关联. 如果 QUIC 需要重传该数据,即使 TLS 已经更新到更新的密钥,它也必须 (MUST) 使用相同的密钥.
每个加密级别对应一个包编号空间 (Packet Number Space). 使用的包编号空间决定了帧的语义. 某些帧在不同的包编号空间中是被禁止的; 参见 [QUIC-TRANSPORT] 的第 12.5 节.
由于数据包在网络上可能被重新排序,QUIC 使用数据包类型 (Packet Type) 来指示用于保护给定数据包的密钥,如表 1 所示. 当需要发送不同类型的数据包时,端点应该 (SHOULD) 使用合并数据包 (Coalesced Packets) 将它们发送到同一个 UDP 数据报中.
| 数据包类型 | 加密密钥 | 包编号空间 |
|---|---|---|
| Initial | Initial secrets | Initial |
| 0-RTT Protected | 0-RTT | Application data |
| Handshake | Handshake | Handshake |
| Retry | Retry | N/A |
| Version Negotiation | N/A | N/A |
| Short Header | 1-RTT | Application data |
表 1: 按数据包类型划分的加密密钥
[QUIC-TRANSPORT] 的第 17 节展示了各个加密级别的数据包如何适配到握手过程中.
4.1. TLS 接口 (Interface to TLS)
如图 4 所示,从 QUIC 到 TLS 的接口由四个主要功能组成:
- 发送和接收握手消息
- 处理来自恢复会话的存储的传输和应用状态,并确定生成或接受 0-RTT 数据是否有效
- 重新生成密钥 (Rekeying,包括发送和接收)
- 更新握手状态
可能需要额外的功能来配置 TLS. 特别是,QUIC 和 TLS 需要就哪一方负责验证对等方凭据(如证书验证 [RFC5280])达成一致.
4.1.1. 握手完成 (Handshake Complete)
在本文档中,当 TLS 栈报告握手已完成时,TLS 握手被视为完成. 这发生在 TLS 栈既发送了 Finished 消息又验证了对等方的 Finished 消息时. 验证对等方的 Finished 消息为端点提供了先前握手消息未被修改的保证. 请注意,握手不会在两个端点同时完成. 因此,任何基于握手完成的要求都取决于所讨论端点的视角.
4.1.2. 握手确认 (Handshake Confirmed)
在本文档中,当握手完成时,服务器的 TLS 握手被视为已确认. 服务器必须 (MUST) 在握手完成后立即发送 HANDSHAKE_DONE 帧. 在客户端,当收到 HANDSHAKE_DONE 帧时,握手被视为已确认.
此外,客户端可以 (MAY) 在收到对 1-RTT 数据包的确认时将握手视为已确认. 这可以通过记录使用 1-RTT 密钥发送的最低包编号,并将其与任何收到的 1-RTT ACK 帧中的 Largest Acknowledged 字段进行比较来实现: 一旦后者大于或等于前者,握手就被确认.
4.1.3. 发送和接收握手消息 (Sending and Receiving Handshake Messages)
为了驱动握手,TLS 依赖于能够发送和接收握手消息. 该接口上有两个基本功能: 一个是 QUIC 请求握手消息,另一个是 QUIC 提供组成握手消息的字节.
在开始握手之前,QUIC 向 TLS 提供它希望承载的传输参数 (Transport Parameters,参见第 8.2 节).
QUIC 客户端通过从 TLS 请求 TLS 握手字节来启动 TLS. 客户端在发送其第一个数据包之前获取握手字节. QUIC 服务器通过向 TLS 提供客户端的握手字节来启动该过程.
在任何时候,端点的 TLS 栈都将具有当前发送加密级别和接收加密级别. TLS 加密级别决定了用于保护数据的 QUIC 数据包类型和密钥.
每个加密级别都与不同的字节序列相关联,这些字节序列通过 CRYPTO 帧可靠地传输到对等方. 当 TLS 提供要发送的握手字节时,它们被附加到当前加密级别的握手字节中. 然后,加密级别确定承载生成的 CRYPTO 帧的数据包类型; 参见表 1.
使用四个加密级别,为 Initial、0-RTT、Handshake 和 1-RTT 数据包生成密钥. CRYPTO 帧仅在其中三个级别中承载,省略了 0-RTT 级别. 这四个级别对应三个包编号空间: Initial 和 Handshake 加密数据包使用各自独立的空间; 0-RTT 和 1-RTT 数据包使用应用数据包编号空间.
QUIC 将 TLS 握手记录的未受保护内容作为 CRYPTO 帧的内容. QUIC 不使用 TLS 记录保护. QUIC 将 CRYPTO 帧组装成 QUIC 数据包,这些数据包使用 QUIC 数据包保护进行保护.
QUIC CRYPTO 帧只承载 TLS 握手消息. TLS 警报 (Alerts) 被转换为 QUIC CONNECTION_CLOSE 错误代码; 参见第 4.8 节. TLS 应用数据和其他内容类型不能由 QUIC 在任何加密级别承载; 如果从 TLS 栈接收到它们,这是一个错误.
当端点从网络接收包含 CRYPTO 帧的 QUIC 数据包时,它按以下方式处理:
-
如果数据包使用当前 TLS 接收加密级别,则像往常一样将数据排序到输入流中. 与 STREAM 帧一样,偏移量用于在数据序列中找到正确的位置. 如果此过程的结果是有新数据可用,则按顺序将其交付给 TLS.
-
如果数据包来自先前安装的加密级别,它禁止 (MUST NOT) 包含超出该流中先前接收数据末尾的数据. 实现必须 (MUST) 将任何违反此要求的行为视为类型为 PROTOCOL_VIOLATION 的连接错误.
-
如果数据包来自新的加密级别,它将被保存以供 TLS 稍后处理. 一旦 TLS 移动到从此加密级别接收,就可以将保存的数据提供给 TLS. 当 TLS 为更高加密级别提供密钥时,如果存在 TLS 未消耗的来自先前加密级别的数据,这必须 (MUST) 被视为类型为 PROTOCOL_VIOLATION 的连接错误.
每次向 TLS 提供新数据时,都会从 TLS 请求新的握手字节. 如果 TLS 收到的握手消息不完整或它没有要发送的数据,TLS 可能不提供任何字节.
CRYPTO 帧的内容可能由 TLS 增量处理,也可能被缓冲直到完整的消息或飞行 (Flights) 可用. TLS 负责缓冲按顺序到达的握手字节. QUIC 负责缓冲乱序到达的握手字节或尚未就绪的加密级别的握手字节. QUIC 不为 CRYPTO 帧提供任何流量控制手段; 参见 [QUIC-TRANSPORT] 的第 7.5 节.
一旦 TLS 握手完成,这将与 TLS 需要发送的任何最终握手字节一起指示给 QUIC. 在此阶段,对等方在握手期间宣传的传输参数得到验证; 参见第 8.2 节.
握手完成后,TLS 变为被动状态. TLS 仍然可以从其对等方接收数据并以同样的方式响应,但除非特别请求(由应用程序或 QUIC 请求),否则它不需要发送更多数据. 发送数据的一个原因是服务器可能希望向客户端提供额外的或更新的会话票据 (Session Tickets).
当握手完成时,QUIC 只需要向 TLS 提供到达 CRYPTO 流中的任何数据. 以与握手期间使用的相同方式,在提供接收到的数据后从 TLS 请求新数据.
4.1.4. 加密级别变化 (Encryption Level Changes)
当给定加密级别的密钥对 TLS 可用时,TLS 向 QUIC 指示该加密级别的读取或写入密钥可用.
新密钥的可用性始终是向 TLS 提供输入的结果. TLS 仅在初始化后(由客户端)或在提供新握手数据时提供新密钥.
但是,TLS 实现可以异步执行其某些处理. 特别是,验证证书的过程可能需要一些时间. 在等待 TLS 处理完成时,如果端点可能使用尚不可用的密钥处理接收到的数据包,则端点应该 (SHOULD) 缓冲这些数据包. 一旦 TLS 提供密钥,就可以处理这些数据包. 在此期间,端点应该 (SHOULD) 继续响应可以处理的数据包.
处理输入后,TLS 可能会生成握手字节、新加密级别的密钥或两者.
当新加密级别可用时,TLS 向 QUIC 提供三项内容:
- 一个密钥 (Secret)
- 一个带关联数据的认证加密 (Authenticated Encryption with Associated Data, AEAD) 函数
- 一个密钥派生函数 (Key Derivation Function, KDF)
这些值基于 TLS 协商的值,并由 QUIC 用于生成数据包和包头保护密钥; 参见第 5 节和第 5.4 节.
如果 0-RTT 是可能的,它在客户端发送 TLS ClientHello 消息或服务器接收该消息后就绪. 在向 QUIC 客户端提供第一个握手字节后,TLS 栈可能会发出切换到 0-RTT 密钥的信号. 在服务器上,在接收包含 ClientHello 消息的握手字节后,TLS 服务器可能会发出 0-RTT 密钥可用的信号.
尽管 TLS 一次只使用一个加密级别,但 QUIC 可能使用多个级别. 例如,在发送其 Finished 消息(使用握手加密级别的 CRYPTO 帧)后,端点可以发送 STREAM 数据(使用 1-RTT 加密). 如果 Finished 消息丢失,端点使用握手加密级别重传丢失的消息. 数据包的重新排序或丢失可能意味着 QUIC 需要处理多个加密级别的数据包. 在握手期间,这意味着可能处理高于和低于 TLS 当前使用的加密级别的数据包.
特别是,服务器实现需要能够同时在握手加密级别和 0-RTT 加密级别读取数据包. 客户端可以将使用握手密钥保护的 ACK 帧与 0-RTT 数据交错,服务器需要处理这些确认以检测丢失的握手数据包.
QUIC 还需要访问 TLS 实现通常可能不可用的密钥. 例如,客户端可能需要在准备好在该加密级别发送 CRYPTO 帧之前确认握手数据包. 因此,TLS 需要在可能为自己使用之前向 QUIC 提供密钥.
4.1.5. TLS 接口摘要 (TLS Interface Summary)
图 5 总结了客户端和服务器的 QUIC 与 TLS 之间的交换. 实线箭头表示承载握手数据的数据包; 虚线箭头显示可以发送应用数据的位置. 每个箭头都标有用于该传输的加密级别.
客户端 服务器
====== ======
Get Handshake
Initial ------------->
Install tx 0-RTT keys
0-RTT - - - - - - - ->
Handshake Received
Get Handshake
<------------- Initial
Install rx 0-RTT keys
Install Handshake keys
Get Handshake
<----------- Handshake
Install tx 1-RTT keys
<- - - - - - - - 1-RTT
Handshake Received (Initial)
Install Handshake keys
Handshake Received (Handshake)
Get Handshake
Handshake ----------->
Handshake Complete
Install 1-RTT keys
1-RTT - - - - - - - ->
Handshake Received
Handshake Complete
Handshake Confirmed
Install rx 1-RTT keys
<--------------- 1-RTT
(HANDSHAKE_DONE)
Handshake Confirmed
图 5: QUIC 和 TLS 之间的交互摘要
图 5 显示了组成单个消息"飞行"的多个数据包被单独处理,以显示哪些传入消息触发了不同的操作. 这显示了多个"Get Handshake"调用以检索不同加密级别的握手消息. 在处理传入数据包后请求新的握手消息.
图 5 显示了简单握手交换的一种可能结构. 确切的过程根据端点实现的结构和数据包到达的顺序而变化. 实现可以使用不同数量的操作或以其他顺序执行它们.
4.2. TLS 版本 (TLS Version)
本文档描述了如何将 TLS 1.3 [TLS13] 与 QUIC 一起使用.
在实践中,TLS 握手将协商要使用的 TLS 版本. 如果两个端点都支持该版本,这可能会导致协商出比 1.3 更新的 TLS 版本. 只要 QUIC 使用的 TLS 1.3 功能得到更新版本的支持,这是可以接受的.
客户端禁止 (MUST NOT) 提供早于 1.3 的 TLS 版本. 配置错误的 TLS 实现可能会协商 TLS 1.2 或其他更旧版本的 TLS. 如果协商出早于 1.3 的 TLS 版本,端点必须 (MUST) 终止连接.
4.3. ClientHello 大小 (ClientHello Size)
来自客户端的第一个 Initial 数据包包含其第一个加密握手消息的开头或全部,对于 TLS 而言即 ClientHello. 服务器可能需要解析整个 ClientHello(例如,访问服务器名称指示 (Server Name Indication, SNI) 或应用层协议协商 (Application-Layer Protocol Negotiation, ALPN) 等扩展)以决定是否接受新的传入 QUIC 连接. 如果 ClientHello 跨越多个 Initial 数据包,这样的服务器需要缓冲首次接收到的片段,如果客户端地址尚未验证,这可能会消耗过多资源. 为避免这种情况,服务器可以 (MAY) 使用重试 (Retry) 功能(参见 [QUIC-TRANSPORT] 的第 8.1 节)仅缓冲来自已验证地址的客户端的部分 ClientHello 消息.
QUIC 数据包和帧化至少为 ClientHello 消息增加了 36 字节的开销. 如果客户端选择长度大于零字节的源连接 ID (Source Connection ID) 字段,该开销会增加. 开销也不包括令牌 (Token) 或长度大于 8 字节的目标连接 ID (Destination Connection ID),如果服务器发送重试数据包,这两者都可能是必需的.
典型的 TLS ClientHello 可以轻松装入 1200 字节的数据包中. 但是,除了 QUIC 添加的开销之外,还有几个变量可能导致超出此限制. 大型会话票据 (Session Tickets)、多个或大型密钥共享 (Key Shares)、以及支持的密码套件 (Cipher Suites)、签名算法 (Signature Algorithms)、版本、QUIC 传输参数以及其他可协商参数和扩展的长列表都可能导致此消息增长.
对于服务器,除了连接 ID 和令牌之外,TLS 会话票据的大小可能会影响客户端有效连接的能力. 最小化这些值的大小会增加客户端可以使用它们并仍然将其整个 ClientHello 消息放入其第一个 Initial 数据包的概率.
TLS 实现不需要确保 ClientHello 足够大以满足 QUIC 对承载 Initial 数据包的数据报的要求; 参见 [QUIC-TRANSPORT] 的第 14.1 节. QUIC 实现使用 PADDING 帧或数据包合并 (Packet Coalescing) 来确保数据报足够大.
4.4. 对等方认证 (Peer Authentication)
身份验证的要求取决于正在使用的应用协议. TLS 提供服务器身份验证,并允许服务器请求客户端身份验证.
客户端必须 (MUST) 验证服务器的身份. 这通常涉及验证服务器的身份是否包含在证书中,以及证书是否由受信任的实体颁发(例如参见 [RFC2818]).
注意: 当服务器提供证书进行身份验证时,证书链的大小可能会消耗大量字节. 控制证书链的大小对于 QUIC 的性能至关重要,因为在验证客户端地址之前,服务器只能为每收到的一个字节发送 3 个字节; 参见 [QUIC-TRANSPORT] 的第 8.1 节. 可以通过限制名称或扩展的数量、使用具有小公钥表示的密钥(如 ECDSA)或使用证书压缩 [COMPRESS] 来管理证书链的大小.
服务器可以 (MAY) 在握手期间请求客户端进行身份验证. 如果客户端在被请求时无法进行身份验证,服务器可以 (MAY) 拒绝连接. 客户端身份验证的要求根据应用协议和部署而异.
服务器禁止 (MUST NOT) 使用握手后客户端身份验证 (Post-Handshake Client Authentication,如 [TLS13] 的第 4.6.2 节所定义),因为 QUIC 提供的多路复用会阻止客户端将证书请求与触发它的应用级事件相关联(参见 [HTTP2-TLS13]). 更具体地说,服务器禁止 (MUST NOT) 发送握手后 TLS CertificateRequest 消息,客户端必须 (MUST) 将收到此类消息视为类型为 PROTOCOL_VIOLATION 的连接错误.
4.5. 会话恢复 (Session Resumption)
QUIC 可以使用 TLS 1.3 的会话恢复功能. 它通过在握手完成后在 CRYPTO 帧中承载 NewSessionTicket 消息来实现这一点. 会话恢复可用于提供 0-RTT,也可以在禁用 0-RTT 时使用.
使用会话恢复的端点在创建恢复连接时可能需要记住有关当前连接的一些信息. TLS 要求保留一些信息; 参见 [TLS13] 的第 4.6.1 节. 除非同时使用 0-RTT,否则 QUIC 本身在恢复连接时不依赖于保留任何状态; 参见 [QUIC-TRANSPORT] 的第 7.4.1 节和第 4.6.1 节. 应用协议可能依赖于在恢复连接之间保留的状态.
客户端可以将恢复所需的任何状态与会话票据一起存储. 服务器可以使用会话票据来帮助承载状态.
会话恢复允许服务器将原始连接上的活动与恢复的连接关联起来,这对客户端来说可能是隐私问题. 客户端可以选择不启用恢复以避免创建这种关联. 客户端不应该 (SHOULD NOT) 重复使用票据,因为这允许服务器以外的实体关联连接; 参见 [TLS13] 的附录 C.4.
4.6. 0-RTT
QUIC 中的 0-RTT 功能允许客户端在握手完成之前发送应用数据. 这是通过重用来自先前连接的协商参数实现的. 要启用此功能,0-RTT 依赖于客户端记住关键参数,并向服务器提供 TLS 会话票据,使服务器能够恢复相同的信息.
此信息包括确定 TLS 状态的参数(由 [TLS13] 管理)、QUIC 传输参数、选择的应用协议以及应用协议可能需要的任何信息; 参见第 4.6.3 节. 此信息确定了 0-RTT 数据包及其内容的形成方式.
为确保两个端点都可以使用相同的信息,用于建立 0-RTT 的所有信息都来自同一个连接. 端点不能选择性地忽略可能改变 0-RTT 发送或处理的信息.
[TLS13] 对原始连接和任何尝试使用 0-RTT 之间的时间设置了七天的限制. 0-RTT 使用还有其他限制,特别是由于可能暴露于重放攻击而引起的限制; 参见第 9.2 节.
4.6.1. 启用 0-RTT (Enabling 0-RTT)
NewSessionTicket 消息中的 TLS early_data 扩展被定义为传达(在 max_early_data_size 参数中)服务器愿意接受的 TLS 0-RTT 数据量. QUIC 不使用 TLS 早期数据. QUIC 使用 0-RTT 数据包来承载早期数据. 因此,max_early_data_size 参数被重新用于保存哨兵值 0xffffffff,以指示服务器愿意接受 QUIC 0-RTT 数据. 要指示服务器不接受 0-RTT 数据,需从 NewSessionTicket 中省略 early_data 扩展. 客户端可以在 QUIC 0-RTT 中发送的数据量由服务器提供的 initial_max_data 传输参数控制.
服务器禁止 (MUST NOT) 发送 max_early_data_size 字段设置为 0xffffffff 以外的任何值的 early_data 扩展. 客户端必须 (MUST) 将收到包含设置为任何其他值的 early_data 扩展的 NewSessionTicket 视为类型为 PROTOCOL_VIOLATION 的连接错误.
希望发送 0-RTT 数据包的客户端在后续握手的 ClientHello 消息中使用 early_data 扩展; 参见 [TLS13] 的第 4.2.10 节. 然后它在 0-RTT 数据包中发送应用数据.
尝试 0-RTT 的客户端如果服务器已发送 NEW_TOKEN 帧,还可以提供地址验证令牌 (Address Validation Token); 参见 [QUIC-TRANSPORT] 的第 8.1 节.
4.6.2. 接受和拒绝 0-RTT (Accepting and Rejecting 0-RTT)
服务器通过在 EncryptedExtensions 中发送 early_data 扩展来接受 0-RTT; 参见 [TLS13] 的第 4.2.10 节. 然后服务器处理并确认它接收到的 0-RTT 数据包.
服务器通过发送不带 early_data 扩展的 EncryptedExtensions 来拒绝 0-RTT. 如果服务器发送 TLS HelloRetryRequest,它将始终拒绝 0-RTT. 拒绝 0-RTT 时,服务器禁止 (MUST NOT) 处理任何 0-RTT 数据包,即使它可以. 当 0-RTT 被拒绝时,如果客户端能够检测到这种情况,它应该 (SHOULD) 将收到对 0-RTT 数据包的确认视为类型为 PROTOCOL_VIOLATION 的连接错误.
当 0-RTT 被拒绝时,客户端假设的所有连接特性可能都是不正确的. 这包括应用协议的选择、传输参数以及任何应用配置. 因此,客户端必须 (MUST) 重置所有流的状态,包括绑定到这些流的应用状态.
如果客户端收到重试 (Retry) 或版本协商 (Version Negotiation) 数据包,它可以 (MAY) 重新尝试 0-RTT. 这些数据包不表示拒绝 0-RTT.
4.6.3. 验证 0-RTT 配置 (Validating 0-RTT Configuration)
当服务器接收到带有 early_data 扩展的 ClientHello 时,它必须决定是接受还是拒绝来自客户端的 0-RTT 数据. 其中一些决定由 TLS 栈做出(例如,检查正在恢复的密码套件是否包含在 ClientHello 中; 参见 [TLS13] 的第 4.2.10 节). 即使 TLS 栈没有理由拒绝 0-RTT 数据,QUIC 栈或使用 QUIC 的应用协议也可能拒绝 0-RTT 数据,因为与恢复会话相关联的传输或应用配置与服务器当前配置不兼容.
QUIC 要求将额外的传输状态与 0-RTT 会话票据相关联. 实现这一点的一种常见方法是使用无状态会话票据 (Stateless Session Tickets) 并将此状态存储在会话票据中. 使用 QUIC 的应用协议可能对关联或存储状态有类似要求. 此关联状态用于决定是否必须拒绝 0-RTT 数据. 例如,HTTP/3 设置 [QUIC-HTTP] 确定如何解释来自客户端的 0-RTT 数据. 使用 QUIC 的其他应用程序对于确定是接受还是拒绝 0-RTT 数据可能有不同的要求.
4.7. HelloRetryRequest
HelloRetryRequest 消息(参见 [TLS13] 的第 4.1.4 节)可用于请求客户端提供新信息,例如密钥共享 (Key Share),或验证客户端的某些特征. 从 QUIC 的角度来看,HelloRetryRequest 与在 Initial 数据包中承载的其他加密握手消息没有区别. 尽管原则上可以使用此功能进行地址验证,但 QUIC 实现应该 (SHOULD) 改为使用重试功能; 参见 [QUIC-TRANSPORT] 的第 8.1 节.
4.8. TLS 错误 (TLS Errors)
如果 TLS 遇到错误,它会生成 [TLS13] 的第 6 节中定义的适当警报.
TLS 警报被转换为 QUIC 连接错误. AlertDescription 值加上 0x0100 以生成保留给 CRYPTO_ERROR 的范围内的 QUIC 错误代码; 参见 [QUIC-TRANSPORT] 的第 20.1 节. 结果值在类型为 0x1c 的 QUIC CONNECTION_CLOSE 帧中发送.
QUIC 只能传达"致命" (Fatal) 的警报级别. 在 TLS 1.3 中,"警告" (Warning) 级别的唯一现有用途是发出连接关闭的信号; 参见 [TLS13] 的第 6.1 节. 由于 QUIC 提供了连接终止的替代机制,并且只有在遇到错误时才关闭 TLS 连接,QUIC 端点必须 (MUST) 将来自 TLS 的任何警报视为处于"致命"级别.
QUIC 允许使用通用代码代替特定错误代码; 参见 [QUIC-TRANSPORT] 的第 11 节. 对于 TLS 警报,这包括用通用警报(如握手失败 (handshake_failure,QUIC 中为 0x0128))替换任何警报. 端点可以 (MAY) 使用通用错误代码来避免可能暴露机密信息.
4.9. 丢弃未使用的密钥 (Discarding Unused Keys)
在 QUIC 完成移动到新加密级别后,可以丢弃先前加密级别的数据包保护密钥. 这在握手期间发生多次,以及在更新密钥时也会发生; 参见第 6 节.
数据包保护密钥不会在新密钥可用时立即丢弃. 如果来自较低加密级别的数据包包含 CRYPTO 帧,则重传该数据的帧必须 (MUST) 在相同加密级别发送. 类似地,端点在与被确认的数据包相同的加密级别生成确认. 因此,在新加密级别的密钥可用后的短时间内,可能需要较低加密级别的密钥.
除非端点已在该加密级别从其对等方接收到所有加密握手消息且其对等方也已完成相同操作,否则端点不能丢弃给定加密级别的密钥. 为 Initial 密钥(第 4.9.1 节)和握手密钥(第 4.9.2 节)提供了确定这一点的不同方法. 这些方法不会阻止在该加密级别接收或发送数据包,因为对等方可能尚未收到所有必要的确认.
尽管端点可能保留较旧的密钥,但新数据必须 (MUST) 在当前可用的最高加密级别发送. 只有 ACK 帧和 CRYPTO 帧中数据的重传才在先前的加密级别发送. 这些数据包也可以 (MAY) 包括 PADDING 帧.
4.9.1. 丢弃初始密钥 (Discarding Initial Keys)
使用 Initial 密钥(第 5.2 节)保护的数据包未经身份验证,这意味着攻击者可以伪造数据包以中断连接. 为了限制这些攻击,Initial 数据包保护密钥被比其他密钥更积极地丢弃.
成功使用握手数据包表明不再需要交换 Initial 数据包,因为这些密钥只能在从 Initial 数据包接收所有 CRYPTO 帧后才能生成. 因此,客户端必须 (MUST) 在首次发送握手数据包时丢弃 Initial 密钥,服务器必须 (MUST) 在首次成功处理握手数据包时丢弃 Initial 密钥. 此后,端点禁止 (MUST NOT) 发送 Initial 数据包.
这导致放弃 Initial 加密级别的丢失恢复状态并忽略任何未完成的 Initial 数据包.
4.9.2. 丢弃握手密钥 (Discarding Handshake Keys)
当 TLS 握手被确认时(第 4.1.2 节),端点必须 (MUST) 丢弃其握手密钥.
4.9.3. 丢弃 0-RTT 密钥 (Discarding 0-RTT Keys)
0-RTT 和 1-RTT 数据包共享相同的包编号空间,客户端在发送 1-RTT 数据包后不发送 0-RTT 数据包(第 5.6 节).
因此,客户端应该 (SHOULD) 在安装 1-RTT 密钥后立即丢弃 0-RTT 密钥,因为它们在那之后没有用处.
此外,服务器可以 (MAY) 在收到 1-RTT 数据包后立即丢弃 0-RTT 密钥. 但是,由于数据包重新排序,0-RTT 数据包可能在 1-RTT 数据包之后到达. 服务器可以 (MAY) 暂时保留 0-RTT 密钥,以允许解密重新排序的数据包,而无需使用 1-RTT 密钥重传其内容. 在收到 1-RTT 数据包后,服务器必须 (MUST) 在短时间内丢弃 0-RTT 密钥; 推荐 (RECOMMENDED) 的时间段是探测超时 (Probe Timeout, PTO,参见 [QUIC-RECOVERY])的三倍. 如果服务器确定已接收到所有 0-RTT 数据包(可以通过跟踪缺失的包编号来完成),则可以 (MAY) 更早地丢弃 0-RTT 密钥.