RFC9001 QUIC-TLS
前言
本文是QUIC使用TLS进行加密通信的网络规范文档译文,尚未完成翻译,欢迎指正。
摘要
本文描述如何使用传输层安全协议(TLS)加密QUIC。
备忘状态
本文是互联网标准追踪文档。
本文产自互联网工程任务组(IETF),已接受公开审查,并由互联网互联网工程指导委员会(IESG)批准出版。更多互联网标准相关信息详见RFC 7841第2章。
关于本文当前状态、勘误及反馈方式等相关信息请移步https://www.rfc-editor.org/info/rfc9001。
版权声明
版权所有(c)2021 IETF信托及确认为文档作者的个人。保留所有权利。
本文遵守BCP 78及在本文发布之日起生效的IETF信托涉及IETF文档的法律条文(https://trustee.ietf.org/license-info)。请仔细阅读相关条文,因为其描述了你对本文所有的权利及限制。从本文中摘录的代码组件必须包含信托法律条文第4.e章的简版BSD License文件,并且不附带任何该文件所描述的保证。
1. 介绍
2. 标准规范
本文中的关键字“必须(MUST)”、“必须不(MUST NOT)”、“需要(REQUIRED)”、“强烈要求(SHALL)”、“强烈要求不(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“不推荐(NOT RECOMMENDED)”、“可以(MAY)”,以及“可选(OPTIONAL)”应理解为BCP 14 《RFC2119》《RFC8174》所描述的,当且仅当它们像本段一样以斜体加粗方式出现的时候。
本文档使用了在《QUIC传输》中建立的术语。
为了简洁,缩写TLS指代的是TLS 1.3,但也可以使用更高的版本;详见第4.2章。
2.1. TLS概述
TLS为两个终端提供一种经由不受信任的中间人(例如,互联网)建立通信的方法。TLS认证对端的身份并且为终端间交换的信息提供可信度和完整性保护。
TLS在协议内部有着不同的层,它的结构如图1所示。
每条内容层消息(例如握手、警告和应用数据)都是由记录层将其作为一系列具有类型的TLS记录来传递的。每条记录受到单独的加密保护,然后经由一种可靠的传输方式(通常是TCP)被发送出去,可靠是指它提供了有序且受保证的交付。
在两个终端(客户端和服务器)间会进行TLS认证密钥的交换。交换由客户端发起,服务器则负责响应。如果密钥交换顺利完成,那么客户端和服务器将就一个秘密值达成一致。TLS既支持预共享密钥(PSK),也支持基于有限域或椭圆曲线的迪菲-赫尔曼金密钥交换((EC)DHE)。预共享密钥是早期数据(0-RTT)的基础;后者在(EC)DHE密钥被销毁时提供前向安全(Forward Secrecy)。还可以组合使用这两种模式,以在使用PSK认证时提供前向安全。
只要完成了TLS握手,客户端就能了解到并验证服务器的身份,而服务器能够可选地去了解和验证客户端的身份。TLS支持使用X.509(详见《RFC5280》)证书来认证服务器和客户端。当使用PSK密钥交换时(在恢复过程中会用到),有关PSK的知识也能帮助验证对端的身份。
TLS密钥交换能抵御攻击者的篡改,并且由它生成的共享秘密值不受任一终端的控制。
TLS提供两种QUIC感兴趣的基本握手模式:
-
完整的1-RTT握手,这种情况下客户端可以在单次数据往返后发送应用数据,而服务器可以在接收到来自客户端的首条握手消息后立即作出响应。
-
0-RTT握手,这种情况下客户端使用有关服务器的已知信息来立即发送应用数据。这些应用数据可以被攻击者重放,所以0-RTT不适合用来传递被重放后可能引发意外的副作用的指令。
图2展示的是简化后的发送0-RTT应用数据的TLS握手过程。
图2省略了QUIC不会使用到的EndOfEarlyData
(早期数据结束)消息;详见第8.3章。类似地,QUIC也不会使用ChangeCipherSpec
(更改加密设置)消息和KeyUpdate
(密钥更新)消息。在TLS 1.3中ChangeCipherSpec
是冗余的;详见第8.4章。QUIC有自己的密钥更新机制;详见第6章。
多种加密级别被用于保护数据:
-
初始密钥
-
早期数据(0-RTT)密钥
-
握手密钥
-
应用数据(1-RTT)密钥
应用数据只能出现在早期数据和应用数据这两种密级中。握手消息和警告消息可以出现在任一密级。
如果客户端和服务器之间曾进行过通信,那么可以使用0-RTT握手。进行1-RTT握手时,客户端只有在收到所有来自服务器的握手消息后才能发送受保护的应用数据。
3. 协议概述
QUIC(详见《QUIC传输》)负责数据包的可信度和完整性保护。为此它使用了衍生自TLS握手(详见《TLS13》)的密钥,但如图3所示,TLS握手和警告消息由QUIC传输层直接传递,而不是在QUIC传输层的基础上使用TLS记录来传递(TCP是这么做的),也就是说QUIC接管了TLS记录层的职责。
QUIC还依靠TLS来验证和协商那些安全和性能方面的关键参数。
这两种协议间没有严格的层次区分,取而代之的是它们相互合作:QUIC使用TLS的握手;TLS使用由QUIC提供的可靠性、有序交付和记录层等特性。
抽象地说,TLS组件和QUIC组件间主要有两类交互:
-
TLS组件经由QUIC组件发送和接收消息,QUIC向TLS提供一种可靠的流的抽象。
-
TLS组件向QUIC组件提供一系列状态更新,包括(a)新的数据包保护密钥,和(b)状态变更,例如握手完成、服务器证书,等等。
图4更详细地展示了这些交互,并且把QUIC数据包保护单独提了出来。
不像基于TCP的TLS,想要发送数据的QUIC应用并不使用TLS应用数据记录来发送,而是将数据以QUIC流帧或其他类型的帧的形式发送,它们都是由QUIC数据包传递的。
4. 传递TLS消息
QUIC使用加密帧传递TLS握手数据,每个帧由一些连续的握手数据块组成,并使用偏移值和长度值来区分数据块。这些帧被封装进QUIC数据包并受到当前密级的加密。就像基于TCP的TLS一样,一旦TLS握手数据被交付给QUIC,QUIC就有责任将数据可靠地交付给对端。每一份由TLS生成的数据分块都被关联到TLS正在使用的一组密钥。如果QUIC需要重传某份数据,那么它必须使用相同的密钥,哪怕TLS已经改用更新的密钥了。
每个密级对应着一个数据包号空间。正在使用的数据包号空间决定了帧的语义。在特定的数据包号空间里,有些帧是被禁止使用的;详见《QUIC传输》的第12.5章。
由于数据包在链路上可能出现乱序,QUIC使用数据包类型来表明用于保护给定数据包的密钥,如表1所示。当要发送不同类型的数据包时,终端应该使用合并数据包的方法从而在单个UDP数据报中发送它们。
数据包类型 | 加密密钥 | 数据包号空间 |
---|---|---|
初始 | 初始秘密值 | 初始 |
0-RTT | 0-RTT | Application data |
握手 | 握手 | 握手 |
重试 | 重试 | 不适用 |
版本协商 | 不适用 | 不适用 |
短包头 | 1-RTT | 应用数据 |
4.1. 面向TLS的接口
4.1.1. 握手完成
在本文档中,当TLS栈报告握手已完成时,就可以认定此次TLS握手完成。这一事件会在TLS栈发送了自己的Finished
(已结束)消息并且验证了对端的Finished
消息时发生。验证对端的Finished
消息使得终端能够确信之前的握手消息没有受到篡改。注意,两侧的终端不是同时认定握手完成的。所以,任何基于握手完成这一时机的要求,该时机都是由终端在具体问题中的角色决定的。
4.1.2. 握手已确认
在本文档中,在握手完成的同时,服务器一侧的TLS握手就可以被认定为已确认。一旦握手完成,服务器必须立即发送握手完成帧。而在客户端一侧,只有接收到握手完成帧后才能认定握手为已确认。
除此之外,客户端可以在接收到对于某个1-RTT数据包的确认时将握手认定为已确认。这可以通过记录使用1-RTT密钥发送的最小数据包号,并将它与接收到的1-RTTACK帧的最大确认数字段作比较,来实现:一旦后者大于等于前者,即可确认握手。
4.1.3. 发送和接收握手消息
为了进行握手,TLS需要发送和接收握手消息的能力。对于这个接口,有两个基本函数:QUIC从其中一个函数获取握手消息,向另一个函数提供组成握手消息的字节数据。
在启动握手前,QUIC向TLS提供它想传递的传输参数(详见第8.2章)。
QUIC客户端以向TLS获取握手的字节数据的方式启动TLS。客户端在发送首个数据包前获取握手的字节数据。QUIC服务器则以向TLS提供客户端的握手字节数据的方式启动TLS。
任一终端的TLS栈都始终记录着当前的发送密级和接收密级。TLS密级决定了QUIC数据包类型和用于保护数据的密钥。
每个密级都关联着各自的一份字节序列,这些字节内容会由加密帧可靠地发送给对端。当TLS提供了将要发送的握手字节时,这些字节会被追加到当前密级的握手字节内容后面。该密级决定着最终发送加密帧时所用的数据包类型;详见表1。
四个密级分别为初始数据包、0-RTT数据包、握手数据包和1-RTT数据包生成密钥。其中只有三个密级能被用来传递加密帧,不在其中的是0-RTT这个密级。这四个密级对应着三个数据包号空间:受初始密钥和握手密钥加密的数据包分别使用各自的空间;0-RTT数据包和1-RTT数据包使用应用数据数据包号空间。
QUIC使用未受保护的TLS握手记录的内容作为加密帧的内容。QUIC不使用TLS的记录保护。QUIC将加密帧组装进具有QUIC数据包保护的QUIC数据包。
QUIC加密帧仅传递TLS握手消息。TLS警告被转换为QUIC连接关闭帧的错误码;详见第4.8章。TLS应用数据和其他内容类型不能由QUIC以任何密级传递;不应该从TLS栈接收到它们,否则这是一种错误。
当终端从网络接收到了一个包含加密帧的QUIC数据包时,它应该这样处理:
-
如果数据包使用了当前的TLS接收密级,那么数据会被正常地按顺序置入输入流。和流帧一样,要使用偏移值来在数据序列中找到正确的置入位置。如果完成该过程后出现了新的可用数据,那么新数据会被有序地交付给TLS。
-
如果数据包来自之前使用过的密级,那么它包含的数据必须不扩展那个密级的数据流末尾。QUIC实现必须将任何违反这项要求的情况视作类型为
PROTOCOL_VIOLATION
(协议违反)的连接错误。 -
如果数据包来自新的密级,那么它会被保存起来,用于将来给TLS处理。一旦TLS改用此密级接收数据,暂存着的数据就可以被交给TLS。当TLS为更高的密级提供密钥时,如果还有来自之前的密级但是还未被TLS处理的数据,那么必须将该情况视作类型为
PROTOCOL_VIOLATION
的连接错误。
每次将新数据交给TLS时,都会向TLS获取新的握手字节。如果TLS接收到的握手消息不完整或它没有数据需要发送,那么它可能不会提供任何字节数据。
加密帧的内容可能会被TLS增量处理,或被缓存起来直到有完整的可用消息。TLS负责缓存按序到达的握手字节数据。QUIC负责缓存未按序到达或属于尚未可用的密级的握手字节。QUIC不会为加密帧提供任何流量控制的方法;详见《QUIC传输》的第7.5章。
一旦TLS握手完成,这一事件就会随着TLS要发送的最后的握手字节一起被告知给QUIC。在这个阶段,握手期间对端宣告的传输参数会被验证;详见第8.2章。
一旦握手完成,TLS就变得被动起来。TLS仍然可以从对端接收数据并作出响应,但它没有必要发送更多数据,除非被应用或QUIC专门提出要发送数据的请求。发送数据的可能原因之一是服务器可能想要向客户端新增或更新会话票据。
当握手完成后,QUIC只需要向TLS提供以加密帧的形式到达的数据。和握手期间的行为一样,处理完接收到的数据后QUIC会向TLS获取新数据。
4.1.4. 改变密级
当某个密级的密钥对TLS可用时,TLS会告知QUIC那个密级的读取密钥或写入密钥已经变为可用了。
新密钥变为可用这一事件一定是因向TLS提供输入而引发的。TLS只有在(被客户端)初始化后或接收到新握手数据时才会提供新密钥。
然而,TLS实现可能会异步地进行某些处理过程。尤其是,验证证书的过程可能会花不少时间。当等待TLS处理时,如果终端接收到了需要用尚未可用的密钥来处理的数据包,那么终端应该将它们缓存起来。一旦TLS提供了密钥,这些数据包就能够得到处理。终端应该继续响应此时可以处理的数据包。
在处理完输入后,TLS可能生成出握手字节、新密级的密钥或两者兼具。
当新密级可用时,TLS会向QUIC提供以下三份内容:
-
一个秘密值
-
一个带有关联数据的认证加密(AEAD)函数
-
一个密钥衍生函数(KDF)
这些值源自TLS协商出的那些值,以及被QUIC用于生成数据包与头部保护密钥的那些值;详见第5章和第5.4章。
如果要使用0-RTT,那么0-RTT会在客户端发送了TLS的ClientHello
消息或服务器接收到这条消息后就绪。在第一次向QUIC提供握手字节后,TLS栈可能会发送有关0-RTT密钥发生变化的信号。在服务器接收到包含ClientHello
消息的握手字节后,TLS服务器可以发送有关0-RTT密钥变为可用的信号。
尽管TLS在某一时刻只会使用一个密级,QUIC却可以同时使用不止一个密级。举例来说,终端在发送完Finished
消息后(使用的是处于握手密级的加密帧),它可以发送流帧(使用的是1-RTT加密)。如果Finished
消息在传输过程中被丢失了,那么终端会用握手密级重传丢失的消息。数据包乱序或丢包的存在意味着QUIC可能需要同时处理处于不同密级的数据包。在握手期间,这意味着有可能需要处理位于比TLS正在使用的密级更高或更低的密级的数据包。
特别是,服务器上的QUIC实现需要有能力同时读取位于握手密级和位于0-RTT密级的数据包。客户端可能使0-RTT数据与受握手密钥保护的ACK帧交错传输,而服务器得处理这些确认才能检测出丢包的握手数据包。
QUIC还需要访问对TLS实现来说并不可用的密钥。例如,客户端可能需要在准备好发送握手密级的加密帧前确认握手数据包。因此TLS可能需要在出于自己使用的目的而生成密钥前就向QUIC提供这些密钥。
4.1.5. TLS接口概述
4.2. TLS版本
本文档描述了如何与QUIC一起使用TLS 1.3(详见《TLS13》)。
在实际操作时,TLS握手会协商出一个要使用的TLS版本。在两侧终端都支持的情况下,它们最终可能协商出一个比1.3
还要高的TLS版本。这是可以接受的,因为QUIC使用的TLS 1.3特性都会在更高版本中得到支持。
客户端必须不提供低于1.3
的TLS版本。未恰当配置的TLS实现可能想要协商TLS 1.2或别的低版本TLS。如果协商了低于1.3
的TLS版本,那么终端必须终止连接。
4.3. ClientHello的尺寸
来自客户端的首个初始数据包包含着客户端首个加密握手消息的所有部分或起始部分,对TLS来说这个消息就是ClientHello
(客户端问候)。服务器可能需要解析完完整的ClientHello
(例如,为了访问服务器名称认证(SNI)和应用层协议协商(ALPN)等扩展)后才能决定要不要接受这个新传入的QUIC连接。如果ClientHello
被拆分为多个初始数据包,那么这么做的服务器就需要缓存接收到的数据分段,这会在客户端地址尚未被验证时消耗过多资源。为了避免这一情况,服务器可以使用重试特性(详见《QUIC传输》的第8.1章)以仅缓存来自具有经验证地址的客户端的ClientHello
消息。
QUIC数据包和分帧过程会向ClientHello
消息添加至少36字节的开销。如果客户端选择了长于零字节的源连接ID字段,那么这个开销还会增加。这个开销还不包含令牌字段和长于8字节的目标连接ID字段,当服务器发送重试数据包时会需要它们。
一个典型的TLSClientHello
可以被轻松地放进1200字节的数据包中。然而,除了由QUIC引入的开销之外,还有其他因素会使得ClientHello
的尺寸超过这一限制。较大的会话票据、多个或较大的密钥共享值和较长的受支持的加密方法、签名算法、版本、QUIC传输参数或其他可协商参数与扩展的列表都可能使得这条消息变大。
对服务器而言,除了连接ID和令牌外,TLS会话票据的尺寸也可能会影响客户端能否高效地进行连接。尽可能减小这些值的尺寸可以提高客户端使用它们并且仍能将整个ClientHello
消息放进首个初始数据包的可能性。
TLS实现不需要确保ClientHello
的尺寸足够大来满足QUIC对于传递初始数据包的数据报的要求;详见《QUIC传输》的第14.1章。QUIC实现会使用填充帧或数据包合并的方法来确保数据报足够大。
4.4. 对端验证
有关验证的要求是视正在使用的应用协议而定的。TLS提供了验证服务器的手段,并且允许服务器请求进行客户端验证。
客户端必须验证服务器的身份。这通常包含两部分验证过程,一是验证证书中包含着服务器的身份,二是验证证书是由可信任的实体签发的(有关样例详见《RFC2818》)。
注意:当服务器提供用于验证的证书时,证书链的尺寸可能消耗大量字节。控制证书链的尺寸对于QUIC的性能是十分关键的,因为服务器在验证客户端地址前可发送的字节数不能超过接收到字节数的3倍;详见《QUIC传输》的第8.1章。证书链的尺寸是可以被控制的,为此可以限制名称或扩展的数量;使用以较短的公钥表示方法,如ECDSA,表示的密钥;或使用证书压缩(详见《COMPRESS》)。
服务器可以在握手期间请求进行客户端验证。如果客户端在被请求验证后不能够提供证明,那么服务器可以拒绝这条连接。对于客户端验证的要求会基于应用协议和部署方法的不同而不同。
服务器必须不使用握手后客户端验证(详见《TLS13》的第4.6.2章),因为QUIC提供的多路复用机制使得客户端不能将证书请求与触发它的应用层事件关联起来(详见《HTTP2-TLS13》)。更准确地说,在握手完成后服务器必须不发送TLS的CertificateRequest
(证书请求)消息,并且客户端必须将接收到这种消息的情况视作类型为PROTOCOL_VIOLATION
的连接错误。
4.5. 会话恢复
QUIC可以使用TLS 1.3的会话恢复特性。这可以在握手完成后通过使用加密帧传递NewSessionTicket
(新会话票据)消息的方式来做到。会话恢复可以被用于提供0-RTT,无论0-RTT是否被禁用。
使用会话恢复的终端可能需要在恢复会话时记录有关当前连接的一些信息。TLS会要求某些信息得到持续保留;详见《TLS13》的第4.6.1章。在恢复连接时,QUIC本身不依赖于任何被持续保留的状态,除非还使用了0-RTT;详见第4.6.1章和《QUIC传输》的第7.4.1章。应用协议可以依赖在被恢复的连接间持续保留的状态。
客户端可以将任何恢复会话所需的状态与会话票据一起存储起来。服务器可以使用会话票据来协助保存状态。
会话恢复使得服务器将原始连接上的活动与被恢复的连接关联起来,这可能涉及到客户端的隐私问题。客户端可以选择禁用恢复机制来避免建立这种关联。客户端不应该重用票据,因为这使得除服务器外的实体能够将不同连接关联起来;详见《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的连接间的时间间隔设置了7天的上限。对于0-RTT的使用还存在其他限制,尤其是那些因潜在的重放攻击风险而施加的;详见第9.2章。
4.6.1. 启用0-RTT
TLS在NewSessionTicket
消息中定义的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
(初始最大数据量)传输参数控制的。
若max_early_data_size
字段的值并非0xffffffff
,则服务器必须不发送early_data
扩展。如果客户端收到的NewSessionTicket
中包含early_data
扩展但是max_early_data_size
是其他值,那么客户端必须将此情况视作类型为PROTOCOL_VIOLATION
的连接错误。
想要发送0-RTT数据包的客户端在后续握手的ClientHello
消息中使用early_data
扩展;详见《TLS13》的第4.2章。然后它就能在0-RTT数据包中发送应用数据。
4.6.2. 接受与拒绝0-RTT
服务器通过在加密扩展(EncryptedExtensions
)中发送early_data
扩展的方式接受0-RTT;详见《TLS13》的第4.2.10章。随后服务器处理并确认它接收到的0-RTT数据包。
服务器通过发送不带early_data
扩展的加密扩展(EncryptedExtensions
)的方式拒绝0-RTT。如果服务器发送了TLS的HelloRetryRequest
(问候重试请求),就意味着它拒绝了0-RTT。如果拒绝了0-RTT,那么服务器必须不处理任何0-RTT数据包,即使它有能力这么做。如果0-RTT被拒绝,那么客户端应该在有能力的情况下将收到一个对于0-RTT数据包的确认的情况视作类型为PROTOCOL_VIOLATION
的连接错误。
当0-RTT被拒绝时,客户端假设的所有关于连接的特性都可能是不正确的。这包括应用协议、传输参数和任何应用配置的选择。因此客户端必须重置所有流的状态,包括与这些流相关的应用状态。
如果客户端接收到重试数据包或版本协商数据包,那么它可以再次尝试0-RTT。这些数据包并不标志着0-RTT被拒绝。
4.6.3. 验证0-RTT配置
当服务器接收到了具有early_data
扩展的ClientHello
,它就必须决定是要接受还是拒绝来自客户端的0-RTT数据。TLS栈会参与这个决定(例如,检查ClientHello
中是否包含那组恢复出来的加密套件;详见《TLS13》的第4.2.10章)。即使TLS栈没有拒绝0-RTT数据的理由,QUIC栈或使用QUIC的应用协议也可以拒绝0-RTT数据,因为与被恢复的会话关联的传输或应用配置与服务器的当前配置可能不一致。
为了关联0-RTT会话票据,QUIC需要额外的传输状态。一种常见的实现方法是使用无状态的会话票据并将这些状态存储在会话票据中。使用QUIC的应用协议可能对于关联和存储状态有着类似的要求。被关联的状态会被用于决定是否拒绝0-RTT。举例来说,HTTP/3设置(详见《QUIC-HTTP》)会决定怎样解释来自客户端的0-RTT数据。其他使用QUIC的应用协议为了决定接受还是拒绝0-RTT数据,可能有着不同的要求。
4.7. HelloRetryRequest
4.8. TLS错误
如果TLS遇到了错误,那么它会按照《TLS13》的第6章中定义的那样创建一个合适的警告。
TLS警告会被转换为QUIC的连接错误。为了使创建出的QUIC错误码落在为CRYPTO_ERROR
(加密错误)保留的范围内,警告描述的值需要与0x0100
相加;详见《QUIC传输》的第20.1章。相加的结果使用QUIC的类型为0x1c
的连接关闭帧来发送。
QUIC仅仅有能力传达级别为“致命”的警告。在TLS 1.3中,“警告”级别的唯一用处是发送连接关闭的信号;详见《TLS13》的第6.1章。由于QUIC提供了关闭连接的替代机制,并且TLS连接只会在遇到错误时才被关闭,QUIC终端必须将所有来自TLS的警告都视作为“致命”级别。
QUIC允许使用通用的错误码来代替专门的错误码;详见《QUIC传输》的第11章。对TLS警告来说,这就表示允许将任何警告都替换为通用的警告,比如handshake_failure
(握手失败,在QUIC中的错误码为0x0128
)。终端可以使用通用的错误码来避免暴露加密信息。
4.9. 丢弃不再使用的密钥
在QUIC移动到新的密级后,先前密级的数据包保护密钥就可以被弃用。这会在握手过程中以及密钥被更新时发生数次,详见第6章。
当新的数据包保护密钥可用时,先前的密钥不会被立即弃用。如果有处于较低密级的数据包包含着加密帧,那么用于重传其中数据的新帧必须使用相同密级发送。类似地,在确认这个较低密级的数据包时,终端要为这个密级的数据包创建确认。因此,在新密级密钥可用后的短暂时间内需要较低密级的密钥的情况是有可能出现的。
终端只有从对端收到了某个密级的所有加密握手消息并且对端也收到了所有相应消息时,才能弃用这个密级的密钥。要判断这一点,初始密钥和握手密钥使用不同的方法(详见第4.9.1章和第4.9.2章)。这些方法不会阻止数据包在某个密级上的发送与接收,因为对端可能还没有接收完所有必需的确认。
尽管终端可以持续保留旧的密钥,但是新数据必须使用当前可用的最高密级来发送。只有ACK帧和重传数据的加密帧会使用先前的密级来发送。这些数据包中还可以包含填充帧。
4.9.1. 弃用初始密钥
受初始秘密值(详见第5.2章)保护的数据包是不受认证的,这意味着攻击者可以伪造数据包来干扰连接。为了限制这种攻击造成的影响,初始数据包保护密钥相比其他密钥会被更激进地弃用。
开始使用握手数据包就意味着不再需要交换初始数据包,因为只有在接收到所有初始数据包中的加密帧后才能创建握手密钥。因此,客户端必须在首次发送握手数据包时弃用握手密钥,同时服务器必须在首次成功处理握手数据包时弃用初始密钥。在此之后,终端必须不发送初始数据包。
这会丢弃初始密级的丢包恢复状态并且忽略所有未得到处理的初始数据包。
4.9.2. 弃用握手密钥
当TLS的握手已确认(详见第4.1.2章)时,终端必须弃用它的握手密钥。
4.9.3. 弃用0-RTT密钥
0-RTT数据包和1-RTT数据包共享同一个数据包号空间,并且客户端在发送了1-RTT数据包后就不会再发送0-RTT数据包(详见第5.6章)。
因此,客户端应该在建立了1-RTT密钥后立即弃用0-RTT密钥,因为在那之后它们不再有任何用处。
除此之外,服务器应该在接收到1-RTT数据包后立即弃用0-RTT密钥。然而,由于数据包乱序的存在,0-RTT数据包可能晚于1-RTT数据包到达。服务器应该暂时保留0-RTT密钥以能够解密乱序数据包而不需要等对端用1-RTT密钥重传其中的数据。在接收到1-RTT数据包后,服务器必须在一段较短的时间内弃用0-RTT密钥;推荐的时长是探测包超时时间(PTO,详见《QUIC恢复》)的三倍大小。如果服务器判断它已经接收到了所有0-RTT数据包,那么它可以提早弃用0-RTT密钥,这可以通过追踪缺失的数据包号来做到。
5. 数据包保护
就像基于TCP的TLS一样,QUIC用衍生自TLS握手的密钥来保护数据包,使用的是由TLS协商的AEAD算法(详见《AEAD》)。
QUIC数据包的保护方法视数据包类型而定:
-
版本协商数据包不受加密保护。
-
重试数据包使用
AEAD_AES_128_GCM
来提供保护以免受到意外修改,并且限制能够构造合法重试数据包的实体;详见第5.8章。 -
初始数据包使用
AEAD_AES_128_GCM
和衍生自客户端发送的首个初始数据包的目标连接ID字段的密钥;详见第5.2章。 -
所有其他类型的数据包在可信度和完整性上都受到健壮的加密保护,使用的是由TLS协商的密钥和加密算法。
本章描述了怎样将数据包保护应用到握手数据包、0-RTT数据包和1-RTT数据包上。同样的数据包保护过程还会被应用到初始数据包上。然而,由于决定用于初始数据包的密钥的过程是公开的,所以这类数据包不被认为受到可信度和完整性保护。类似地,重试数据包使用了固定密钥所以也缺乏可信度和完整性保护。
5.1. 数据包保护密钥
QUIC衍生数据包保护密钥的方法与TLS衍生记录保护密钥的方法是一致的。
每个密级在不同发送数据的方向上分别有各自的秘密值用来保护数据包。这些秘密值是由TLS衍生的(详见《TLS13》的第7.1章),并且会被QUIC用在除了初始密级外的所有其他密级上。用于初始密级的秘密值是基于客户端的初始目标连接ID计算出来的,详见第5.2章。
用于数据包保护的密钥是通过对TLS秘密值使用TLS提供的KDF(密钥衍生函数)的方式计算出来的。对于TLS 1.3,使用的KDF是在《TLS13》的第7.1章中介绍的HKDF-Expand-Label
函数,协商出的加密套件中的哈希函数也会被用到。在QUIC中所有使用HKDF-Expand-Label
的地方都使用零长度的Context
参数。
注意,标签(label
参数)是以ASCII(详见《ASCII》)字节的形式编码的字符串,其中不包含引号或任何末尾NUL
字节。
为了和QUIC一起使用,其他版本的TLS必须提供类似的函数。
为了生成AEAD密钥,KDF的输入是当前密级的秘密值和值为quic key
的标签;衍生初始化向量(Initialization Vector
,IV)时,使用的标签值为quic iv
;详见第5.3章。头部保护密钥使用的是值为quic hp
的标签;详见第5.4章。使用以上标签能够区分QUIC和TLS的密钥;详见第9.6章。
quic key
和quic hp
都被用来生成密钥,所以与这些标签一道交给HKDF-Expand-Label
函数的Length
字段的值是由AEAD或头部保护算法的密钥长度决定的。和quic iv
一起使用的Length
字段的值是AEAD随机数的最小长度,但不能小于8字节;详见《AEAD》。
总是使用来自TLS 1.3的HKDF-Expand-Label
函数作为用于初始秘密值的KDF;详见第5.2章。
5.2. 初始秘密值
数据包保护的过程适用于初始数据包,但是要使用的秘密值是从客户端首个初始数据包的目标连接ID字段衍生来的。
这个秘密值是通过对值为0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a
的盐和值为目标连接ID的输入密钥材料(IKM)应用HKDF-Extract
(详见《HKDF》的第2.2章)来决定的。这能生成一个起中间作用的伪随机密钥(PRK),它被用来衍生出两个分别用于发送和接收的秘密值。
客户端用于构造初始数据包的秘密值是通过将该PRK和值为client in
的标签输入至来自TLS(详见《TLS13》)的HKDF-Expand-Label
函数来生成的,这会产生一个32字节长的秘密值。服务器以同样的形式并使用值为server in
的标签来构造数据包。衍生初始秘密值和密钥时,HKDF使用的哈希函数是SHA-256(详见SHA)。
该过程的伪代码如下:
initial_salt = 0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a
initial_secret = HKDF-Extract(initial_salt,
client_dst_connection_id)
client_initial_secret = HKDF-Expand-Label(initial_secret,
"client in", "",
Hash.length)
server_initial_secret = HKDF-Expand-Label(initial_secret,
"server in", "",
Hash.length)
HKDF-Expand-Label
使用的连接ID是客户端发送的初始数据包里的目标连接ID。它会是一个随机选择的值,除非客户端是在收到重试数据包后才创建的初始数据包,那么这时的目标连接ID是由服务器选择的。
将来版本的QUIC应该使用一个新的盐值,从而确保每个QUIC版本的密钥互不相同。这会使得仅能识别一种QUIC版本的中间设备无法读取或修改将来版本的数据包的内容。
对于初始数据包,必须使用在TLS 1.3中定义的HKDF-Expand-Label
函数,即便可使用的TLS版本中并不包含TLS 1.3。
当服务器发送重试数据包以使用由服务器选择的连接ID时,用于构建后续初始数据包的秘密值会发生变化。在客户端为了响应来自服务器的初始数据包而更改目标连接ID时,这些秘密值不会发生变化。
注意:目标连接ID字段的长度可以是不超过20字节的任意值,包括零长度,零长度的情况会在服务器发送具有零长度的源连接ID字段的重试数据包时出现。在启动重试流程后,初始密钥就不能使得客户端确信服务器接收到了它的数据包,所以客户端必须依靠包含重试数据包的通信才能验证服务器地址;详见《QUIC传输》的第8.1章。
附录A中展示了作为样例的初始数据包。
5.3. AEAD的使用
QUIC数据包保护所用的带有关联数据的认证加密(AEAD)函数(详见《AEAD》)是在对TLS连接进行协商时产生的。例如,如果TLS要使用TLS_AES_128_GCM_SHA256
这一组加密套件,那么就要使用AEAD_AES_128_GCM
函数。
QUIC可以使用在《TLS13》中定义的任意一组加密套件,但是TLS_AES_128_CCM_8_SHA256
除外。除非QUIC为某组加密套件定义了头部保护方案,否则必须不对此套件进行协商。本文档为《TLS13》中定义的除TLS_AES_128_CCM_8_SHA256
外的所有加密套件都定义了头部保护方案。这些加密套件都具有16字节的认证标签并且会生成比输入要长16字节的输出。
对于一个提供了不受终端支持的加密套件的ClientHello
(客户端问候),终端必须不拒绝它,否则将来的QUIC版本将无法部署新加密套件。此要求同样适用于TLS_AES_128_CCM_8_SHA256
。
当构建数据包时,AEAD函数会在进行头部保护前被应用;详见第5.4章。未经保护的数据包头部是关联数据(A)的一部分。在处理数据包时,终端首先移除头部保护。
数据包的密钥和IV的计算方法如第5.1章所述。随机数,N,是通过组合数据包保护的IV和数据包号的方式来构造的。以网络字节序重建的62位QUIC数据包号会以在左侧补零的方式被扩充至IV的长度。扩充后的数据包号与IV的按位异或结果就是AEAD的随机数。
AEAD的关联数据,A,就是QUIC头部的内容,无论短包头还是长包头都是从首个字节开始,结束于且包含未受保护的数据包号。
AEAD的输入明文,P,就是QUIC数据包的载荷,如《QUIC传输》中所述。
AEAD的输出密文,C,会代替P被传输至对端。
一些AEAD函数对于在相同密钥和IV下能够加密的数据包数量有限制;详见第6.6章。这个限制值可能会低于数据包号的数量限制。在超出当前使用的AEAD所设的任何限制前,终端必须发起密钥更新(详见第6章)。
5.4. 头部保护
QUIC数据包头部的一部分,尤其是数据包号字段,会受到某个密钥的保护,这个密钥单独衍生自数据包保护密钥和IV。使用值为quic hp
的标签衍生的这个密钥被用于对那些没有暴露给路径上设备的字段提供可信度保护。
这项保护应用于首个字节的几个最低有效位,以及数据包号字段。对于长包头数据包,首个字节的四个最低有效位会受到保护;对于短包头数据包,首个字节的五个最低有效位会受到保护。不管是哪种形式的头部,都会覆盖到保留比特位和数据包号长度字段;短包头数据包的密钥阶段比特位也会受到保护。
在连接期间,即便是在经过密钥更新后(详见第6章),使用的头部保护密钥也保持不变。这使得头部保护可以被用来保护密钥阶段。
头部保护的过程不适用于重试数据包或版本协商数据包,这类数据包不包含受保护的载荷或任何会被此过程保护的字段。
5.4.1. 应用头部保护的过程
头部保护会在数据包保护之后被应用(详见第5.3章)。数据包的密文会被采样并被用作某个加密算法的输入。使用的算法取决于协商出的AEAD。
该算法的输出是一个5字节的掩码,这个掩码会以按位异或的方式被应用到受保护的头部字段。数据包首个字节的几个最低有效位会被掩码首个字节的对应最低有效位掩饰起来,同时数据包号会被剩余字节掩饰起来。掩码中未使用的字节不会被使用,这种情况可能在遇到较短的数据包号编码结果时发生。
图6展示了应用头部保护的样例算法。移除头部保护的过程只在决定数据包号长度(pn_length
)这一步的顺序上与此有所不同(这里使用^
来表示按位异或运算)。
mask = header_protection(hp_key, sample)
pn_length = (packet[0] & 0x03) + 1
if (packet[0] & 0x80) == 0x80:
# 长包头: 掩饰4个比特位
packet[0] ^= mask[0] & 0x0f
else:
# 短包头: 掩饰5个比特位
packet[0] ^= mask[0] & 0x1f
# pn_offset 是数据包号字段的起始位置
packet[pn_offset:pn_offset+pn_length] ^= mask[1:1+pn_length]
每一组加密套件都有专门的头部保护函数的定义;详见第5.4.3章和第5.4.4章。
图7展示了一个长包头数据包(初始数据包)和一个短包头数据包(1-RTT数据包)的样例。从图7中可以看出每种头部中被头部保护覆盖到的字段和受保护的数据包载荷中被采样的部分。
初始数据包 {
包头形式 (1) = 1,
固定比特位 (1) = 1,
长数据包类型 (2) = 0,
保留比特位 (2), # 受保护
数据包号长度 (2), # 受保护
版本 (32),
目标连接ID长度 (8),
目标连接ID (0..160),
源连接ID长度 (8),
源连接ID (0..160),
令牌长度 (i),
令牌 (..),
长度 (i),
数据包号 (8..32), # 受保护
数据包载荷 (0..24), # 跳过的部分
数据包载荷 (128), # 样本的部分
数据包载荷 (..) # 其余的部分
}
1-RTT数据包 {
包头形式 (1) = 0,
固定比特位 (1) = 1,
自旋比特位 (1),
保留比特位 (2), # 受保护
密钥阶段 (1), # 受保护
数据包号长度 (2), # 受保护
目标连接ID (0..160),
数据包号 (8..32), # 受保护
数据包载荷 (0..24), # 跳过的部分
数据包载荷 (128), # 样本的部分
数据包载荷 (..), # 其余的部分
}
在与QUIC共同使用TLS的某组加密套件前,必须为这组加密套件中的AEAD指定一种头部保护算法。本文档为AEAD_AES_128_GCM
、AEAD_AES_128_CCM
、AEAD_AES_256_GCM
(《AEAD》中定义了这些基于AES的AEAD)和AEAD_CHACHA20_POLY1305
(由《CHACHA》定义)定义了头部保护算法。在TLS选择加密套件之前,基于AES的头部保护(详见第5.4.3章)就会在使用AEAD_AES_128_GCM
的数据包保护中被用到。
5.4.2. 头部保护的采样
头部保护算法会用到头部保护密钥和来自数据包载荷字段的密文样本。
采样的字节数总是相同的,但是需要存在一种使得不知道数据包号字段的长度的接收方也有能力移除保护的方法。密文的样本采取自数据包号字段的起始位置向后偏移4个字节的位置。也就是说,在为头部保护采样数据包密文时,数据包号字段被假设为具有4个字节的长度(数据包号经编码后其长度的最大的可能值)。
终端必须丢弃过短以至于无法提供完整样本的数据包。
为了确保存在足够数据用于采样,数据包会被扩充从而使得经编码的数据包号与受保护载荷的长度之和至少要比头部保护所需样本的长度要大4个字节。《TLS13》中定义的加密套件——除TLS_AES_128_CCM_8_SHA256
外,因为本文档没有为它定义头部保护方案——都具有16字节的扩充量和16字节的头部保护样本长度要求。这意味着如果数据包号被编码至单个字节中,那么在未经保护的载荷中需要有至少3个字节长的帧,或者在被编码至双字节中时需要有至少2个字节的帧。
可以使用以下伪代码来决定采样的密文:
# pn_offset 是数据包号字段的起始位置
sample_offset = pn_offset + 4
sample = packet[sample_offset..sample_offset+sample_length]
可以这样计算短包头数据包的数据包号偏移:
而长包头数据包的数据包号偏移可以这样计算:
pn_offset = 7 + len(destination_connection_id) +
len(source_connection_id) +
len(payload_length)
if packet_type == Initial:
pn_offset += len(token_length) +
len(token)
举个例子,如果某个短包头数据包具有8字节的连接ID并且受到AEAD_AES_128_GCM
的保护,那么样本就是从字节13
至字节28
(包含两端,且索引的起始值为0
)。
单个UDP数据报中可以包含数个QUIC数据包。每个数据包都会被单独处理。
5.4.3. 基于AES的头部保护
本节为AEAD_AES_128_GCM
、AEAD_AES_128_CCM
和AEAD_AES_256_GCM
定义了数据包保护算法。AEAD_AES_128_GCM
和AEAD_AES_128_CCM
使用的是电子密码本(ECB)模式,128位的AES。AEAD_AES_256_GCM
使用的是ECB模式,256位的AES。AES的定义详见《AES》。
该算法从数据包中采样16字节长的密文。这份样本会被用作AES-ECB的输入。用伪代码的方式将头部保护函数定义为:
5.4.4. 基于ChaCha20的头部保护
若要使用AEAD_CHACHA20_POLY1305
,进行头部保护时就要使用原始的ChaCha20函数,它被定义于《CHACHA》的第2.4章。它会使用一个256位的密钥和采样自数据包保护的输出的16个字节样本。
密文样本的前4个字节被用作块计数器。如果ChaCha20的实现要求计数器的输入是一个32位的整数而不是字节序列,那么这时就要将字节序列解释为一个小端编码的值。
剩余的12个字节被用作随机数。如果ChaCha20的实现要求随机数的输入是三个32位整数组成的数组而不是字节序列,那么这时就要将随机数的字节解释为一串小端编码的32位整数。
通过使用ChaCha20来保护5个值为0
的字节,可以得到加密掩码。用伪代码的方式将头部保护函数定义为:
5.5. 接收受保护的数据包
一旦终端成功地接收到了具有某个数据包号的数据包,那么对于相同数据包号空间中具有更高数据包号的所有数据包,如果终端无法使用相同的密钥或,如果存在密钥更新,后续的数据包保护密钥来移除它们的保护,那么它必须丢弃这些具有更高数据包号的数据包;详见第6章。类似地,看上去应该触发密钥更新却无法被成功地移除保护的数据包必须被丢弃。
无法移除数据包保护并不意味着受到了攻击或对端处出现了违背协议的错误。因为在数据包被严重延误时,QUIC使用的截断数据包号的编码方式有可能造成数据包号被错误地解码。
5.6. 0-RTT密钥的使用
如果可以使用0-RTT密钥(详见第4.6.1章),那么在缺乏针对重放攻击的保护措施的情况下就必须限制它们的使用以避免针对QUIC协议的重放攻击。
在《QUIC传输》定义的各种帧中,流帧、重置流帧、停止发送帧和连接关闭帧在与0-RTT一起使用时可能是不安全的,因为它们携带着应用数据。在0-RTT中接收到的应用数据会使得客户端处的应用重复处理相同数据,而不是只处理一次。客户端如果重复处理了经重放的应用数据,那么有可能产生意外的副作用。因此客户端必须不将0-RTT用于应用数据,除非正在运行的应用有意要求这么做。
使用QUIC的应用协议必须提供一份文件并在其中定义允许使用0-RTT的范围;否则,0-RTT只能被用于传递没有携带应用数据的QUIC帧。例如,HTTP对应的这份内容被描述于《HTTP-REPLAY》中,并且被使用在HTTP/3里;详见《QUIC-HTTP》的第10.9章。
尽管重放数据包可能会引发额外的连接尝试,但是处理经重放的却未携带应用数据的帧的影响仅限于改变相关连接的状态。使用经重放的数据包无法使得TLS握手成功完成。
在TLS握手完成前,客户端可以对它发送的数据施加额外的限制。
否则,客户端将0-RTT密钥与1-RTT密钥等同对待,只不过它不能用0-RTT密钥发送特定类型的帧;详见《QUIC传输》的第12.5章。
如果客户端接收到了能够表明服务器已经接受0-RTT数据的信号,那么它可以继续发送0-RTT数据,直到它接收到了服务器的所有握手消息。如果客户端接收到了能够表明0-RTT数据被拒绝的信号,那么它应该停止发送0-RTT数据。
服务器必须不使用0-RTT密钥来保护数据包;它使用1-RTT密钥来保护对于0-RTT数据包的确认。客户端必须不尝试解密它接收到的0-RTT数据包,而是必须丢弃它们。
一旦客户端已经建立了1-RTT密钥,那么它必须不再发送0-RTT数据包。
注意: 0-RTT数据可能在被服务器接收到时就得到确认,但是包含对于0-RTT数据的确认的数据包可能无法被客户端移除数据包保护,直到TLS握手完成。移除数据包保护所必需的1-RTT密钥只有在客户端接收到服务器所有的握手消息后才能被衍生出来。
5.7. 接收乱序的受保护数据包
因为有乱序和丢包的情况存在,受保护的数据包可能在终端接收到最后的TLS握手消息前就被接收到了。这时客户端还无法解密来自服务器的1-RTT数据包,或服务器还无法解密来自客户端的1-RTT数据包。任一终端必须不在握手完成前解密来自对端的1-RTT数据包。
即使服务器在接收到来自客户端的首条握手消息后就能建立1-RTT密钥,但它还不能确信此时客户端的状态:
因此,服务器在握手完成前对1-RTT密钥的使用目的受限为数据的发送。服务器在TLS握手完成前必须不处理传入的受1-RTT密钥保护的数据包。因为发送确认就表明数据包中的所有帧都已经被处理了,所以服务器在TLS握手完成前不能发送对1-RTT数据包的确认。已接收到的受1-RTT密钥保护的数据包可以被存储起来,在将来握手完成时再解密和使用它们。
注意:TLS的实现可能会在握手完成前就提供所有1-RTT秘密值。在握手完成前,QUIC的实现即使获得了1-RTT读取密钥,也不能使用这些密钥。
服务器必须等待客户端的Finished
(已结束)消息的这项要求意味着服务器依赖于那条消息被成功交付到服务器。客户端可以避免这种依赖暗含的队头阻塞问题,方法是将它的1-RTT数据包和一个包含着携带Finished
消息的加密帧的握手数据包合并,直到其中一个握手数据包被确认。这使得服务器可以立即处理那些数据包。
服务器可能在接收到TLS的ClientHello
前就接收到受0-RTT密钥保护的数据包。服务器可以保留这些数据包,并期待能接收到ClientHello
以将它们解密。
客户端通常会在握手完成的同时得到1-RTT密钥。即使它拥有1-RTT的秘密值,客户端也必须不在TLS握手完成前处理传入的受1-RTT密钥保护的数据包。
5.8. 重试数据包的完整性
重试数据包(详见《QUIC传输》的第17.2.5章)携带着重试完整性标签,该标签具有两项属性:它使得被网络意外破坏的数据包可以被丢弃,同时只有观察到初始数据包的实体才能发送合法的重试数据包。
重试完整性标签是一个长度为128位的字段,它是将以下参数作为AEAD_AES_128_GCM
(详见《AEAD》)的输入而计算出的结果。
-
密钥,K,是一个长度为128位的固定值
0xbe0c690b9f66575a1d766b54e368c84e
。 -
随机数,N,是一个长度为96位的固定值
0x461599d35d632bf2239825bb
。 -
明文,P,为空。
-
关联数据,A,是重试伪数据包的内容,如图8所示。
密钥和随机数的值是通过对值为0xd9c9943e6101fd200021506bcc02814c73030f25c79d71ce876eca876e6fca8e
的秘密值和值分别为quic key
与quic iv
的标签调用HKDF-Expand-Label
而衍生出来的(详见第5.1章)。
重试伪数据包 {
原始目标连接ID长度 (8),
原始目标连接ID (0..160),
包头形式 (1) = 1,
固定比特位 (1) = 1,
长数据包类型 (2) = 3,
未使用 (4),
版本 (32),
目标连接ID长度 (8),
目标连接ID (0..160),
源连接ID长度 (8),
源连接ID (0..160),
重试令牌 (..),
}
重试伪数据包不会被实际发送。它是通过对被实际发送的重试数据包移除重试完整性标签,再追加以下两个字段的方式来构建出来的:
- 原始目标连接ID长度(ODCID Length):
-
原始目标连接ID长度字段中包含了跟在它后方的原始目标连接ID字段以字节为单位的长度,它被编码为一个8位无符号整型值。
- 原始目标连接ID(Original Destination Connection ID):
-
原始目标连接ID包含了这个重试数据包正在响应的初始数据包中目标连接ID字段的值。这个字段的长度在原始目标连接ID长度字段中给出。这个字段的存在确保了合法的重试数据包只能由观察到初始数据包的实体构造出来。
6. 密钥更新
当握手完成时(详见第4.1.2章),终端可以发起密钥更新。
使用密钥阶段比特位可以区分用于保护数据包的数据包保护密钥。密钥阶段比特位在第一组1-RTT数据包上被初始化为状态0
,并在后续每次密钥更新时切换状态。
密钥阶段比特位使得接收方即使没有接收到第一个发送的会引发密钥材料变化的数据包,也能检测到这种变化。注意到密钥阶段比特位变化的终端会更新密钥,再解密那个比特位发生变化的数据包。
发起密钥更新会导致两侧终端均更新密钥。这与TLS不同,终端在后者中可以独立更新密钥。
这项机制代替了TLS的密钥更新机制,后者依赖于受到1-RTT加密密钥保护的KeyUpdate
(密钥更新)消息。终端必须不发送TLS的KeyUpdate
消息。终端必须将受到TLS的KeyUpdate
消息的情况视作类型为0x010a
的连接错误,它等价于TLS中致命级别的unexpected_message
(意外的消息)警报;详见第4.8章。
图9展示了密钥更新的过程,其中初始时使用的那组密钥(标记为@M
)被更新后的密钥(标记为@N
)代替了。密钥阶段比特位的值用中括号[]
来表示。
6.1. 发起密钥更新
终端为数据包保护维护着相互独立的读取秘密值和写入秘密值。终端通过更新数据包的写入秘密值并用它去保护新数据包的方式来发起密钥更新。终端如《TLS13》的第7.2章中所展示的那样,从已有的写入秘密值创建新的写入秘密值。这会用到由TLS提供的KDF函数和值为quic ku
的标签。该秘密值会如第5.1章中所述的那样,创建出相应的密钥和IV。而头部保护密钥不会被更新。
举个例子,在使用TLS 1.3时,为了更新写入密钥,要这样使用HKDF-Expand-Label
:
终端会切换密钥阶段比特位的状态,并将更新后的密钥和IV用于保护后续数据包。
在握手已确认(详见第4.1.2章)前,终端必须不发起密钥更新。在终端接收到对于受当前密钥阶段的密钥保护的数据包的确认前,它必须不发起后续的密钥更新。这确保了再一次发起密钥更新前,当前密钥在两侧终端处均为可用状态。要做到这一点,可以追踪用各个密钥阶段的密钥发送的最小数据包号,以及在1-RTT空间中的最大已确认数据包号:一旦后者大于等于前者,就可以再发起一次密钥更新。
注意:非1-RTT数据包的密钥从不会被更新;它们的密钥一定是从TLS握手状态中衍生出来的。
发起密钥更新的终端还会更新用于接收数据包的密钥。这些密钥会在更新后被用于处理对端发送的数据包。
在成功移除了使用新密钥发送的数据包的保护前,终端必须保留旧密钥。在成功移除了使用新密钥发送的数据包的保护后,终端应该将旧密钥保留一段时间。过早地弃用旧密钥会导致延误的数据包被丢弃。丢弃数据包的行为会被对端当作数据包遭遇了丢包,反而会影响性能。
6.2. 响应密钥更新
在接收到对于受当前密钥阶段的密钥保护的数据包的确认后,对端就可以发起密钥更新。终端在处理到一个密钥阶段的值与它曾发出的最后一个数据包中的值不同的数据包时,会将该情况视作为密钥更新。为了处理这个数据包,终端要使用下一阶段中的数据包保护密钥和IV。有关创建这些密钥时的考量,详见第6.3章。
如果某个数据包可以使用下一阶段中的密钥和IV来处理,就说明对端发起了密钥更新。作为响应,终端必须将它的发送密钥更新到相应的密钥阶段,如第6.1章所述。在发送对于使用了更新后的密钥来接收的数据包的确认前,必须更新发送密钥。通过用受更新后的密钥保护的数据包来确认触发密钥更新的数据包的方式,终端发送出密钥更新完成的信号。
终端可以根据自己平时的数据包发送习惯,推迟发送数据包或确认:没有必要为了响应密钥更新而立即创建新数据包。终端发送的下一个数据包会用上更新后的密钥。下一个包含确认的数据包会使得密钥更新完成。如果终端检测到了第二次密钥更新,但它这时还没有使用更新后的密钥发送包含对于触发前一次密钥更新的数据包的响应的数据包,那么这就表明对端没有等待确认就更新了密钥两次。终端可以将这样的连续密钥更新行为视作类型为KEY_UPDATE_ERROR
(密钥更新错误)的连接错误。
如果终端接收到的确认来自受旧密钥保护的数据包,但确认是对于受新密钥保护的数据包的,那么它可以将这种情况视作类型为KEY_UPDATE_ERROR
的连接错误。这表明对端已经接收到并且确认了发起密钥更新的数据包,但是在响应时没有更新密钥。
6.3. 对创建接收密钥的计时
终端在响应密钥更新时必须不产生在计时侧信道上可能表明密钥阶段比特位的有效性(详见第9.5章)的信号。当未批准进行密钥更新时,终端可以假装进行密钥更新,并使用随机化的数据包保护密钥来代替因更新而被弃用的密钥。使用随机化的密钥确保了移除数据包保护的尝试不会造成计时结果的变化,且具有无效的密钥阶段比特位的数据包会被正确地拒绝。
在接收数据包时,创建新的数据包保护密钥的过程可能透露出这期间进行了密钥更新。终端可以将创建新密钥作为数据包处理的一部分,但是这会产生计时上的信号,这个信号会被攻击者用于学习密钥更新的发生时机,从而泄露密钥阶段比特位的值。
通常,当前阶段中的和下一阶段中的数据包保护密钥应该都是对终端可用的。在密钥更新完成后不超过PTO的短暂时间里,终端可以延迟下一组接收用的数据包保护密钥的建立。这使得终端只需要维护两组接收密钥;详见第6.5章。
下一组数据包保护密钥一旦生成就应该被持续保留,哪怕接收到的数据包后来被丢弃了。看上去包含密钥更新的数据包很容易伪造,尽管密钥更新的过程不需要大量计算资源,但是攻击者还是可以触发它来进行拒绝服务攻击。
因此,终端必须有能力维护两组用于接收数据包的数据包保护密钥:当前阶段中的和下一阶段中的。维护除此之外的先前的密钥可能会提升性能,但是这是不必要的。
6.4. 使用更新后的密钥来发送
终端绝不会发送受旧密钥保护的数据包。只有当前阶段的密钥会被用到。切换到新密钥后,用于保护数据包的旧密钥可以被立即弃用。
用于保护具有更大数据包号的数据包的密钥必须与用于保护更小数据包号数据包的密钥一致,或比后者更新。如果终端用旧密钥移除了保护,但具有更小数据包号的数据包是受新密钥保护的,那么它必须将这种情况视作类型为KEY_UPDATE_ERROR
的连接错误。
6.5. 使用不同的密钥来接收
在密钥更新期间,可能会接收到受旧密钥保护的数据包,它们因为被网络延误而刚刚抵达。保留旧的数据包保护密钥使得这些数据包能够被成功处理。
由于受来自下一阶段的密钥保护的数据包会与受来自上一阶段的密钥保护的数据包使用相同的密钥阶段,要想处理受旧密钥保护的数据包,就有必要区分这两种数据包。这可以通过使用数据包号来做到。如果重建出来的数据包号比当前密钥阶段的任一数据包号要小,那么这些数据包就要使用上一阶段的数据包保护密钥;如果重建出来的数据包号比当前密钥阶段的任一数据包号要大,那么这些数据包就要使用下一阶段的数据包保护密钥。
为了确保在上一阶段、当前阶段和下一阶段的数据包保护密钥间选择的过程不会在计时侧信道上泄露最终用于移除数据包保护的是哪一组密钥,必须谨慎操作。有关更多信息,详见第9.5章。
作为替代方案,终端可以仅维护两组数据包保护密钥,只需在经过对网络重排序来说充足的时间后将上一阶段的密钥替换为下一阶段的密钥。在这种情况下,只需要密钥阶段比特位就足以选择密钥。
在将下一阶段的接收密钥提升为当前阶段后,终端可以在建立再下一组数据包保护密钥前等待一段约为一个探测包超时(PTO;详见《QUIC恢复》)的时间。这组被推迟建立的新密钥可以在经过这段等待时间后才替换上一阶段的旧密钥。只不过有一个缺点,即PTO是一个主观的测量结果——也就是说,对端对于RTT的预估可能不同——这段等待时间既应该足够长以使得所有乱序数据包哪怕被确认了也都会被对端认定为丢包,又应该足够短以使得对端可以发起后续的密钥更新。
在对端保留旧密钥期间,对端可能无法解密发起密钥更新的数据包,终端需要允许这种情况的存在。在接收到能够表明先前的密钥更新已经被接收到的确认后,终端在发起密钥更新前应该等待一段时长为PTO三倍大小的时间。若没有留足时间,可能导致数据包被丢弃。
在接收到受新密钥保护的数据包后,终端保留旧的读取密钥的时长应该不超过PTO的三倍。在这段等待时间之后,旧的读取密钥和它们对应的秘密值应该被弃用。
6.6. AEAD的用量上限
本文档为AEAD设置了用量上限以确保在使用QUIC时哪怕过量使用AEAD也不会在通信的可信度和完整性上给予攻击者过多的优势。
TLS 1.3中定义的用量上限是为了抵御针对可信度的攻击,只有遵从这些限制,使用AEAD的保护才是有效的。认证加密中的完整性保护也依赖于对伪造数据包的尝试次数进行限制。TLS是以一遇到未通过认证校验的记录就关闭连接的方式做到这一点的。相比之下,QUIC会忽略无法通过认证的所有数据包,允许多次尝试伪造数据包。
QUIC为AEAD的可信度上限和完整性上限分别进行计数。在可信度方面,限制某个密钥可以加密的数据包数量。在完整性方面,限制某个连接中可以解密的数据包数量。下文是有关为各种AEAD算法施加限制的细节。
终端必须为每一组密钥单独计算加密数据包的数量。如果同一密钥的加密数据包总数超过了所选AEAD的可信度上限,那么终端必须停止使用这组密钥。在发送的受保护数据包数量超过所选AEAD允许的可信度上限前,终端必须发起密钥更新。如果无法进行密钥更新或触及了完整性上限,那么终端必须停止使用当前连接,并且以仅仅发送无状态重置的方式响应接收到的数据包。推荐终端在进入无法进行密钥更新的状态前立即用类型为AEAD_LIMIT_REACHED
(触及AEAD上限)的连接错误来关闭连接。
对于AEAD_AES_128_GCM
和AEAD_AES_256_GCM
,可信度上限为223
个加密数据包;详见附录B.1。对于AEAD_CHACHA20_POLY1305
,其可信度上限要比可能的数据包数量(262
)还大,因此不用考虑。对于AEAD_AES_128_CCM
,可信度上限是221.5
个加密数据包;详见附录B.2。
除了要为发送的数据包计数外,终端必须为在一条连接的存活期间内接收到但未通过认证的数据包计数。如果在某条连接中接收到但未通过认证的数据包总数,无论受什么密钥保护,超过了所选AEAD的完整性上限,那么终端必须立即用类型为AEAD_LIMIT_REACHED
的连接错误来关闭连接,并且不再处理更多数据包。
对于AEAD_AES_128_GCM
和AEAD_AES_256_GCM
,完整性上限为252
个非法数据包;详见附录B.1。对于AEAD_CHACHA20_POLY1305
,完整性上限为236
个非法数据包;详见《AEBounds》。对于AEAD_AES_128_CCM
,完整性上限为221.5
个非法数据包;详见附录B.2。应用这些限制能够降低攻击者成功伪造数据包的可能性;详见《AEBounds》、《ROBUST》和《GCM-MU》。
限制数据包尺寸的终端可以使用更高的可信度上限和完整性上限;有关细节详见附录B。
将来的分析与规范可以放松对于某AEAD的可信度上限或完整性上限。
可以被用于QUIC的任一TLS加密套件必须定义相关AEAD函数在可信度和完整性方面的用量上限。也就是说,这些限制必须准确指出允许被认证的数据包数量和允许未通过认证的数据包数量。提供一份分析如何计算该值的参考文献——并指出所有在此分析中使用到的假设——使得限制值能够变化以适应不同的使用条件。
6.7. 密钥更新的错误码
错误码KEY_UPDATE_ERROR
(0x0e
)被用于发送与密钥更新相关的错误的信号。
7. 初始消息的安全性
初始数据包并不受密钥的保护,因此它们可能会被攻击者篡改。QUIC提供的保护可以阻挡无法读取数据包的攻击者,但此保护并不足以抵御来自有能力观察和注入数据包的攻击者的攻击。部分形式的篡改——例如对TLS消息的修改——能被检测出来,但是另一些——例如对ACK帧的修改——则不能。
举例来说,攻击者可以注入一个包含虚假ACK帧的数据包,以使得某个数据包貌似还没被接收到,或者使得终端对连接的状态建立起错误印象(可以通过修改ACK延迟的方法)。注意,这样的数据包会使得本应合法的数据包被终端认定为重复而丢弃它们。对于初始数据包中包含的未经其他途径验证的数据,QUIC实现应该谨慎地使用它们。
攻击者还有可能篡改使用握手数据包传递的数据,但由于这类篡改依赖于对TLS握手消息的修改,所以它们会使得TLS握手失败。
8. QUIC中对TLS握手的调整
与QUIC一起使用时,TLS某些方面的行为会发生变化。
QUIC还要求TLS提供额外功能。除了协商加密参数外,TLS握手还要传递和认证QUIC的传输参数。
8.1. 协议协商
QUIC要求加密握手提供经认证的协议协商过程。TLS使用应用层协议协商(ALPN,详见《ALPN》)来选择应用协议。终端必须使用ALPN来协商应用协议,除非使用了其他机制来就应用协议达成一致。
在使用ALPN时,如果没有协商出应用协议,那么终端必须立即使用值为no_application_protocol
(无应用协议)的TLS警告(QUIC的错误码为0x0178
;详见第4.8章)来关闭连接(详见《QUIC传输》的第10.2章)。尽管《ALPN》中只规定了服务器要使用此警告,但是QUIC客户端在ALPN协商失败时必须使用类型为0x0178
的错误来终止连接。
应用协议可以限制它所操作的QUIC版本。服务器必须选择一个与客户端所选QUIC版本兼容的应用协议。服务器必须将无法选出兼容的应用协议的情况视作类型为0x0178
(相当于no_application_protocol
)的连接错误。类似地,客户端必须将服务器选择了不兼容的应用协议的情况视作类型为0x0178
的连接错误。
8.2. QUIC传输参数扩展
QUIC传输参数是使用TLS扩展来传递的。不同的QUIC版本可能为传输配置的协商定义不同方法。
在TLS握手中包含传输参数,就可以为这些值提供完整性保护。
quic_transport_parameters
(QUIC传输参数)扩展的extension_data
(扩展数据)字段中包含的是一个由正在使用的QUIC版本定义的值。
quic_transport_parameters
扩展是在握手期间使用ClientHello
(客户端问候)和EncryptedExtensions
(加密扩展)消息来传递的。终端必须发送quic_transport_parameters
扩展;接收到不带quic_transport_parameters
扩展的ClientHello
消息或EncryptedExtensions
消息的终端必须使用类型为0x016d
(相当于TLS中致命级别的missing_extension
警告,详见第4.8章)的错误来关闭连接。
传输参数在握手完成前就会变为可用。服务器可以在握手完成前就使用这些值。不过,直到握手完成,传输参数的值都是未经认证的,所以即使提前使用这些参数也不要依赖于它们的真实性。任何对于传输参数的篡改最终都会使得握手失败。
终端必须不在并未使用QUIC的TLS连接中(例如在《TLS13》中定义的基于TCP的TLS连接)发送此扩展。如果在非QUIC的传输中接收到了这个扩展,那么支持此扩展的终端必须发送致命级别的unsupported_extension
警告。
协商quic_transport_parameters
扩展会使得EndOfEarlyData
(早期数据结束)被移除;详见第8.3章。
8.3. 移除EndOfEarlyData消息
QUIC中不会使用TLS的EndOfEarlyData
消息。QUIC并不使用这条消息去标记0-RTT数据的结束位置,或去发送握手密钥发生变化的信号。
客户端必须不发送EndOfEarlyData
消息。服务器必须将在0-RTT数据包中接收到加密帧的情况视作类型为PROTOCOL_VIOLATION
(协议违背)的连接错误。
于是,EndOfEarlyData
不会出现在TLS握手的记录单(transcript
)中。
8.4. 禁止TLS的中间设备兼容模式
《TLS13》的附录D.4描述了TLS 1.3握手的一种替代方案,并将之作为某些中间设备上程序出错时的应对措施。TLS 1.3中的这种中间设备兼容模式需要将ClientHello
和ServerHello
(服务器问候)中的legacy_session_id
(兼容会话ID)字段设置为一个32字节长的值,随后发送change_cipher_spec
(更改加密设置)记录。这个字段和记录不传递任何具有语义的内容,并且会被忽略。
这个模式在QUIC中没有用处,因为它只适用于会干预基于TCP的TLS的中间设备。同时QUIC也没有提供任何传递change_cipher_spec
记录的方法。客户端必须不请求使用TLS 1.3的兼容模式。服务器应该将接收到legacy_session_id
字段非空的TLSClientHello
的情况视作类型为PROTOCOL_VIOLATION
的连接错误。
9. 关于安全性的考量
所有适用于TLS的关于安全性的考量同样适用于QUIC中的TLS。通读《TLS13》及其附录是理解QUIC的安全属性的最佳方式。
本章概述了特定于TLS集成在其他协议中时的一些重要的关于安全性的考量,不过本文档中的其余部分中还有许多与安全性相关的细节。
9.1. 会话的可关联性
TLS会话票据的使用使得服务器或其他实体能够将同一客户端创建的连接相互关联起来;有关细节详见第4.5章。
9.2. 与0-RTT相关的重放攻击
正如《TLS13》的第8章所述,只要使用了TLS早期数据就会被暴露于重放攻击之下。QUIC的0-RTT有着类似的风险。
终端必须实现并使用《TLS13》中描述的重放保护,然而这些保护并不被认为是完美的。因此,对于重放攻击的风险还需要更多考量。
QUIC并不易受到重放攻击,除非是利用QUIC传递的应用协议信息的攻击。基于在《QUIC传输》中定义的帧类型的QUIC协议状态管理机制不易受到重放攻击。QUIC帧的处理是幂等的,在帧被重放、乱序或遭遇丢包时不会引发连接状态异常。QUIC连接期间产生的副作用在连接的生命周期结束后就会失效,除非是那些由QUIC承载的应用协议产生的副作用。
TLS会话票据和地址验证令牌被用于在不同的连接间传递QUIC配置信息,尤其是,它使得服务器能够在连接建立和地址验证时高效地恢复状态数据。必须不使用它们在终端间传递应用语义;客户端必须将它们视作为内容不透明的值。如果这些令牌有可能被重用,那么它们就需要更强的针对重放攻击的保护。
在某条连接上接受0-RTT的服务器比不接受0-RTT的服务器要消耗更多的计算资源。服务器在接受0-RTT时需要考虑到重放的可能性以及其他相关资源的消耗。
管理与0-RTT相关的重放攻击的风险,其最终的责任在于应用协议。使用QUIC的应用协议必须描述该协议会怎样使用0-RTT以及用于抵御重放攻击的手段。对于重放风险的分析需要考虑所有会传递应用语义的QUIC协议特性。
要抵御重放攻击,完全禁用0-RTT是最有效的。
QUIC扩展必须描述重放攻击会如何影响扩展的行为,或禁止与0-RTT一起被使用。应用协议必须禁止使用会在0-RTT中传递应用语义的扩展,或提供抵御重放攻击的策略。
9.3. 数据包放大攻击的抵御
9.4. 对头部保护的分析
《NAN》分析了许多能保护随机数的认证加密算法,它们被称为“随机数隐藏”(HN)转换。本文档中的头部保护构建方法大体上可以算作是这些算法中的其中一种(HN1)。头部保护会在数据包保护AEAD之后被应用,它从AEAD的输出中采样一组字节样本(表达式中的sample
),并以此方法使用伪随机函数(表达式中的PRF
)来加密头部字段:
本文档中的头部保护的不同之处在于使用伪随机排列(PRP)来代替常规的PRF。然而,由于所有PRP都算作PRF(详见《IMC》),所以这种修改并不违背HN1的构建原则。
由于hp_key
与数据包保护密钥不同,因此头部保护能够达到《NAN》中描述的AE2安全级别,从而保护数据包头部中的字段。将来的基于相同构建方式的头部保护变体必须使用PRF来确保获得一致的安全保障。
多次使用相同的密钥和密文样本会有使头部保护失效的风险。使用相同的密钥和密文样本来保护两份不同的包头会泄露受保护字段的异或结果。假设以AEAD作为PRF,如果采样了L
个比特位,那么两份密文样本一致的概率约为2-L/2
,也就是所谓的生日攻击。对于在本文档中描述的算法,这个概率为264
分之一。
为了防止被攻击者修改,数据包头部会受到数据包保护的认证;整个数据包头部都是受认证的额外数据的一部分。受保护的字段有没有被篡改只能在移除数据包保护后才被检测出来。
9.5. 头部保护的计时侧信道
攻击者可以猜测数据包号或密钥阶段的值,并通过计时侧信道观察终端会不会接受这个值。类似地,对于数据包号长度也可以进行猜测和破解。如果数据包的接收方直接将数据包号重复的数据包丢弃,而没有尝试移除数据包保护,那么它们就会通过计时侧信道暴露此数据包号与一个已接收到的数据包匹配的事实。为了使认证过程不会对侧信道产生影响,必须完整地先移除头部保护、然后恢复数据包号,接着移除数据包保护,从而避免从计时或其他方面的侧信道泄露信息。
在数据包的发送方面,数据包载荷与数据包号的构建和保护过程必须不会从侧信道泄露数据包号或它的编码后长度。
在密钥更新期间,创建新密钥所花费的时间可能通过计时侧信道透露出有没有发生密钥更新。除此之外,当攻击者注入数据包时,该侧信道会泄露被注入的数据包里密钥阶段的值。在接收到密钥更新后,终端应该如第6.3章所述的那样,建立并储存下一组接受用的数据包保护密钥。通过在接收到密钥更新前就建立新密钥,数据包的接收就不会创建出会泄露密钥阶段的值的计时上的信号。
这项要求依赖于不在数据包处理期间建立新密钥,同时它可能需要终端维护三组接收用的数据包保护密钥:上一密钥阶段的、当前密钥阶段的和下一密钥阶段的。作为替代方案,终端可以选择延迟建立下一密钥阶段的数据包保护密钥,直到它弃用旧密钥为止,这使得它在同一时间只需要维护两组接收密钥。
9.6. 密钥的隔离性
在使用TLS时,会用到其核心的密钥衍生计划表。由于TLS握手消息被集成进了秘密值计算,QUIC传输参数扩展的使用能够确保握手密钥和1-RTT密钥与运行基于TCP的TLS的服务器所生成出来的密钥不一致。为了降低不同协议间出现密钥碰撞情况的可能性,有额外的措施来提高密钥的隔离性。
QUIC的数据包保护密钥和IV是使用与TLS中不同的标签来衍生的。
为了维持这种隔离性,新版本的QUIC应该为计算数据包保护密钥和IV的密钥,以及头部保护密钥,的衍生过程定义新的标签值。本版本的QUIC使用了字符串quic
。其他版本可以使用与版本相关的标签来替换该字符串。
初始秘密值使用的是由协商出的QUIC版本指定的密钥。新的QUIC版本应该为秘密值的计算定义新的盐值。
9.7. 随机性
QUIC依赖于终端生成安全的随机数的能力,这项能力既会被直接用在协议里的一些值上,如连接ID,也会经由TLS被使用到。有关安全的随机数生成的指导,详见《RFC4086》。
10. 关于IANA的考量
IANA已经在“TLS扩展类型值”注册表(详见《TLS-REGISTRIES》)中为quic_transport_parameters
(QUIC传输参数)扩展(有关定义详见第8.2章)注册了值为57
(也就是0x39
)的码点。
该扩展的受推荐一栏被标记为“是”。TLS 1.3一栏中包含CH(ClientHello
,客户端问候)和EE(EncryptedExtensions
,加密扩展)。
值 | 扩展名称 | TLS 1.3 | 受推荐 | 参考文献 |
---|---|---|---|---|
57 | quic_transport_parameters | CH, EE | 是 | 本文档 |
11. 参考文献
11.1. 规范性参考文献
McGrew, D., “An Interface and Algorithms for Authenticated Encryption”, RFC 5116, DOI 10.17487/RFC5116, January 2008, https://www.rfc-editor.org/info/rfc5116.
“Advanced encryption standard (AES)”, National Institute of Standards and Technology report, DOI 10.6028/nist.fips.197, November 2001, https://doi.org/10.6028/nist.fips.197.
Friedl, S., Popov, A., Langley, A., and E. Stephan, “Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension”, RFC 7301, DOI 10.17487/RFC7301, July 2014, https://www.rfc-editor.org/info/rfc7301.
Nir, Y. and A. Langley, “ChaCha20 and Poly1305 for IETF Protocols”, RFC 8439, DOI 10.17487/RFC8439, June 2018, https://www.rfc-editor.org/info/rfc8439.
Krawczyk, H. and P. Eronen, “HMAC-based Extract-and-Expand Key Derivation Function (HKDF)”, RFC 5869, DOI 10.17487/RFC5869, May 2010, https://www.rfc-editor.org/info/rfc5869.
Iyengar, J., Ed. and I. Swett, Ed., “QUIC Loss Detection and Congestion Control”, RFC 9002, DOI 10.17487/RFC9002, May 2021, https://www.rfc-editor.org/info/rfc9002.
Iyengar, J., Ed. and M. Thomson, Ed., “QUIC: A UDP-Based Multiplexed and Secure Transport”, RFC 9000, DOI 10.17487/RFC9000, May 2021, https://www.rfc-editor.org/info/rfc9000.
[RFC2119] RFC文档中用于指出要求级别的关键字
Bradner, S., “Key words for use in RFCs to Indicate Requirement Levels”, BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, https://www.rfc-editor.org/info/rfc2119.
Eastlake 3rd, D., Schiller, J., and S. Crocker, “Randomness Requirements for Security”, BCP 106, RFC 4086, DOI 10.17487/RFC4086, June 2005, https://www.rfc-editor.org/info/rfc4086.
[RFC8174] RFC2119中关键字大写与小写的歧义
Leiba, B., “Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words”, BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, https://www.rfc-editor.org/info/rfc8174.
Dang, Q., “Secure Hash Standard”, National Institute of Standards and Technology report, DOI 10.6028/nist.fips.180-4, July 2015, https://doi.org/10.6028/nist.fips.180-4.
Salowey, J. and S. Turner, “IANA Registry Updates for TLS and DTLS”, RFC 8447, DOI 10.17487/RFC8447, August 2018, https://www.rfc-editor.org/info/rfc8447.
Rescorla, E., “The Transport Layer Security (TLS) Protocol Version 1.3”, RFC 8446, DOI 10.17487/RFC8446, August 2018, https://www.rfc-editor.org/info/rfc8446.
11.2. 资料性参考文献
Luykx, A. and K. Paterson, “Limits on Authenticated Encryption Use in TLS”, 28 August 2017, https://www.isg.rhul.ac.uk/~kp/TLS-AEbounds.pdf.
Cerf, V., “ASCII format for network interchange”, STD 80, RFC 20, DOI 10.17487/RFC0020, October 1969, https://www.rfc-editor.org/info/rfc20.
Jonsson, J., “On the Security of CTR + CBC-MAC”, Selected Areas in Cryptography, SAC 2002, Lecture Notes in Computer Science, vol 2595, pp. 76-93, DOI 10.1007/3-540-36492-7_7, 2003, https://doi.org/10.1007/3-540-36492-7_7.
Ghedini, A. and V. Vasiliev, “TLS Certificate Compression”, RFC 8879, DOI 10.17487/RFC8879, December 2020, https://www.rfc-editor.org/info/rfc8879.
Hoang, V., Tessaro, S., and A. Thiruvengadam, “The Multi-user Security of GCM, Revisited: Tight Bounds for Nonce Randomization”, CCS ‘18: Proceedings of the 2018 ACM SIGSAC Conference on Computer and Communications Security, pp. 1429-1440, DOI 10.1145/3243734.3243816, 2018, https://doi.org/10.1145/3243734.3243816.
Thomson, M., Nottingham, M., and W. Tarreau, “Using Early Data in HTTP”, RFC 8470, DOI 10.17487/RFC8470, September 2018, https://www.rfc-editor.org/info/rfc8470.
Benjamin, D., “Using TLS 1.3 with HTTP/2”, RFC 8740, DOI 10.17487/RFC8740, February 2020, https://www.rfc-editor.org/info/rfc8740.
Katz, J. and Y. Lindell, “Introduction to Modern Cryptography, Second Edition”, ISBN 978-1466570269, 6 November 2014.
Bellare, M., Ng, R., and B. Tackmann, “Nonces Are Noticed: AEAD Revisited”, Advances in Cryptology - CRYPTO 2019, Lecture Notes in Computer Science, vol 11692, pp. 235-265, DOI 10.1007/978-3-030-26948-7_9, 2019, https://doi.org/10.1007/978-3-030-26948-7_9.
Bishop, M., Ed., “Hypertext Transfer Protocol Version 3 (HTTP/3)”, Work in Progress, Internet-Draft, draft-ietf-quic-http-34, 2 February 2021, https://tools.ietf.org/html/draft-ietf-quic-http-34.
Rescorla, E., “HTTP Over TLS”, RFC 2818, DOI 10.17487/RFC2818, May 2000, https://www.rfc-editor.org/info/rfc2818.
Cooper, D., Santesson, S., Farrell, S., Boeyen, S., Housley, R., and W. Polk, “Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile”, RFC 5280, DOI 10.17487/RFC5280, May 2008, https://www.rfc-editor.org/info/rfc5280.
Fischlin, M., Günther, F., and C. Janson, “Robust Channels: Handling Unreliable Networks in the Record Layers of QUIC and DTLS 1.3”, 16 May 2020, https://eprint.iacr.org/2020/718.
附录A. 数据包保护样例
本章展示了数据包保护的一些例子,以便于QUIC的实现可以逐步验证自己的计算结果。这里为客户端和服务器分别定义了初始数据包的样例,以及一个重试数据包。这些数据包使用了一个由客户端选择的8字节长的目标连接ID,其值为0x8394c8f03e515708
。样例中还包含了计算过程的一些中间值。所有值都是以十六进制表示的。
A.1. 密钥
在HKDF-Expand-Label
函数的执行期间生成的标签(也就是HkdfLabel.label
),以及传给HKDF-Expand
函数的参数为:
client in
: 00200f746c73313320636c69656e7420696e00
server in
: 00200f746c7331332073657276657220696e00
quic key
: 00100e746c7331332071756963206b657900
quic iv
: 000c0d746c733133207175696320697600
quic hp
: 00100d746c733133207175696320687000
初始秘密值是通用的:
initial_secret = HKDF-Extract(initial_salt, cid)
= 7db5df06e7a69e432496adedb0085192
3595221596ae2ae9fb8115c1e9ed0a44
用于保护客户端数据包的秘密值为:
client_initial_secret
= HKDF-Expand-Label(initial_secret, "client in", "", 32)
= c00cf151ca5be075ed0ebfb5c80323c4
2d6b7db67881289af4008f1f6c357aea
key = HKDF-Expand-Label(client_initial_secret, "quic key", "", 16)
= 1f369613dd76d5467730efcbe3b1a22d
iv = HKDF-Expand-Label(client_initial_secret, "quic iv", "", 12)
= fa044b2f42a3fd3b46fb255c
hp = HKDF-Expand-Label(client_initial_secret, "quic hp", "", 16)
= 9f50449e04a0e810283a1e9933adedd2
用于保护服务器数据包的秘密值为:
server_initial_secret
= HKDF-Expand-Label(initial_secret, "server in", "", 32)
= 3c199828fd139efd216c155ad844cc81
fb82fa8d7446fa7d78be803acdda951b
key = HKDF-Expand-Label(server_initial_secret, "quic key", "", 16)
= cf3a5331653c364c88f0f379b6067e37
iv = HKDF-Expand-Label(server_initial_secret, "quic iv", "", 12)
= 0ac1493ca1905853b0bba03e
hp = HKDF-Expand-Label(server_initial_secret, "quic hp", "", 16)
= c206b8d9b9f0f37644430b490eeaa314
A.2. 客户端初始数据包
客户端会发送初始数据包。该数据包未经保护的载荷中包含着加密帧,以及足够多的填充帧以使得载荷长度达到1162字节:
060040f1010000ed0303ebf8fa56f129 39b9584a3896472ec40bb863cfd3e868
04fe3a47f06a2b69484c000004130113 02010000c000000010000e00000b6578
616d706c652e636f6dff01000100000a 00080006001d00170018001000070005
04616c706e0005000501000000000033 00260024001d00209370b2c9caa47fba
baf4559fedba753de171fa71f50f1ce1 5d43e994ec74d748002b000302030400
0d0010000e0403050306030203080408 050806002d00020101001c0002400100
3900320408ffffffffffffffff050480 00ffff07048000ffff08011001048000
75300901100f088394c8f03e51570806 048000ffff
在未经保护的头部中,长度字段的值表示着后方数据的总长度,即1182字节:4字节长的数据包号,1162字节长的帧,还有16字节长的认证标签。头部中还包含了连接ID和值为2
的数据包号:
对载荷进行保护后,其输出密文会被头部保护采样。因为头部中的数据包号被编码为四字节,所以受保护载荷的前16个字节被作为样本(sample
),应用在头部保护中:
sample = d1b1c98dd7689fb8ec11d242b123dc9b
mask = AES-ECB(hp, sample)[0..4]
= 437b9aec36
header[0] ^= mask[0] & 0x0f
= c0
header[18..21] ^= mask[1..4]
= 7b9aec34
header = c000000001088394c8f03e5157080000449e7b9aec34
最后,经保护的数据包的内容为:
c000000001088394c8f03e5157080000 449e7b9aec34d1b1c98dd7689fb8ec11
d242b123dc9bd8bab936b47d92ec356c 0bab7df5976d27cd449f63300099f399
1c260ec4c60d17b31f8429157bb35a12 82a643a8d2262cad67500cadb8e7378c
8eb7539ec4d4905fed1bee1fc8aafba1 7c750e2c7ace01e6005f80fcb7df6212
30c83711b39343fa028cea7f7fb5ff89 eac2308249a02252155e2347b63d58c5
457afd84d05dfffdb20392844ae81215 4682e9cf012f9021a6f0be17ddd0c208
4dce25ff9b06cde535d0f920a2db1bf3 62c23e596d11a4f5a6cf3948838a3aec
4e15daf8500a6ef69ec4e3feb6b1d98e 610ac8b7ec3faf6ad760b7bad1db4ba3
485e8a94dc250ae3fdb41ed15fb6a8e5 eba0fc3dd60bc8e30c5c4287e53805db
059ae0648db2f64264ed5e39be2e20d8 2df566da8dd5998ccabdae053060ae6c
7b4378e846d29f37ed7b4ea9ec5d82e7 961b7f25a9323851f681d582363aa5f8
9937f5a67258bf63ad6f1a0b1d96dbd4 faddfcefc5266ba6611722395c906556
be52afe3f565636ad1b17d508b73d874 3eeb524be22b3dcbc2c7468d54119c74
68449a13d8e3b95811a198f3491de3e7 fe942b330407abf82a4ed7c1b311663a
c69890f4157015853d91e923037c227a 33cdd5ec281ca3f79c44546b9d90ca00
f064c99e3dd97911d39fe9c5d0b23a22 9a234cb36186c4819e8b9c5927726632
291d6a418211cc2962e20fe47feb3edf 330f2c603a9d48c0fcb5699dbfe58964
25c5bac4aee82e57a85aaf4e2513e4f0 5796b07ba2ee47d80506f8d2c25e50fd
14de71e6c418559302f939b0e1abd576 f279c4b2e0feb85c1f28ff18f58891ff
ef132eef2fa09346aee33c28eb130ff2 8f5b766953334113211996d20011a198
e3fc433f9f2541010ae17c1bf202580f 6047472fb36857fe843b19f5984009dd
c324044e847a4f4a0ab34f719595de37 252d6235365e9b84392b061085349d73
203a4a13e96f5432ec0fd4a1ee65accd d5e3904df54c1da510b0ff20dcc0c77f
cb2c0e0eb605cb0504db87632cf3d8b4 dae6e705769d1de354270123cb11450e
fc60ac47683d7b8d0f811365565fd98c 4c8eb936bcab8d069fc33bd801b03ade
a2e1fbc5aa463d08ca19896d2bf59a07 1b851e6c239052172f296bfb5e724047
90a2181014f3b94a4e97d117b4381303 68cc39dbb2d198065ae3986547926cd2
162f40a29f0c3c8745c0f50fba3852e5 66d44575c29d39a03f0cda721984b6f4
40591f355e12d439ff150aab7613499d bd49adabc8676eef023b15b65bfc5ca0
6948109f23f350db82123535eb8a7433 bdabcb909271a6ecbcb58b936a88cd4e
8f2e6ff5800175f113253d8fa9ca8885 c2f552e657dc603f252e1a8e308f76f0
be79e2fb8f5d5fbbe2e30ecadd220723 c8c0aea8078cdfcb3868263ff8f09400
54da48781893a7e49ad5aff4af300cd8 04a6b6279ab3ff3afb64491c85194aab
760d58a606654f9f4400e8b38591356f bf6425aca26dc85244259ff2b19c41b9
f96f3ca9ec1dde434da7d2d392b905dd f3d1f9af93d1af5950bd493f5aa731b4
056df31bd267b6b90a079831aaf579be 0a39013137aac6d404f518cfd4684064
7e78bfe706ca4cf5e9c5453e9f7cfd2b 8b4c8d169a44e55c88d4a9a7f9474241
e221af44860018ab0856972e194cd934
A.3. 服务器初始数据包
作为回应,服务器会发送以下载荷,其中包含一个ACK帧和一个加密帧,并且不包含填充帧:
02000000000600405a020000560303ee fce7f7b37ba1d1632e96677825ddf739
88cfc79825df566dc5430b9a045a1200 130100002e00330024001d00209d3c94
0d89690b84d08a60993c144eca684d10 81287c834d5311bcf32bb9da1a002b00
020304
来自服务器的头部包含着一个新的连接ID和一个值为1
且被编码至双字节中数据包号:
对载荷进行保护后,从第三个密文字节起的一段数据被取作头部保护的样本。
sample = 2cd0991cd25b0aac406a5816b6394100
mask = 2ec0d8356a
header = cf000000010008f067a5502a4262b5004075c0d9
最后,经保护的数据包的内容为:
A.4. 重试数据包
这里展示了一个可以被用于响应附录A.2中的初始数据包的重试数据包。完整性检查中使用了了由客户端选择的值为0x8394c8f03e515708
的连接ID,但是这个值不会被包含在最终的重试数据包的明文中:
A.5. 使用ChaCha20-Poly1305的短包头数据包
本例展示了保护短包头数据包时所需的一些步骤。本例使用了AEAD_CHACHA20_POLY1305
。
在本例中,TLS生成了一个应用写入秘密值(secret
),服务器使用HKDF-Expand-Label
从这个秘密值生成四个值:一个密钥(key
)、一个IV(iv
)、一个头部保护密钥(hp
),和一个将被在密钥被更新后使用到的秘密值(ku
,但它在本例中没有被使用到)。
secret
= 9ac312a7f877468ebe69422748ad00a1
5443f18203a07d6060f688f30f21632b
key = HKDF-Expand-Label(secret, "quic key", "", 32)
= c6d98ff3441c3fe1b2182094f69caa2e
d4b716b65488960a7a984979fb23e1c8
iv = HKDF-Expand-Label(secret, "quic iv", "", 12)
= e0459b3474bdd0e44a41c144
hp = HKDF-Expand-Label(secret, "quic hp", "", 32)
= 25a282b9e82f06f21f488917a4fc8f1b
73573685608597d0efcb076b0ab7a7a4
ku = HKDF-Expand-Label(secret, "quic ku", "", 32)
= 1223504755036d556342ee9361d25342
1a826c9ecdf3c7148684b36b714881f9
下面展示了保护一个目标连接ID为空的最小数据包时所需的一些步骤。这个数据包仅包含了一个Ping帧(也就是说,载荷是0x01
),并且其数据包号为654360564
。在本例中,使用长度为3
的数据包号编码方式(也就是编码为49140
)避免了扩充数据包载荷的需要;如果数据包号被编码至更少的字节中,那么就需要填充帧。
pn = 654360564 # 十进制
nonce = e0459b3474bdd0e46d417eb0
unprotected header = 4200bff4
payload plaintext = 01
payload ciphertext = 655e5cd55c41f69080575d7999c25a5bfb
其结果密文的长度是在可能的范围中最小的。在为头部保护采样时,会跳过一个字节。
经保护的数据包,其21字节的长度是在可能的范围中最小的。
附录B. AEAD算法分析
本章记述了在为AEAD_AES_128_GCM
、AEAD_AES_128_CCM
和AEAD_AES_256_GCM
计算AEAD算法的用量上限时使用的分析方法。在下文的分析中,使用了乘法运算符号(*
)、除法运算符号(/
)和幂运算符号(^
),并使用括号来指定优先级。除此之外,还使用了以下符号:
t
:-
认证标签以比特为单位的长度。对于以上加密算法,
t
的值为128
。 n
:-
块函数以比特位单位的长度。对于以上算法,
n
的值为128
。 k
:-
密钥以比特为单位的长度。对于
AEAD_AES_128_GCM
和AEAD_AES_128_CCM
,n
的值为128
;AEAD_AES_256_GCM
则为256。 l
:-
每个数据包中块的数量(见下文)。
q
:-
终端创建和保护的真实数据包的数量。这个值的上限为终端在更新密钥前能够保护的数据包数量。
v
:-
终端接受的伪造数据包的数量。这个值的上限为终端在更新密钥前能够拒绝的伪造数据包的数量。
o
:-
攻击者离线进行的理想加密查询次数。
下文的分析依赖于在创建各条消息时对块操作进行计数。该分析是对尺寸不超过211
(l
为227
)或216
(l
为212
)的数据包进行的。211
这个尺寸应该是一个与常见部署模式匹配的上限值,而216
是单个QUIC数据包的所有可能的尺寸中的最大值。只有严格限制数据包尺寸的终端才能使用通过较小的数据包尺寸计算出的较大的那组可信度和完整性上限。
对于AEAD_AES_128_GCM
和AEAD_AES_256_GCM
,消息长度(l
)是块中关联数据的长度与块中明文的长度之和。
对于AEAD_AES_128_CCM
,块加密操作的总数为以下项目的总和:关联数据以块为单位的长度、密文以块为单位的长度和明文以块为单位的长度,最后再加上1。在本分析中,这个值被简化为数据包以块为单位的长度的两倍(也就是说,对于尺寸上限为211
的数据包,2l
为28
,否则2l
为213
)。这种简化是基于包含全部关联数据和密文的数据包的。它会造成对于每个数据包中的操作数量会多计算一至三个块。
B.1. 对于AEAD_AES_128_GCM和AEAD_AES_256_GCM用量上限的分析
B.1.1. 可信度上限
在可信度方面,《GCM-MU》中的定理4.3指出,对于一个不会重复随机数的用户,计算攻击者随机选择的AEAD算法相比实际使用的真实AEAD算法的优势程度的表达式为:
当目标优势度为2-57
时,会得到这样的关系:
因此,发送不超过211
字节的数据包的终端无法在单条连接中保护228
个以上的数据包却不让攻击者获得超过2-57
优势度。对于允许数据包尺寸达到216
字节的终端,则该限制为223
个数据包。
B.1.2. 完整性上限
在完整性方面,《GCM-MU》中的定理4.3指出,攻击者在伪造数据包时次数不需要超过此值就能获得明显优势:
我们的目标是将此优势限制到2-57
以下。对于AEAD_AES_128_GCM
,不等式中的第四项占据主导地位,所以其余项可以被移除而不会对结果产生重要影响。这会产生以下近似结果:
不会尝试对超过211
字节的数据包移除保护的终端最多可以尝试为257
个数据包移除保护。并不限制所处理的数据包尺寸的终端最多可以尝试为252
个数据包移除保护。
对于AEAD_AES_256_GCM
,占据主导地位的是同一项,但是较大的k
值会产生以下近似结果:
这比对AEAD_AES_128_GCM
的限制要大得多。但是,本文档建议对两个函数施加同样的限制,因为任一限制都是可接受且足够大的。
B.2. 对于AEAD_AES_128_CCM用量上限的分析
TLS(详见《TLS13》)和《AEBounds》都没有为AEAD_AES_128_CCM
的用量设定上限。然而,任何与QUIC一起使用的AEAD都需要在用量上设定上限以确保可信度和完整性都能得到维持。本节记述了关于此上限的分析。
《CCM-ANALYSIS》被用作本分析的基础。该文献中的分析结果被用于计算用量上限。
在可信度方面,《CCM-ANALYSIS》中的定理2指出,攻击者相比理想的伪随机排列(PRP)获得的优势不会超过此值:
在相同消息数量的情况下,《CCM-ANALYSIS》中的定理1中的完整性上限给予了攻击者更多优势。由于可信度方面优势与完整性方面优势的目标值是相同的,所以只需要考虑定理1。
定理1指出,攻击者相比理想的PRP获得的优势不会超过此值:
由于t
和n
都是128
,第一项相比第二项变得微不足道,因此第一项可以被移除而不会对结果产生重要影响。
这产生了一种关系,它将加密尝试次数和解密尝试次数关联到了相同的上限值上,这个上限值是定理为可信度生成的。当目标优势度为2-57
时,这会产生以下结果:
通过设置q = v
,可信度上限和完整性上限的值就都能被推导出来。因此限制数据包尺寸不超过211
字节的终端使用226.5
个数据包的可信度上限和完整性上限。不限制数据包尺寸的终端则使用值为221.5
的上限。
贡献者
IETF QUIC工作组接收到了来自许多人员的大量支持。以下人员对本文档做出了重要贡献:
-
Adam Langley
-
Alessandro Ghedini
-
Christian Huitema
-
Christopher Wood
-
David Schinazi
-
Dragana Damjanovic
-
Eric Rescorla
-
Felix Günther
-
Ian Swett
-
Jana Iyengar
-
奥 一穂 (Kazuho Oku)
-
Marten Seemann
-
Martin Duke
-
Mike Bishop
-
Mikkel Fahnøe Jørgensen
-
Nick Banks
-
Nick Harper
-
Roberto Peon
-
Rui Paulo
-
Ryan Hamilton
-
Victor Vasiliev
联系作者
Martin Thomson (编辑)
Mozilla
Email: mt@lowentropy.net
Sean Turner (编辑)
sn3rd
Email: sean@sn3rd.com
译
-
- Email: yunzhe@zju.edu.cn
-
- Email: fangqiuhang@163.com