状态
推出计划:
| Feature | Testing (not default) | Enabled by default |
|---|---|---|
| Local test code | 2022-02 | |
| Joint test code | 2022-03 | |
| Joint test in-net | 0.9.54 2022-05 | |
| Freeze basic protocol | 0.9.54 2022-05 | |
| Basic Session | 0.9.55 2022-08 | 0.9.56 2022-11 |
| Address Validation (Retry) | 0.9.55 2022-08 | 0.9.56 2022-11 |
| Fragmented RI in handshake | 0.9.55 2022-08 | 0.9.56 2022-11 |
| New Token | 0.9.55 2022-08 | 0.9.57 2022-11 |
| Freeze extended protocol | 0.9.55 2022-08 | |
| Relay | 0.9.55 2022-08 | 0.9.56 2022-11 |
| Peer Test | 0.9.55 2022-08 | 0.9.56 2022-11 |
| Enable for random 2% | 0.9.55 2022-08 | |
| Path Validation | 0.9.55+ dev | 0.9.56 2022-11 |
| Connection Migration | 0.9.55+ dev | 0.9.56 2022-11 |
| Immediate ACK flag | 0.9.55+ dev | 0.9.56 2022-11 |
| Key Rotation | 0.9.57 2023-02 | 0.9.58 2023-05 |
| Disable SSU 1 (i2pd) | 0.9.56 2022-11 | |
| Disable SSU 1 (Java I2P) | 0.9.58 2023-05 | 0.9.61 2023-12 |
| 基本会话包括握手和数据阶段。扩展协议包括中继和对等测试。 |
概述
本提案描述了一个经过身份验证的密钥协商协议,用于提高 SSU 对各种形式的自动化识别和攻击的抵抗能力。
该提案的组织结构如下:首先介绍安全目标,然后讨论基本协议。接下来给出所有协议消息的完整规范。最后讨论 router 地址和版本识别。
与其他 I2P 传输协议一样,SSU2 用于 I2NP 消息的点对点(router 到 router)传输。它不是通用数据管道。与 SSU 类似,它还提供两个额外服务:用于 NAT 穿越的中继功能,以及用于判断入站可达性的对等测试。它还提供第三项服务(SSU 中没有),即当对等节点更改 IP 或端口时的连接迁移功能。
动机
SSU是唯一仍需要ElGamal的协议层,而ElGamal非常缓慢。SSU的流量控制复杂且效果不佳。SSU的某些部分容易受到地址欺骗攻击。握手过程不使用Noise协议。
设计目标
通过消除 ElGamal 来减少 CPU 使用率。使用 X25519 进行 DH。
维护对等测试和中继功能,并增强其安全性。
通过支持标准流量控制算法来简化实现。
减少建立延迟。 目前 NTCP2 的中位建立时间约为 135 毫秒,SSU 约为 187 毫秒, 尽管 NTCP2 需要额外的往返;在 SSU2 中替换 ElGamal 应该能够减少延迟,但其他改进也可能有所帮助。
与 SSU 1 相比,保持或提高最大吞吐量, 通过在测试网络上模拟各种延迟和数据包丢失百分比进行测量。
通过"地址验证"防止来自伪造源地址的流量放大和误路由攻击
使数据包识别更容易,减少对回退机制和启发式方法的依赖,这些方法会使代码过于复杂。
正式化并改进当对等节点的 IP 或端口发生变化时的连接迁移。 在地址验证完成之前不要迁移连接,以防止攻击。 一些 SSU 1 实现使用昂贵的启发式方法来处理由于 NAT 重新绑定导致的端口变化。 没有已知的 SSU 1 实现能够处理 IP 变化。
在单个端口上支持 SSU 1 和 2,自动检测,并在 NetDB 中作为单个"传输"(即 RouterAddress)发布。
在 NetDB 中的单独字段发布仅支持版本 1、仅支持版本 2 或支持版本 1+2,默认为仅支持版本 1(不要将版本支持绑定到特定的 router 版本)
确保所有实现(Java/i2pd/Go)都可以按照自己的时间安排添加版本2支持(或不添加)
为所有消息(包括握手和数据消息)添加随机填充。 所有填充必须被 MAC 覆盖,这与 SSU 1 中的包尾填充不同。 提供选项机制,让双方可以请求最小和最大填充量 和/或填充分布。填充分布的具体细节取决于实现, 可能在协议本身中指定,也可能不指定。
对未完全加密的消息的头部和内容进行混淆,使DPI盒子和AV签名无法轻易对其进行分类。同时确保发送给单个对等点或对等点集合的消息不具有相似的比特模式。
修复由于 Java 格式导致的 DH 中比特丢失问题 Ticket1112,并通过切换到 X25519 来加速 DH。
切换到真正的密钥派生函数 (KDF) 而不是直接使用 DH 结果
添加"探测抗性"(Tor 称之为 probing resistance);这包括重放攻击抗性。
维护双向认证密钥交换(2W-AKE)。单向认证密钥交换(1W-AKE)对我们的应用来说是不够的。
依赖在 RouterInfo 中发布的静态公钥作为认证的另一部分。
在握手中添加选项/版本以便将来扩展。
不要显著增加连接建立所需的CPU;如果可能的话,应显著减少CPU需求。
移除 SSU 1 中 AES 加密所要求的填充到 16 字节倍数的要求
使用标准的 ChaCha/Poly1305 进行加密和 MAC, 替代 SSU 1 中使用的 AES 加密和非标准的 HMAC-MD5-128 MAC。
使用单独的加密密钥进行发送和接收,而不是像 SSU 1 中那样在两个方向上使用相同的密钥。
大幅提升ACK和NACK的效率, 这在SSU 1中表现很糟糕。减少ACK和NACK所需的带宽, 并增加可用于数据的包大小。 高效编码突发丢失消息的NACK, 这在WiFi环境下很常见。
降低实现 I2NP 消息分片所需的复杂性。 绕过完整 I2NP 消息的分片机制和编码。
在填充前最小化协议开销,特别是对于ACK。 虽然会添加填充, 但填充前的开销仍然是开销。 低带宽节点必须能够使用SSU2。
维护时间戳用于重放和偏差检测。
避免时间戳中的2038年问题,必须至少工作到2106年。
将最小 MTU 从 620 增加到 1280 以提高效率、简化实现, 并增加最大 I2NP 消息大小。 分片和重组的开销相当昂贵。 通过为 1028 字节的隧道消息提供空间,绝大多数 I2NP 消息将不需要分片。
将最大 MTU 从 1488(IPv6 为 1484)提高到 1500 以提升效率。 移除 MTU 必须为 16 的倍数的要求。
将最大 I2NP 消息大小从 SSU 1 中的大约 32K 增加到与 NTCP2 中相同的大约 64 KB。
从握手中移除IP和端口字段的签名, 这样不知道自己外部IP和端口的router就能够连接。
在握手过程中保留 SSU 1 的 IP/端口发现机制, 以便路由器可以了解其外部 IP 和端口。
在设计中包含 Java、C++ 和 Go router 开发者的代表。
Non-Goals
防弹级 DPI 抗性… 这将是可插拔传输, 提案 109。
基于TLS的(或类似HTTPS的)传输协议…详见提案104。
基于时序的 DPI 抗性(消息间时序/延迟可以依赖于实现;消息内延迟可以在任何点引入,例如在发送随机填充之前)。人工延迟(obfs4 称之为 IAT 或到达间隔时间)独立于协议本身。
参与会话的可否认性(其中有签名)。
可能部分重新考虑或讨论的非目标:
针对深度包检测 (DPI) 的保护程度
后量子(PQ)安全性
可否认性
Security Goals
我们考虑三个参与方:
- Alice,希望建立新会话的一方。
- Bob,Alice 希望与之建立会话的对象。
- Mallory,位于 Alice 和 Bob 之间的"中间人"。
最多两个参与者可以进行主动攻击。
Alice和Bob都拥有静态密钥对,这些密钥对包含在他们的RouterIdentity中。
提议的协议试图让Alice和Bob在以下要求下就共享密钥(K)达成一致:
私钥安全性:Bob 和 Mallory 都无法了解到 Alice 的静态私钥的任何信息。对称地,Alice 也无法了解到 Bob 的静态私钥的任何信息。
会话密钥 K 只有 Alice 和 Bob 知道。
完全前向保密:约定的会话密钥在未来保持秘密,即使在密钥协商完成后,Alice和/或Bob的静态私钥被泄露。
双向认证:Alice 确信她已经与 Bob 建立了会话,反之亦然。
防护在线 DPI:确保仅使用直接的深度包检测 (DPI) 技术不能轻易检测到 Alice 和 Bob 正在参与该协议。见下文。
有限的可否认性:Alice 和 Bob 都无法否认参与了该协议,但如果任何一方泄露了共享密钥,另一方可以否认传输数据内容的真实性。
当前提案尝试基于站对站(STS)协议来提供所有五个要求。请注意,该协议也是 SSU 协议的基础。
Additional DPI Discussion
我们假设有两个 DPI 组件:
Online DPI
在线 DPI 实时检查所有流量。连接可能被阻断或以其他方式篡改。连接数据或元数据可能被识别并存储用于离线分析。在线 DPI 无法访问 I2P 网络数据库。在线 DPI 仅具有有限的实时计算能力,包括长度计算、字段检查和简单计算如 XOR。在线 DPI 确实具备快速实时加密功能,如 ChaCha20、AEAD 和哈希,但将这些应用于大多数或所有流量会过于昂贵。任何加密操作的应用仅限于之前通过离线分析识别的 IP/端口组合上的流量。在线 DPI 不具备高开销加密功能的能力,如 DH 或 elligator2。在线 DPI 并非专门设计用于检测 I2P,尽管它可能具有用于此目的的有限分类规则。
防止在线DPI进行协议识别是一个目标。
这里的在线或"直接"DPI概念包括以下对手能力:
检查目标发送或接收的所有数据的能力。
对观察到的数据执行操作的能力,例如应用分组密码或哈希函数。
存储和比较之前发送消息的能力。
修改、延迟或分片数据包的能力。
然而,在线DPI被假设具有以下限制:
无法将IP地址映射到router哈希值。虽然通过实时访问netDb可以轻松实现这一点,但这需要专门针对I2P设计的DPI系统。
无法使用时序信息来检测协议。
一般来说,在线DPI工具箱不包含任何专门为I2P检测而设计的内置工具。这包括创建"蜜罐",例如在其消息中包含非随机填充。请注意,这并不排除机器学习系统或高度可配置的DPI工具,只要它们满足其他要求。
为了对抗载荷分析,需要确保所有消息与随机数据无法区分。这还要求消息长度是随机的,这比简单添加随机填充要复杂得多。事实上,在附录A中,作者论证了朴素的(即均匀分布的)填充方案并不能解决这个问题。因此,附录A提出要么包含随机延迟,要么开发一种替代填充方案,以针对所提出的攻击提供合理的保护。
为了防范上述第六项攻击,实现应该在协议中包含随机延迟。此类技术不在本提案覆盖范围内,但它们也可能解决填充长度问题。总而言之,本提案为负载分析提供了良好的保护(当考虑附录A中的注意事项时),但对流量分析仅提供有限的保护。
Offline DPI
离线DPI检查由在线DPI存储的数据以供后续分析。离线DPI可能专门设计用于检测I2P。离线DPI确实具有对I2P网络数据库的实时访问权限。离线DPI确实可以访问此规范和其他I2P规范。离线DPI具有无限的计算能力,包括本规范中定义的所有密码学功能。
离线DPI无法阻断现有连接。离线DPI确实具备近实时(在设置后几分钟内)通过数据包注入向主机/端口发送数据的能力。离线DPI确实具备近实时(在设置后几分钟内)重放先前消息(无论是否修改)以进行"探测"或其他目的的能力。
防止离线 DPI 进行协议识别并不是一个目标。I2P router 实现的前两条消息中混淆数据的所有解码,也可能被离线 DPI 实现。
拒绝使用重放先前消息的连接尝试是一个目标。
Address Validation
以下内容摘自 QUIC RFC 9000。请逐节审阅和编辑。
地址验证确保端点不能被用于流量放大攻击。在此类攻击中,攻击者向服务器发送一个数据包,其中包含伪造的源地址信息来标识受害者。如果服务器响应该数据包时生成更多或更大的数据包,攻击者就可以利用服务器向受害者发送比其自身能够发送的更多数据。
抵御放大攻击的主要防御措施是验证对等节点能够在其声称的传输地址上接收数据包。因此,在从尚未验证的地址接收到数据包后,端点必须将发送到该未验证地址的数据量限制为从该地址接收到的数据量的三倍。这种对响应大小的限制被称为反放大限制。
地址验证在连接建立期间(参见第8.1节)和连接迁移期间(参见第8.2节)都会执行。
Address Validation during Connection Establishment
连接建立过程隐式地为两个端点提供地址验证。特别是,接收到使用握手密钥保护的数据包可以确认对端成功处理了初始数据包。一旦端点成功处理了来自对端的握手数据包,就可以认为对端地址已经得到验证。
此外,如果对等方使用由端点选择的连接ID,并且该连接ID包含至少64位熵,则端点可以考虑对等方地址已验证。
对于客户端,其第一个 Initial 数据包中的 Destination Connection ID 字段的值允许它在成功处理任何数据包的过程中验证服务器地址。来自服务器的 Initial 数据包使用从该值派生的密钥进行保护(参见 QUIC-TLS 的第 5.2 节)。或者,该值由服务器在 Version Negotiation 数据包中回显(第 6 节),或包含在 Retry 数据包的 Integrity Tag 中(QUIC-TLS 的第 5.8 节)。
在验证客户端地址之前,服务器发送的字节数不得超过其接收字节数的三倍。这限制了使用伪造源地址发起的任何放大攻击的规模。为了在地址验证之前避免放大,服务器必须计算在唯一归属于单个连接的数据报中接收到的所有载荷字节。这包括包含成功处理数据包的数据报和包含全部被丢弃数据包的数据报。
客户端必须确保包含Initial数据包的UDP数据报具有至少1200字节的UDP有效载荷,必要时添加PADDING帧。发送填充数据报的客户端允许服务器在完成地址验证之前发送更多数据。
如果客户端不发送额外的 Initial 或 Handshake 数据包,服务器丢失 Initial 或 Handshake 数据包可能会导致死锁。当服务器达到其反放大限制且客户端已收到其发送的所有数据的确认时,可能发生死锁。在这种情况下,当客户端没有理由发送额外数据包时,服务器将无法发送更多数据,因为它尚未验证客户端的地址。为了防止这种死锁,客户端必须在探测超时 (PTO) 时发送数据包;参见 QUIC-RECOVERY 的第 6.2 节。具体而言,如果客户端没有 Handshake 密钥,则必须在包含至少 1200 字节的 UDP 数据报中发送 Initial 数据包,否则发送 Handshake 数据包。
服务器可能希望在开始加密握手之前验证客户端地址。QUIC在Initial数据包中使用token来在完成握手前提供地址验证。此token在连接建立过程中通过Retry数据包传递给客户端(参见第8.1.2节),或在之前的连接中使用NEW_TOKEN帧传递(参见第8.1.3节)。
除了地址验证之前施加的发送限制外,服务器在发送内容方面还受到拥塞控制器设置的限制约束。客户端仅受拥塞控制器约束。
Token Construction
在 NEW_TOKEN 帧或 Retry 数据包中发送的令牌必须以允许服务器识别其如何提供给客户端的方式构造。这些令牌承载在同一字段中,但需要服务器进行不同的处理。
Address Validation Using Retry Packets
收到客户端的 Initial 数据包后,服务器可以通过发送包含令牌的 Retry 数据包(第 17.2.5 节)来请求地址验证。客户端在收到 Retry 数据包后,必须在为该连接发送的所有 Initial 数据包中重复此令牌。
在处理包含 Retry 数据包中提供的令牌的 Initial 数据包时,服务器不能发送另一个 Retry 数据包;它只能拒绝连接或允许连接继续进行。
只要攻击者无法为其自身地址生成有效令牌(参见第8.1.4节),并且客户端能够返回该令牌,这就向服务器证明了它收到了令牌。
服务器也可以使用重试包来推迟连接建立的状态和处理开销。要求服务器提供不同的连接ID,连同第18.2节中定义的original_destination_connection_id传输参数,强制服务器证明它或与其合作的实体收到了来自客户端的原始初始包。提供不同的连接ID还赋予服务器一定的控制权,可以控制后续数据包的路由方式。这可以用于将连接导向不同的服务器实例。
如果服务器收到包含无效 Retry token 但在其他方面有效的客户端 Initial 消息,它知道客户端不会接受另一个 Retry token。服务器可以丢弃此类数据包并允许客户端超时来检测握手失败,但这可能对客户端造成显著的延迟损失。相反,服务器应该立即使用 INVALID_TOKEN 错误关闭(第 10.2 节)连接。请注意,此时服务器尚未为连接建立任何状态,因此不会进入关闭期。
图9显示了使用重试包的流程。
Client Server
Initial[0]: CRYPTO[CH] ->
<- Retry+Token
Initial+Token[1]: CRYPTO[CH] ->
Initial[0]: CRYPTO[SH] ACK[1]
Handshake[0]: CRYPTO[EE, CERT, CV, FIN]
<- 1-RTT[0]: STREAM[1, "..."]
Figure 9: Example Handshake with Retry
Address Validation for Future Connections
服务器可以在一次连接期间向客户端提供地址验证令牌,该令牌可在后续连接中使用。地址验证在 0-RTT 中尤为重要,因为服务器可能会向客户端发送大量数据来响应 0-RTT 数据。
服务器使用 NEW_TOKEN 帧(第 19.7 节)向客户端提供地址验证令牌,该令牌可用于验证将来的连接。在将来的连接中,客户端在 Initial 数据包中包含此令牌以提供地址验证。客户端必须在其发送的所有 Initial 数据包中包含该令牌,除非 Retry 用更新的令牌替换该令牌。客户端不得将 Retry 中提供的令牌用于将来的连接。服务器可以丢弃任何不携带预期令牌的 Initial 数据包。
与为重试数据包创建的立即使用的令牌不同,NEW_TOKEN 帧中发送的令牌可以在经过一段时间后使用。因此,令牌应该(SHOULD)具有过期时间,这可以是明确的过期时间,也可以是可用于动态计算过期时间的发行时间戳。服务器可以存储过期时间或将其以加密形式包含在令牌中。
使用 NEW_TOKEN 发布的令牌不得包含允许观察者将值与发布该令牌的连接关联起来的信息。例如,它不能包含先前的连接 ID 或寻址信息,除非这些值已加密。服务器必须确保它发送的每个 NEW_TOKEN 帧在所有客户端中都是唯一的,但用于修复先前发送的 NEW_TOKEN 帧丢失的情况除外。允许服务器区分来自 Retry 和 NEW_TOKEN 的令牌的信息可能被服务器以外的实体访问。
两个不同连接上的客户端端口号相同的可能性很小;因此验证端口不太可能成功。
在 NEW_TOKEN 帧中接收到的令牌适用于连接被认为具有权威性的任何服务器(例如,证书中包含的服务器名称)。当连接到客户端保留适用且未使用令牌的服务器时,它应该在其初始数据包的令牌字段中包含该令牌。包含令牌可能允许服务器在无需额外往返的情况下验证客户端地址。客户端不得包含不适用于其要连接的服务器的令牌,除非客户端知道发布令牌的服务器和客户端要连接的服务器正在联合管理这些令牌。客户端可以使用来自与该服务器的任何先前连接的令牌。
令牌允许服务器在发放令牌的连接和使用令牌的任何连接之间关联活动。希望与服务器断开身份连续性的客户端可以丢弃通过 NEW_TOKEN frame 提供的令牌。相比之下,在 Retry 数据包中获得的令牌必须在连接尝试期间立即使用,不能在后续的连接尝试中使用。
客户端不应该在不同的连接尝试中重复使用来自 NEW_TOKEN 帧的令牌。重复使用令牌会允许网络路径上的实体关联连接;参见第 9.5 节。
客户端可能在单个连接上收到多个令牌。除了防止关联性之外,任何令牌都可以在任何连接尝试中使用。服务器可以发送额外的令牌,以便为多个连接尝试启用地址验证或替换可能变为无效的旧令牌。对于客户端来说,这种模糊性意味着发送最新的未使用令牌最可能有效。尽管保存和使用旧令牌没有负面后果,但客户端可以认为旧令牌对服务器进行地址验证的用处较小。
当服务器收到带有地址验证令牌的Initial数据包时,必须尝试验证该令牌,除非它已经完成了地址验证。如果令牌无效,那么服务器应当按照客户端没有经过验证的地址的方式处理,包括可能发送Retry数据包。通过NEW_TOKEN帧和Retry数据包提供的令牌可以被服务器区分(参见第8.1.1节),后者可以进行更严格的验证。如果验证成功,服务器应当允许握手继续进行。
注意:将客户端视为未验证而不是丢弃数据包的原因是,客户端可能在之前的连接中通过 NEW_TOKEN 帧接收到了令牌,如果服务器丢失了状态,可能完全无法验证令牌,如果丢弃数据包将导致连接失败。
在无状态设计中,服务器可以使用加密和认证的令牌向客户端传递信息,服务器稍后可以恢复这些信息并用于验证客户端地址。令牌不集成到加密握手中,因此它们不被认证。例如,客户端可能能够重用令牌。为了避免利用此属性的攻击,服务器可以将令牌的使用限制为仅验证客户端地址所需的信息。
客户端可以将在一个连接上获得的 token 用于使用相同版本的任何连接尝试。在选择要使用的 token 时,客户端无需考虑正在尝试的连接的其他属性,包括可能的应用协议选择、会话票证或其他连接属性。
Address Validation Token Integrity
地址验证令牌必须难以猜测。在令牌中包含至少128位熵的随机值就足够了,但这取决于服务器记住它发送给客户端的值。
基于令牌的方案允许服务器将与验证相关的任何状态卸载到客户端。为了使此设计能够正常工作,令牌必须受到完整性保护,以防止客户端修改或伪造。如果没有完整性保护,恶意客户端可能生成或猜测服务器会接受的令牌值。只有服务器需要访问令牌的完整性保护密钥。
由于生成 token 的服务器也会使用它,因此不需要为 token 定义单一的明确格式。在 Retry 数据包中发送的 token 应该包含允许服务器验证客户端数据包中源 IP 地址和端口保持恒定的信息。
在 NEW_TOKEN 帧中发送的令牌必须包含允许服务器验证客户端 IP 地址自令牌颁发以来未发生变化的信息。服务器可以使用来自 NEW_TOKEN 帧的令牌来决定不发送 Retry 数据包,即使客户端地址已发生变化。如果客户端 IP 地址已发生变化,服务器必须遵守反放大限制;请参见第 8 节。请注意,在存在 NAT 的情况下,此要求可能不足以保护共享 NAT 的其他主机免受放大攻击。
攻击者可能通过重放令牌来使用服务器作为DDoS攻击的放大器。为防止此类攻击,服务器必须确保防止或限制令牌的重放。服务器应该确保在Retry数据包中发送的令牌只在短时间内被接受,因为这些令牌会被客户端立即返回。在NEW_TOKEN帧(第19.7节)中提供的令牌需要有效期更长,但不应该被多次接受。鼓励服务器尽可能只允许令牌使用一次;令牌可以包含关于客户端的附加信息,以进一步缩小适用性或重复使用范围。
在线 DPI
路径验证在连接迁移期间被两个对等节点使用(参见第9节),用于在地址变更后验证可达性。在路径验证中,端点测试特定本地地址与特定对等节点地址之间的可达性,其中地址是IP地址和端口的二元组。
路径验证测试发送到对等节点路径上的数据包是否被该对等节点接收。路径验证用于确保从迁移对等节点接收的数据包不携带伪造的源地址。
路径验证不会验证对等节点能够在返回方向发送数据。确认消息不能用于返回路径验证,因为它们包含的熵不足且可能被伪造。端点独立确定路径每个方向的可达性,因此返回可达性只能由对等节点建立。
路径验证可以由任一端点在任何时候使用。例如,端点可能会在静默期后检查对等节点是否仍然拥有其地址。
路径验证并非设计用作NAT穿越机制。尽管这里描述的机制可能有效地创建支持NAT穿越的NAT绑定,但预期是一个端点能够在该路径上首次发送数据包之前就能接收数据包。有效的NAT穿越需要额外的同步机制,而这里并未提供这些机制。
端点可以在用于路径验证的 PATH_CHALLENGE 和 PATH_RESPONSE 帧中包含其他帧。特别地,端点可以在 PATH_CHALLENGE 帧中包含 PADDING 帧用于路径最大传输单元发现 (PMTUD);参见第 14.2.1 节。端点在发送 PATH_RESPONSE 帧时也可以包含自己的 PATH_CHALLENGE 帧。
端点为从新本地地址发送的探测使用新的连接ID;请参见第9.5节。当探测新路径时,端点可以确保其对等端有可用的未使用连接ID用于响应。如果对等端的active_connection_id_limit允许,在同一数据包中发送NEW_CONNECTION_ID和PATH_CHALLENGE帧,可确保对等端在发送响应时有可用的未使用连接ID。
端点可以选择同时探测多个路径。用于探测的同时路径数量受到其对等端先前提供的额外连接 ID 数量的限制,因为用于探测的每个新本地地址都需要一个先前未使用的连接 ID。
离线 DPI
为了启动路径验证,端点在要验证的路径上发送包含不可预测载荷的 PATH_CHALLENGE 帧。
端点可以发送多个 PATH_CHALLENGE 帧来防范数据包丢失。但是,端点不应该在单个数据包中发送多个 PATH_CHALLENGE 帧。
端点不应该使用包含 PATH_CHALLENGE 帧的数据包探测新路径的频率超过发送 Initial 数据包的频率。这确保了连接迁移对新路径的负载不会超过建立新连接的负载。
端点必须在每个PATH_CHALLENGE帧中使用不可预测的数据,以便能够将对等方的响应与相应的PATH_CHALLENGE关联起来。
端点必须将包含 PATH_CHALLENGE frame 的数据报扩展到至少 1200 字节的最小允许最大数据报大小,除非该路径的反放大限制不允许发送此大小的数据报。发送此大小的 UDP 数据报确保从端点到对等端的网络路径可用于 QUIC;参见第 14 节。
当端点由于反放大限制无法将数据报大小扩展到 1200 字节时,路径 MTU 将不会被验证。为确保路径 MTU 足够大,端点必须通过在至少 1200 字节的数据报中发送 PATH_CHALLENGE 帧来执行第二次路径验证。这种额外的验证可以在成功接收到 PATH_RESPONSE 后执行,或者当在路径上接收到足够的字节数,使得发送更大的数据报不会导致超过反放大限制时执行。
与扩展数据报的其他情况不同,当数据报包含 PATH_CHALLENGE 或 PATH_RESPONSE 时,端点不得丢弃看似过小的数据报。
Path Validation Responses
在收到PATH_CHALLENGE帧时,端点必须通过在PATH_RESPONSE帧中回显PATH_CHALLENGE帧包含的数据来响应。端点不得延迟传输包含PATH_RESPONSE帧的数据包,除非受到拥塞控制的限制。
PATH_RESPONSE 帧必须在接收到 PATH_CHALLENGE 帧的网络路径上发送。这确保了对等节点的路径验证只有在路径双向功能正常时才会成功。发起路径验证的端点不得强制执行此要求,因为这会导致对迁移的攻击;参见第 9.3.3 节。
端点必须将包含 PATH_RESPONSE 帧的数据报扩展到至少 1200 字节的最小允许最大数据报大小。这验证了路径能够在两个方向上承载这种大小的数据报。但是,如果结果数据超过反放大限制,端点不得扩展包含 PATH_RESPONSE 的数据报。这种情况预期仅在接收到的 PATH_CHALLENGE 未在扩展数据报中发送时发生。
端点不得发送超过一个 PATH_RESPONSE 帧来响应一个 PATH_CHALLENGE 帧;请参见第 13.3 节。预期对等方会根据需要发送更多 PATH_CHALLENGE 帧来引发额外的 PATH_RESPONSE 帧。
连接建立过程中的地址验证
当收到包含先前在 PATH_CHALLENGE 帧中发送的数据的 PATH_RESPONSE 帧时,路径验证成功。在任何网络路径上收到的 PATH_RESPONSE 帧都会验证发送 PATH_CHALLENGE 的路径。
如果端点在未扩展到至少 1200 字节的数据报中发送 PATH_CHALLENGE 帧,并且对其的响应验证了对等地址,则路径已验证但路径 MTU 未验证。因此,端点现在可以发送超过已接收数据量三倍的数据。但是,端点必须使用扩展数据报发起另一次路径验证,以验证路径支持所需的 MTU。
收到包含 PATH_CHALLENGE 帧的数据包的确认并不足以作为有效验证,因为确认可能被恶意对等方伪造。
Token 构建
路径验证仅在尝试验证路径的端点放弃其验证路径的尝试时才会失败。
端点应该基于计时器放弃路径验证。在设置此计时器时,实现需要注意新路径可能比原路径具有更长的往返时间。建议使用当前 PTO 或新路径 PTO 中较大值的三倍(使用 kInitialRtt,如 QUIC-RECOVERY 中定义)。
此超时允许在路径验证失败之前多个 PTO 到期,这样单个 PATH_CHALLENGE 或 PATH_RESPONSE 帧的丢失不会导致路径验证失败。
请注意,端点可能会在新路径上接收到包含其他帧的数据包,但需要包含适当数据的 PATH_RESPONSE 帧才能使路径验证成功。
当端点放弃路径验证时,它确定该路径不可用。这并不一定意味着连接失败——端点可以根据需要继续通过其他路径发送数据包。如果没有可用路径,端点可以等待新路径变为可用或关闭连接。没有到其对等方的有效网络路径的端点可以使用 NO_VIABLE_PATH 连接错误来发出信号,需要注意的是,只有当网络路径存在但不支持所需的 MTU 时才可能发生这种情况(第 14 节)。
除了失败之外,路径验证可能因其他原因被放弃。主要是,如果在旧路径上的路径验证正在进行时启动了到新路径的连接迁移,就会发生这种情况。
Connection Migration
以下内容复制自 QUIC RFC 9000。请审查和编辑每个部分。
使用连接 ID 可以让连接在端点地址(IP 地址和端口)发生变化时保持存活,比如端点迁移到新网络时引起的变化。本节描述端点迁移到新地址的过程。
QUIC 的设计依赖于端点在握手期间保持稳定的地址。端点在握手确认之前不得启动连接迁移,如 QUIC-TLS 第 4.1.2 节所定义。
如果对等方发送了 disable_active_migration transport parameter,端点也不得从不同的本地地址向对等方在握手期间使用的地址发送数据包(包括探测数据包;参见第 9.1 节),除非端点已根据对等方的 preferred_address transport parameter 采取行动。如果对等方违反了此要求,端点必须要么丢弃该路径上的传入数据包而不生成 Stateless Reset,要么继续进行路径验证并允许对等方迁移。生成 Stateless Reset 或关闭连接会允许网络中的第三方通过欺骗或以其他方式操纵观察到的流量来导致连接关闭。
并非所有对等节点地址的更改都是有意的或主动的迁移。对等节点可能遇到 NAT 重新绑定:由于中间设备(通常是 NAT)为数据流分配新的出站端口甚至新的出站 IP 地址而导致的地址更改。端点必须执行路径验证(第 8.2 节),如果它检测到对等节点地址的任何更改,除非它之前已经验证过该地址。
当端点没有可用于发送数据包的已验证路径时,它可以丢弃连接状态。支持连接迁移的端点可以在丢弃连接状态之前等待新路径变为可用。
本文档限制连接迁移到新的客户端地址,除非在第9.6节中描述的情况。客户端负责启动所有迁移。服务器不会向客户端地址发送非探测数据包(见第9.1节),直到它们从该地址看到非探测数据包。如果客户端从未知的服务器地址接收到数据包,客户端必须丢弃这些数据包。
使用重试数据包进行地址验证
端点可以在将连接迁移到新本地地址之前,使用路径验证(第8.2节)从新本地地址探测对等方的可达性。路径验证失败仅意味着新路径对此连接不可用。验证路径失败不会导致连接终止,除非没有可用的有效替代路径。
PATH_CHALLENGE、PATH_RESPONSE、NEW_CONNECTION_ID 和 PADDING 帧是"探测帧",所有其他帧是"非探测帧"。仅包含探测帧的数据包是"探测数据包",包含任何其他帧的数据包是"非探测数据包"。
未来连接的地址验证
端点可以通过从新的本地地址发送包含非探测帧的数据包来将连接迁移到该地址。
每个端点在建立连接期间都会验证其对等端的地址。因此,迁移的端点可以向其对等端发送数据,因为它知道对等端愿意在对等端的当前地址接收数据。因此,端点可以迁移到新的本地地址,而无需首先验证对等端的地址。
为了在新路径上建立可达性,端点在新路径上启动路径验证(第8.2节)。端点可以推迟路径验证,直到对等端向其新地址发送下一个非探测帧之后。
迁移时,新路径可能无法支持端点当前的发送速率。因此,端点会重置其拥塞控制器和 RTT 估算,如第 9.4 节所述。
新路径可能不具有相同的 ECN 能力。因此,端点按照第 13.4 节中描述的方法验证 ECN 能力。
地址验证令牌完整性
从包含非探测帧的新对等地址接收数据包表明该对等节点已迁移到该地址。
如果接收方允许迁移,它必须向新的对等地址发送后续数据包,并且必须启动路径验证(第8.2节)来验证对等方对该地址的所有权(如果验证尚未进行)。如果接收方没有来自对等方的未使用连接ID,它将无法在新路径上发送任何内容,直到对等方提供一个;参见第9.5节。
端点只会响应编号最高的非探测数据包来更改其发送数据包的目标地址。这确保了在接收到乱序数据包的情况下,端点不会将数据包发送到旧的对等方地址。
端点可以向未验证的对等地址发送数据,但必须防范第9.3.1节和第9.3.2节中描述的潜在攻击。如果某个对等地址最近已被观察到,端点可以跳过该地址的验证。特别是,如果端点在检测到某种形式的虚假迁移后返回到先前验证过的路径,跳过地址验证并恢复丢失检测和拥塞状态可以减少攻击对性能的影响。
在更改其发送非探测数据包的地址后,端点可以放弃对其他地址的任何路径验证。
从新的对等节点地址接收数据包可能是对等节点NAT重新绑定的结果。
验证新客户端地址后,服务器应该向客户端发送新的地址验证令牌(第8节)。
路径验证
某个对等节点可能会伪造其源地址,导致端点向不知情的主机发送大量数据。如果端点发送的数据量远超伪造对等节点的数据量,连接迁移可能被用来放大攻击者向受害者生成的数据流量。
如第9.3节所述,endpoint需要验证对等方的新地址,以确认对等方拥有该新地址。在对等方的地址被认为有效之前,endpoint会限制向该地址发送的数据量;参见第8节。如果没有这个限制,endpoint可能会被用于对无辜受害者进行拒绝服务攻击。
如果端点跳过如上所述的对等地址验证,则无需限制其发送速率。
启动路径验证
路径上的攻击者可以通过复制并转发一个带有伪造地址的数据包来导致虚假的连接迁移,使其在原始数据包之前到达。带有伪造地址的数据包将被视为来自迁移连接,而原始数据包将被视为重复数据包并被丢弃。在虚假迁移之后,源地址验证将失败,因为源地址处的实体没有必要的加密密钥来读取或响应发送给它的PATH_CHALLENGE帧,即使它想要这样做也无法做到。
为了保护连接不因此类虚假迁移而失败,当新对等方地址验证失败时,端点必须恢复使用上次验证过的对等方地址。此外,从合法对等方地址接收到具有更高数据包编号的数据包将触发另一次连接迁移。这将导致放弃对虚假迁移地址的验证,从而遏制攻击者通过注入单个数据包发起的迁移。
如果端点对最后验证的对等方地址没有状态信息,它必须通过丢弃所有连接状态来静默关闭连接。这会导致连接上的新数据包被通用处理。例如,端点可以发送无状态重置来响应任何进一步的传入数据包。
路径验证响应
一个能够观察数据包的路径外攻击者可能会将真实数据包的副本转发给端点。如果复制的数据包在真实数据包之前到达,这将表现为NAT重新绑定。任何真实的数据包都将作为重复包被丢弃。如果攻击者能够继续转发数据包,它可能能够导致迁移到通过攻击者的路径。这将使攻击者处于路径上,使其能够观察或丢弃所有后续数据包。
这种攻击方式依赖于攻击者使用一条与端点之间直接路径具有大致相同特征的路径。如果发送的数据包相对较少,或者数据包丢失与攻击尝试同时发生,那么这种攻击会更加可靠。
在原始路径上接收到的非探测数据包如果增加了最大接收数据包编号,将导致端点回到该路径。在此路径上引发数据包会增加攻击失败的可能性。因此,缓解此攻击依赖于触发数据包交换。
响应明显的迁移时,端点必须使用 PATH_CHALLENGE 帧验证之前活跃的路径。这会引发在该路径上发送新数据包。如果路径不再可行,验证尝试将超时并失败;如果路径可行但不再需要,验证将成功,但只会导致在路径上发送探测数据包。
在活跃路径上收到 PATH_CHALLENGE 的端点应该发送非探测数据包作为响应。如果非探测数据包在攻击者制作的任何副本之前到达,这将导致连接迁移回原始路径。任何后续迁移到另一条路径都会重新启动整个过程。
这种防护措施并不完美,但这不被认为是一个严重问题。如果通过攻击路径的速度在多次尝试使用原始路径后仍然可靠地快于原始路径,就无法区分这是攻击还是路由的改进。
端点也可以使用启发式方法来改进对这种攻击方式的检测。例如,如果最近在旧路径上接收到数据包,则NAT重新绑定不太可能发生;同样,IPv6路径上的重新绑定很少见。端点还可以查找重复的数据包。相反,连接ID的更改更可能表示有意迁移而不是攻击。
成功的路径验证
新路径上的可用容量可能与旧路径不同。在旧路径上发送的数据包不得影响新路径的拥塞控制或RTT估计。
在确认对等节点拥有其新地址后,端点必须立即将新路径的拥塞控制器和往返时间估算器重置为初始值(参见 QUIC-RECOVERY 的附录 A.3 和 B.3),除非对等节点地址的唯一变化是其端口号。由于仅端口变化通常是 NAT 重新绑定或其他中间盒活动的结果,在这些情况下,端点可以选择保留其拥塞控制状态和往返时间估计,而不是恢复到初始值。在从旧路径保留的拥塞控制状态用于具有显著不同特性的新路径的情况下,发送方可能会过于激进地传输,直到拥塞控制器和 RTT 估算器适应为止。通常,建议实现在新路径上使用先前值时要谨慎。
在迁移期间,当端点从/向多个地址发送数据和探测包时,接收端可能会出现明显的重排序,因为两条结果路径可能具有不同的往返时间。在多条路径上接收数据包的接收端仍将发送覆盖所有已接收数据包的 ACK 帧。
虽然在连接迁移过程中可能会使用多个路径,但单个拥塞控制上下文和单个丢失恢复上下文(如 QUIC-RECOVERY 中所述)可能就足够了。例如,端点可能会延迟切换到新的拥塞控制上下文,直到确认不再需要旧路径为止(如第 9.3.3 节中描述的情况)。
发送方可以对探测数据包制定例外规则,使其丢包检测独立进行,不会不当地导致拥塞控制器降低其发送速率。当发送 PATH_CHALLENGE 时,端点可能会设置一个单独的计时器,如果收到相应的 PATH_RESPONSE,该计时器将被取消。如果在收到 PATH_RESPONSE 之前计时器触发,端点可能会发送一个新的 PATH_CHALLENGE 并重新启动计时器以设置更长的时间段。此计时器应当按照 QUIC-RECOVERY 第 6.2.1 节中的描述进行设置,且不得更加激进。
路径验证失败
在多个网络路径上使用稳定的连接ID会允许被动观察者关联这些路径之间的活动。在网络间移动的端点可能不希望其活动被除对等端以外的任何实体关联,因此在从不同本地地址发送时使用不同的连接ID,如第5.1节所述。为了使这种做法有效,端点需要确保它们提供的连接ID不能被任何其他实体关联。
在任何时候,端点都可以将它们传输的目标连接 ID 更改为在其他路径上未使用过的值。
端点在从多个本地地址发送时绝对不能重用连接ID——例如,在启动第9.2节中描述的连接迁移时,或在探测第9.1节中描述的新网络路径时。
类似地,端点在向多个目标地址发送数据时不得重用连接ID。由于其对等节点无法控制的网络变化,端点可能会收到来自新源地址但具有相同目标连接ID字段值的数据包,在这种情况下,它可以继续使用当前连接ID与新的远程地址通信,同时仍从相同的本地地址发送。
关于连接ID重用的这些要求仅适用于数据包的发送,因为在连接ID不变的情况下可能会发生路径的意外变化。例如,在一段时间的网络不活动之后,NAT重新绑定可能会导致客户端恢复发送时数据包通过新路径发送。端点对此类事件的响应如第9.3节所述。
在每条新网络路径上为双向发送的数据包使用不同的连接ID,消除了通过连接ID在不同网络路径上关联同一连接数据包的可能性。报头保护确保数据包号码无法用于关联活动。但这并不能阻止数据包的其他属性(如时序和大小)被用于关联活动。
端点不应该与请求零长度连接 ID 的对等方发起迁移,因为新路径上的流量可能会与旧路径上的流量产生明显的关联性。如果服务器能够将带有零长度连接 ID 的数据包关联到正确的连接,这意味着服务器正在使用其他信息来对数据包进行多路分解。例如,服务器可能为每个客户端提供唯一的地址——比如使用 HTTP 替代服务 ALTSVC。可能允许跨多个网络路径正确路由数据包的信息,也会允许除对等方之外的实体关联这些路径上的活动。
客户端可能希望通过切换到新的连接 ID、源 UDP 端口或 IP 地址(参见 RFC8981)来减少在一段时间不活动后发送流量时的可关联性。同时更改发送数据包的地址可能会导致服务器检测到连接迁移。这确保了支持迁移的机制即使对于不经历 NAT 重新绑定或真实迁移的客户端也能得到运行。更改地址可能会导致对等方重置其拥塞控制状态(参见第 9.4 节),因此地址应该只在不频繁的情况下更改。
耗尽可用连接 ID 的端点无法探测新路径或发起迁移,也无法响应对等端的探测或迁移尝试。为确保迁移的可能性并防止在不同路径上发送的数据包被关联,端点应该在对等端迁移之前提供新的连接 ID;参见第 5.1.1 节。如果对等端可能已耗尽可用连接 ID,迁移端点可以在新网络路径上发送的所有数据包中包含 NEW_CONNECTION_ID 帧。
Server’s Preferred Address
QUIC 允许服务器在一个 IP 地址上接受连接,并在握手后不久尝试将这些连接转移到更优选的地址。当客户端最初连接到由多个服务器共享的地址,但更愿意使用单播地址来确保连接稳定性时,这特别有用。本节描述了将连接迁移到优选服务器地址的协议。
在连接过程中将连接迁移到新服务器地址不被本文档中指定的QUIC版本所支持。如果客户端在未主动启动向该地址迁移时从新服务器地址接收到数据包,客户端应当丢弃这些数据包。
探测新路径
服务器通过在 TLS 握手中包含 preferred_address 传输参数来传达首选地址。
服务器可以传达每个地址族(IPv4 和 IPv6)的首选地址,以允许客户端选择最适合其网络连接的地址。
一旦握手确认完成,客户端应该(SHOULD)从服务器提供的两个地址中选择一个并启动路径验证(参见第8.2节)。客户端使用任何之前未使用的活跃连接ID构造数据包,这些ID可以从preferred_address传输参数或NEW_CONNECTION_ID帧中获取。
一旦路径验证成功,客户端应该开始使用新的连接 ID 向新的服务器地址发送所有后续数据包,并停止使用旧的服务器地址。如果路径验证失败,客户端必须继续向服务器的原始 IP 地址发送所有后续数据包。
启动连接迁移
迁移到首选地址的客户端必须在迁移前验证其选择的地址;参见第 21.5.3 节。
服务器在接受连接后的任何时候都可能收到寻址到其首选IP地址的数据包。如果此数据包包含PATH_CHALLENGE帧,服务器将根据第8.2节发送包含PATH_RESPONSE帧的数据包。服务器必须从其原始地址发送非探测数据包,直到它在其首选地址收到来自客户端的非探测数据包,并且直到服务器验证了新路径。
服务器必须从其首选地址向客户端方向进行路径探测。这有助于防范由攻击者发起的虚假迁移。
一旦服务器完成其路径验证并在其首选地址上收到带有新的最大数据包编号的非探测数据包,服务器开始专门从其首选IP地址向客户端发送非探测数据包。服务器应该丢弃在旧IP地址上收到的此连接的较新数据包。服务器可以继续处理在旧IP地址上收到的延迟数据包。
服务器在 preferred_address 传输参数中提供的地址仅对提供它们的连接有效。客户端不得将这些地址用于其他连接,包括从当前连接恢复的连接。
响应连接迁移
客户端可能需要在迁移到服务器首选地址之前执行连接迁移。在这种情况下,客户端应该同时从客户端的新地址对原始服务器地址和首选服务器地址执行路径验证。
如果服务器首选地址的路径验证成功,客户端必须放弃对原始地址的验证并迁移到使用服务器的首选地址。如果服务器首选地址的路径验证失败但服务器原始地址的验证成功,客户端可以迁移到其新地址并继续向服务器的原始地址发送数据。
如果服务器在其首选地址上接收到的数据包的源地址与握手期间从客户端观察到的地址不同,服务器必须按照第 9.3.1 节和第 9.3.2 节所述防范潜在攻击。除了有意的同时迁移外,这种情况也可能是因为客户端的接入网络为服务器的首选地址使用了不同的 NAT 绑定。
服务器在收到来自不同地址的探测数据包时,应该启动对客户端新地址的路径验证;参见第8节。
迁移到新地址的客户端应该为服务器使用来自同一地址族的首选地址。
在 preferred_address 传输参数中提供的连接 ID 并不特定于所提供的地址。提供此连接 ID 是为了确保客户端有一个可用于迁移的连接 ID,但客户端可以在任何路径上使用此连接 ID。
对等节点地址欺骗
QUIC 建议使用 IPv6 发送数据的端点应当按照 RFC 6437 的要求应用 IPv6 流标签,除非本地 API 不允许设置 IPv6 流标签。
不幸的是,Java API 不允许设置 IPv6 流标签。
非目标
以下内容复制自 QUIC RFC 9000。对于每个部分,请审查和编辑。
QUIC 的目标是提供安全的传输连接。第 21.1 节概述了这些特性;后续章节讨论了关于这些特性的约束和注意事项,包括已知攻击和对策的描述。
路径上地址欺骗
对 QUIC 进行完整的安全分析超出了本文档的范围。本节提供了所需安全属性的非正式描述,以帮助实现者并指导协议分析。
QUIC 采用 SEC-CONS 中描述的威胁模型,并提供针对该模型产生的许多攻击的保护。
为此目的,攻击分为被动攻击和主动攻击。被动攻击者具有从网络中读取数据包的能力,而主动攻击者还具有向网络中写入数据包的能力。然而,被动攻击可能涉及攻击者具有引起路由更改或对连接数据包所采用路径进行其他修改的能力。
攻击者进一步分为路径内攻击者或路径外攻击者。路径内攻击者可以读取、修改或移除其观察到的任何数据包,使数据包无法到达其目的地,而路径外攻击者可以观察数据包但无法阻止原始数据包到达其预期目的地。两种类型的攻击者都可以发送任意数据包。这个定义与 SEC-CONS 第 3.5 节的定义不同,因为路径外攻击者能够观察数据包。
握手、受保护数据包和连接迁移的属性将分别进行考虑。
离线路径数据包转发
QUIC握手包含了TLS 1.3握手,并继承了TLS13附录E.1中描述的加密属性。QUIC的许多安全属性都依赖于TLS握手提供这些属性。任何对TLS握手的攻击都可能影响QUIC。
任何对TLS握手的攻击,如果危及会话密钥的保密性或唯一性,或者危及参与对等节点的身份验证,都会影响QUIC提供的依赖于这些密钥的其他安全保证。例如,迁移(第9节)依赖于保密性保护的有效性,包括使用TLS握手进行密钥协商和QUIC数据包保护,以避免跨网络路径的可关联性。
对 TLS 握手完整性的攻击可能允许攻击者影响应用协议或 QUIC 版本的选择。
除了 TLS 提供的属性之外,QUIC 握手还提供了一些针对握手 DoS 攻击的防护。
丢包检测和拥塞控制
地址验证(第8节)用于验证声称拥有给定地址的实体能够在该地址接收数据包。地址验证将放大攻击目标限制在攻击者能够观察到数据包的地址上。
在地址验证之前,端点在发送内容方面受到限制。端点不能向未验证的地址发送超过从该地址接收到的数据三倍的数据。
注意:反放大限制仅适用于端点响应从未验证地址接收到的数据包时。反放大限制不适用于客户端建立新连接或启动连接迁移时。
连接迁移的隐私影响
计算服务器完整握手的第一次传输可能开销很大,需要进行签名和密钥交换计算。为了防止计算型 DoS 攻击,Retry 数据包提供了一种低成本的令牌交换机制,允许服务器在执行任何昂贵计算之前验证客户端的 IP 地址,代价仅为一次往返。成功握手后,服务器可以向客户端发放新令牌,这将允许建立新连接而不会产生此类开销。
服务器首选地址
路径上或路径外的攻击者可以通过替换或抢夺Initial数据包来强制握手失败。一旦有效的Initial数据包已经交换,后续的Handshake数据包将受到Handshake密钥的保护,路径上的攻击者除了通过丢弃数据包来导致端点放弃尝试之外,无法强制握手失败。
路径上的攻击者也可以替换数据包两端的地址,从而导致客户端或服务器对远程地址产生错误的认知。这种攻击与NAT执行的功能无法区分。
传达首选地址
整个握手过程受到加密保护,初始数据包使用每版本密钥加密,握手及后续数据包使用从TLS密钥交换派生的密钥加密。此外,参数协商被融入TLS记录中,因此提供与普通TLS协商相同的完整性保证。攻击者可以观察到客户端的传输参数(只要知道特定版本的盐值),但无法观察到服务器的传输参数,也无法影响参数协商。
连接 ID 在所有数据包中都是未加密的,但受到完整性保护。
此版本的 QUIC 不包含版本协商机制;不兼容版本的实现将无法建立连接。
迁移到首选地址
数据包保护(第 12.1 节)对除版本协商数据包之外的所有数据包应用认证加密,尽管初始数据包和重试数据包由于使用版本特定的密钥材料而具有有限的保护;更多详细信息请参见 QUIC-TLS。本节考虑针对受保护数据包的被动和主动攻击。
路径上和路径外的攻击者都可以发起被动攻击,他们保存观察到的数据包以便在将来进行针对数据包保护的离线攻击;这对于任何网络上任何数据包的任何观察者都是如此。
在无法观察到连接中有效数据包的情况下注入数据包的攻击者不太可能成功,因为数据包保护确保只有拥有握手过程中建立的密钥材料的端点才能生成有效数据包;参见第 7 节和第 21.1.1 节。同样,任何观察数据包并试图在这些数据包中插入新数据或修改现有数据的主动攻击者都不应该能够生成被接收端点认为有效的数据包,除了 Initial 数据包。
欺骗攻击是指主动攻击者重写其转发或注入的数据包中未受保护的部分(如源地址或目标地址),这种攻击只有在攻击者能够将数据包转发到原始端点时才有效。数据包保护确保数据包载荷只能由完成握手的端点处理,无效数据包会被这些端点忽略。
攻击者还可以修改数据包和UDP数据报之间的边界,导致多个数据包被合并到单个数据报中,或将合并的数据包拆分为多个数据报。除了包含Initial数据包的数据报(需要填充)之外,修改数据包在数据报中的排列方式对连接没有功能性影响,尽管可能会改变一些性能特征。
客户端迁移和首选地址的交互
连接迁移(第9节)为端点提供了在多个路径之间的IP地址和端口间进行转换的能力,一次使用一个路径来传输和接收非探测帧。路径验证(第8.2节)确定对等节点既愿意也能够接收在特定路径上发送的数据包。这有助于通过限制发送到伪造地址的数据包数量来减少地址欺骗的影响。
本节描述了在各种类型的 DoS 攻击下连接迁移的预期安全属性。
使用 IPv6 流标签和迁移
能够导致其观察到的数据包无法到达预期目标的攻击者被认为是路径上攻击者。当攻击者位于客户端和服务器之间时,端点需要通过该攻击者发送数据包才能在给定路径上建立连接。
路径上的攻击者可以:
检查数据包
修改 IP 和 UDP 数据包头部
注入新数据包
延迟数据包
重新排序数据包
丢弃数据包
在数据包边界处分割和合并数据报
路径上的攻击者无法:
- 修改数据包的已认证部分并使接收方接受该数据包
路径上的攻击者有机会修改其观察到的数据包;然而,对数据包已认证部分的任何修改都会导致接收端点将其作为无效数据包丢弃,因为数据包载荷既经过认证又经过加密。
QUIC 旨在约束路径上攻击者的能力,具体如下:
路径上的攻击者可以阻止连接使用某条路径,如果连接无法使用不包含攻击者的其他路径,则会导致连接失败。这可以通过丢弃所有数据包、修改数据包使其解密失败或其他方法来实现。
路径上的攻击者可以通过导致新路径上的路径验证失败来阻止迁移到攻击者同样位于其路径上的新路径。
路径上的攻击者无法阻止客户端迁移到攻击者不在路径上的路径。
路径上的攻击者可以通过延迟数据包或丢弃数据包来降低连接的吞吐量。
路径上的攻击者无法使端点接受已被其修改认证部分的数据包。
Off-Path Active Attacks
路径外攻击者不在客户端和服务器之间的直接路径上,但可能能够获取客户端和服务器之间发送的部分或全部数据包的副本。它还能够将这些数据包的副本发送到任一端点。
路径外攻击者可以:
检查数据包
注入新数据包
重新排序注入的数据包
路径外攻击者无法:
修改端点发送的数据包
延迟数据包
丢弃数据包
重新排序原始数据包
离线路径攻击者可以创建其观察到的数据包的修改副本,并将这些副本注入到网络中,可能使用伪造的源地址和目标地址。
在本次讨论中,我们假设路径外攻击者具有向网络注入修改后的数据包副本的能力,该副本将在攻击者观察到的原始数据包到达之前到达目标端点。换句话说,攻击者有能力在端点之间与合法数据包的竞争中持续"获胜",可能导致接收方忽略原始数据包。
还假设攻击者具有影响NAT状态所需的资源。特别是,攻击者可以导致端点失去其NAT绑定,然后获得相同的端口用于自己的流量。
QUIC 旨在按如下方式限制路径外攻击者的能力:
路径外攻击者可以通过抢占数据包并尝试成为"受限的"路径上攻击者。
只要能够在客户端和服务器之间提供改进的连接性,路径外攻击者就可以使转发数据包的路径验证成功,这些数据包的源地址列为该路径外攻击者。
路径外攻击者无法在握手完成后导致连接关闭。
离线路径攻击者如果无法观察到新路径,就无法导致迁移到新路径失败。
路径外攻击者可以在迁移到其同样作为路径外攻击者的新路径期间,成为受限的路径上攻击者。
路径外攻击者可以通过影响共享NAT状态成为有限的路径上攻击者,使其从客户端最初使用的相同IP地址和端口向服务器发送数据包。
安全属性概述
受限路径上攻击者是一种路径外攻击者,通过复制和转发服务器与客户端之间的原始数据包来提供改进的数据包路由,使这些数据包在原始副本之前到达,从而导致原始数据包被目标端点丢弃。
有限路径攻击者与路径攻击者的区别在于,它不在端点之间的原始路径上,因此端点发送的原始数据包仍然能够到达其目的地。这意味着,如果未来无法将复制的数据包比原始路径更快地路由到目的地,也不会阻止原始数据包到达目的地。
有限的路径上攻击者可以:
检查数据包
注入新数据包
修改未加密的数据包头
重新排序数据包
有限的路径攻击者无法:
延迟数据包,使其比通过原始路径发送的数据包更晚到达
丢弃数据包
修改数据包的认证和加密部分,并使接收方接受该数据包
有限的路径内攻击者只能延迟数据包直到原始数据包在重复数据包之前到达,这意味着它无法提供比原始路径更差延迟的路由。如果有限的路径内攻击者丢弃数据包,原始副本仍将到达目标端点。
QUIC 旨在按以下方式限制有限离线路径攻击者的能力:
一旦握手完成,受限的路径攻击者无法导致连接关闭。
有限的路径攻击者无法在客户端首先恢复活动时导致空闲连接关闭。
如果服务器是第一个恢复活动的,有限的路径上攻击者可以导致空闲连接被视为丢失。
请注意,这些保证与任何 NAT 提供的保证相同,原因也相同。
握手
作为一种加密和身份验证的传输协议,QUIC 提供了一系列针对拒绝服务攻击的保护。一旦加密握手完成,QUIC 端点会丢弃大部分未经身份验证的数据包,极大地限制了攻击者干扰现有连接的能力。
一旦建立连接,QUIC端点可能会接受一些未经认证的ICMP数据包(见第14.2.1节),但这些数据包的使用极其有限。端点可能接受的唯一其他类型数据包是无状态重置(第10.3节),它依赖于令牌在使用前保持机密。
在建立连接期间,QUIC 只能提供针对网络路径外攻击的保护。所有 QUIC 数据包都包含接收方看到来自对等方的前一个数据包的证明。
地址在握手过程中不能更改,因此端点可以丢弃在不同网络路径上接收到的数据包。
Source 和 Destination Connection ID 字段是在握手过程中防范离线路径攻击的主要手段;参见第 8.1 节。这些字段必须与对等方设置的值匹配。除了 Initial 和 Stateless Resets 之外,端点只接受包含与该端点先前选择的值匹配的 Destination Connection ID 字段的数据包。这是为 Version Negotiation 数据包提供的唯一保护。
Initial 数据包中的目标连接 ID 字段由客户端选择为不可预测的,这还有一个额外的目的。承载加密握手的数据包使用从该连接 ID 和特定于 QUIC 版本的盐值派生的密钥进行保护。这允许端点使用与加密握手完成后相同的过程来验证它们接收到的数据包。无法验证的数据包将被丢弃。以这种方式保护数据包提供了强有力的保证,即数据包的发送方看到了 Initial 数据包并理解了它。
这些保护措施并非旨在有效防范能够在连接建立前接收到 QUIC 数据包的攻击者。此类攻击者可能发送会被 QUIC 端点接受的数据包。此版本的 QUIC 尝试检测这种攻击,但预期端点将无法建立连接而不是恢复连接。在大多数情况下,加密握手协议 QUIC-TLS 负责检测握手过程中的篡改。
端点被允许使用其他方法来检测握手干扰并尝试从中恢复。可以使用其他方法识别和丢弃无效数据包,但本文档中未强制要求任何特定方法。
反放大攻击防护
攻击者可能能够从服务器接收地址验证令牌(第8节),然后释放用于获取该令牌的IP地址。稍后,攻击者可以通过伪造相同的地址与服务器发起0-RTT连接,该地址现在可能指向不同的(受害者)端点。因此,攻击者可能导致服务器向受害者发送相当于初始拥塞窗口数据量的数据。
服务器应该通过限制地址验证令牌的使用和生存期来提供针对此攻击的缓解措施;参见第 8.1.3 节。
服务器端 DoS
确认未收到数据包的端点可能会导致拥塞控制器允许以超出网络支持的速率发送数据。端点可以在发送数据包时跳过数据包编号来检测这种行为。然后端点可以立即以 PROTOCOL_VIOLATION 类型的连接错误关闭连接;参见第 10.2 节。
路径上握手终止
请求伪造攻击发生在一个端点导致其对等方向受害者发出请求的情况下,该请求由端点控制。请求伪造攻击旨在为攻击者提供对其对等方能力的访问权限,这些能力在其他情况下可能对攻击者不可用。对于网络协议,请求伪造攻击通常用于利用受害者因对等方在网络中的位置而赋予对等方的任何隐式授权。
为了使请求伪造生效,攻击者需要能够影响对等节点发送的数据包内容以及这些数据包的发送目标。如果攻击者能够使用受控载荷来针对易受攻击的服务,该服务可能会执行被归因于攻击者对等节点但实际由攻击者决定的操作。
例如,Web上的跨站请求伪造CSRF攻击会导致客户端发出包含授权cookies COOKIE的请求,允许一个站点访问本应限制给不同站点的信息和操作。
由于 QUIC 运行在 UDP 之上,主要关注的攻击模式是攻击者能够选择其对等方发送 UDP 数据报的地址,并能够控制这些数据包中一些未受保护的内容。由于 QUIC 端点发送的大部分数据都受到保护,这包括对密文的控制。如果攻击者能够使对等方向某个主机发送 UDP 数据报,而该主机会基于数据报中的内容执行某些操作,则攻击就算成功。
本节讨论 QUIC 可能被用于请求伪造攻击的方式。
本节还描述了QUIC端点可以实施的有限对策。这些缓解措施可以由QUIC实现或部署单方面采用,而无需请求伪造攻击的潜在目标采取行动。但是,如果基于UDP的服务没有正确授权请求,这些对策可能是不够的。
由于第 21.5.4 节中描述的迁移攻击相当强大且没有足够的对策,QUIC 服务器实现应该假设攻击者可以使它们向任意目标生成任意 UDP 载荷。QUIC 服务器不应部署在没有部署入口过滤 BCP38 且 UDP 端点安全措施不足的网络中。
虽然通常无法确保客户端不与易受攻击的端点位于同一位置,但此版本的QUIC不允许服务器迁移,从而防止对客户端的伪造迁移攻击。任何允许服务器迁移的未来扩展必须同时定义针对伪造攻击的对策。
参数协商
QUIC 为攻击者提供了一些机会来影响或控制其对等节点发送 UDP 数据报的位置:
初始连接建立(第 7 节),其中服务器能够选择客户端发送数据报的位置——例如,通过填充 DNS 记录;
首选地址(第 9.6 节),其中服务器能够 选择客户端发送数据报的位置;
欺骗性连接迁移(第 9.3.1 节),客户端能够使用源地址欺骗来选择服务器发送后续数据报的位置;以及
伪造的数据包,导致服务器发送版本协商 数据包(第 21.5.5 节)。
在所有情况下,攻击者都可能导致其对等节点向可能无法理解 QUIC 的受害者发送数据报。也就是说,这些数据包是对等节点在地址验证之前发送的;参见第 8 节。
在数据包的加密部分之外,QUIC 为端点提供了多种选项来控制其对等端发送的 UDP 数据报的内容。目标连接 ID 字段提供了对对等端发送的数据包中早期出现的字节的直接控制;参见第 5.1 节。初始数据包中的令牌字段为服务器提供了对初始数据包其他字节的控制;参见第 17.2.2 节。
在这个版本的QUIC中,没有措施来防止对数据包加密部分的间接控制。必须假设端点能够控制对等方发送的帧内容,特别是那些传输应用数据的帧,如STREAM帧。虽然这在某种程度上取决于应用协议的细节,但在许多协议使用场景中,某种程度的控制是可能的。由于攻击者可以访问数据包保护密钥,他们很可能能够预测对等方如何加密未来的数据包。成功控制数据报内容只需要攻击者能够以一定的可靠性预测数据包号码和帧在数据包中的位置。
本节假设限制对数据报内容的控制是不可行的。后续章节中缓解措施的重点是限制在地址验证之前发送的数据报用于请求伪造的方式。
受保护数据包
作为服务器的攻击者可以选择其宣告可用性的IP地址和端口,因此来自客户端的Initial数据包被认为可用于此类攻击。握手中隐含的地址验证确保——对于新连接——客户端不会向不理解QUIC或不愿意接受QUIC连接的目标发送其他类型的数据包。
初始数据包保护(QUIC-TLS 第 5.2 节)使服务器难以控制客户端发送的初始数据包的内容。客户端选择不可预测的目标连接 ID 可确保服务器无法控制来自客户端的初始数据包的任何加密部分。
然而,Token 字段对服务器控制开放,确实允许服务器利用客户端发起请求伪造攻击。使用 NEW_TOKEN 帧(第 8.1.3 节)提供的 token 是在连接建立期间进行请求伪造的唯一选项。
然而,客户端并不必须使用 NEW_TOKEN 帧。如果客户端在服务器地址相比接收到 NEW_TOKEN 帧时发生变化的情况下发送空的 Token 字段,可以避免依赖 Token 字段的请求伪造攻击。
如果服务器地址发生变化,客户端可以避免使用 NEW_TOKEN。但是,不包含 Token 字段可能会对性能产生不利影响。服务器可以依赖 NEW_TOKEN 来启用发送超过三倍限制的数据发送;参见第 8.1 节。特别是,这会影响客户端使用 0-RTT 从服务器请求数据的情况。
发送重试数据包(第17.2.5节)为服务器提供了更改Token字段的选项。发送重试后,服务器还可以控制来自客户端的后续Initial数据包的目标连接ID字段。这也可能允许对Initial数据包的加密内容进行间接控制。但是,重试数据包的交换验证了服务器的地址,从而防止使用后续的Initial数据包进行请求伪造。
连接迁移
服务器可以指定一个首选地址,客户端在确认握手后会迁移到该地址;参见第9.6节。客户端发送到首选地址的数据包中的目标连接ID字段可能被用于请求伪造。
客户端在验证首选地址之前绝不能向该地址发送非探测帧;请参见第8节。这大大减少了服务器控制数据报加密部分的选项。
本文档不提供任何针对首选地址使用的特定对策,这些对策可由端点实施。第21.5.6节中描述的通用措施可用作进一步的缓解手段。
路径上主动攻击
客户端能够在表面上的连接迁移过程中提供伪造的源地址,从而导致服务器向该地址发送数据报。
服务器后续发送到此伪造地址的任何数据包中的目标连接ID字段都可能被用于请求伪造。客户端也可能能够影响密文。
在地址验证之前仅向地址发送探测数据包(第9.1节)的服务器只为攻击者提供了对数据报加密部分的有限控制。然而,特别是对于NAT重新绑定,这可能会对性能产生不利影响。如果服务器发送承载应用程序数据的帧,攻击者可能能够控制数据报的大部分内容。
本文档没有提供端点可以实施的具体对策,除了第 21.5.6 节中描述的通用措施。然而,网络层面的地址欺骗对策——特别是入站过滤 BCP38——对于使用欺骗并源自外部网络的攻击特别有效。
离线路径主动攻击
能够在数据包中提供伪造源地址的客户端可能导致服务器向该地址发送版本协商数据包(第17.2.1节)。
对于未知版本的数据包,连接ID字段没有大小限制,这增加了客户端从结果数据报中控制的数据量。该数据包的第一个字节不受客户端控制,接下来的四个字节为零,但客户端能够从第五个字节开始控制最多512字节的数据。
对于此攻击没有提供特定的对策,不过通用保护措施(第21.5.6节)可能适用。在这种情况下,入口过滤 BCP38 也是有效的。
有限的路径上主动攻击
对抗请求伪造攻击最有效的防御措施是修改易受攻击的服务以使用强身份验证。然而,这并不总是在QUIC部署的控制范围内。本节概述了QUIC端点可以单方面采取的一些其他步骤。这些额外的步骤都是可选的,因为根据具体情况,它们可能会干扰或阻止合法使用。
通过回环接口提供的服务通常缺乏适当的身份验证。端点可以阻止连接尝试或迁移到回环地址。如果同一服务之前在不同接口上可用,或者地址是由非回环地址的服务提供的,端点不应允许连接或迁移到回环地址。依赖这些功能的端点可以提供禁用这些保护的选项。
同样,终端可能会将地址从全局、唯一本地RFC4193或非私有地址变更为链路本地地址RFC4291或私有使用范围内的地址RFC1918视为潜在的请求伪造尝试。终端可以完全拒绝使用这些地址,但这会带来干扰合法使用的重大风险。除非终端对网络有特定了解,表明向给定范围内的未验证地址发送数据报是不安全的,否则终端不应拒绝使用某个地址。
端点可以选择通过不在Initial数据包中包含来自NEW_TOKEN帧的值,或仅在完成地址验证之前的数据包中发送探测帧来降低请求伪造的风险。请注意,这并不能阻止攻击者使用目标连接ID字段进行攻击。
端点不需要掌握可能成为请求伪造攻击脆弱目标的服务器位置的具体信息。然而,随着时间推移,可能会识别出攻击常见目标的特定UDP端口,或者用于攻击的数据报中的特定模式。端点可以选择避免向这些端口发送数据报,或者在验证目标地址之前不发送匹配这些模式的数据报。端点可以丢弃包含已知问题模式的连接ID而不使用它们。
注意:修改端点以应用这些保护措施比部署基于网络的保护更加高效,因为端点在向已验证的地址发送数据时无需执行任何额外处理。
握手拒绝服务攻击
通常被称为 Slowloris SLOWLORIS 的攻击试图保持与目标端点的多个连接处于开放状态,并尽可能长时间地保持这些连接。这些攻击可以通过产生避免因不活跃而被关闭所需的最小活动量来对 QUIC 端点执行。这可能涉及发送少量数据、逐渐打开流量控制窗口以控制发送方速率,或者制造模拟高丢失率的 ACK 帧。
QUIC 部署应该提供针对 Slowloris 攻击的缓解措施,例如增加服务器允许的最大客户端数量、限制单个 IP 地址允许建立的连接数、对连接允许的最小传输速度施加限制,以及限制端点允许保持连接的时间长度。
放大攻击
恶意发送者可能故意不发送流数据的某些部分,导致接收者为未发送的数据分配资源。这可能导致接收端出现不成比例的接收缓冲区内存占用和/或创建庞大且低效的数据结构。
恶意接收方可能故意不确认包含流数据的数据包,试图迫使发送方存储未确认的流数据以备重传。
如果流控制窗口与可用内存相对应,则可以减轻对接收方的攻击。但是,一些接收方会过度承诺内存,并在总体上公布超过实际可用内存的流控制偏移量。当端点行为良好时,过度承诺策略可以带来更好的性能,但会使端点容易受到流分片攻击。
QUIC 部署应该提供针对流分段攻击的缓解措施。缓解措施可能包括避免过度提交内存、限制跟踪数据结构的大小、延迟 STREAM 帧的重组、基于重组空洞的时长和持续时间实施启发式算法,或者这些方法的某种组合。
乐观 ACK 攻击
恶意端点可以开启大量流,耗尽端点上的状态。恶意端点可以在大量连接上重复此过程,其方式类似于TCP中的SYN洪水攻击。
通常,客户端会按顺序打开流,如第2.1节所述。然而,当多个流在短时间间隔内启动时,丢包或重排序可能导致打开流的 STREAM 帧被乱序接收。在接收到更高编号的流 ID 时,接收方需要打开所有相同类型的中间流;参见第3.2节。因此,在新连接上,打开流 4000000 会打开 100万零1个客户端发起的双向流。
活跃流的数量受到 initial_max_streams_bidi 和 initial_max_streams_uni 传输参数的限制,这些参数会根据接收到的任何 MAX_STREAMS 帧进行更新,如第 4.6 节所述。如果选择得当,这些限制可以减轻流承诺攻击的影响。但是,将限制设置得过低可能会在应用程序期望打开大量流时影响性能。
请求伪造攻击
QUIC 和 TLS 都包含在某些情况下具有合法用途的帧或消息,但这些帧或消息可能被滥用,导致对等方消耗处理资源,而不会对连接状态产生任何可观察的影响。
消息也可以用于以微小或不重要的方式更改和恢复状态,例如通过向流控制限制发送小的增量。
如果处理成本与带宽消耗或对状态的影响相比过大,那么这可能允许恶意对等节点耗尽处理能力。
虽然所有消息都有合法用途,但实现方应该跟踪相对于进展的处理成本,并将任何非生产性数据包的过量视为攻击的指示。端点可以通过连接错误或丢弃数据包来响应这种情况。
端点的控制选项
路径上的攻击者可以操纵IP头部中ECN字段的值来影响发送方的速率。RFC3168更详细地讨论了操纵行为及其影响。
有限的路径攻击者可以复制并发送带有修改过的ECN字段的数据包来影响发送方的速率。如果重复数据包被接收方丢弃,攻击者需要让重复数据包与原始数据包竞争才能成功进行此攻击。因此,QUIC端点会忽略IP数据包中的ECN字段,除非该IP数据包中至少有一个QUIC数据包被成功处理;参见第13.4节。
使用客户端初始数据包的请求伪造
无状态重置会产生类似于 TCP 重置注入的可能拒绝服务攻击。如果攻击者能够为具有选定连接 ID 的连接生成无状态重置令牌,则此攻击成为可能。能够生成此令牌的攻击者可以重置具有相同连接 ID 的活动连接。
如果数据包可以路由到共享静态密钥的不同实例——例如,通过更改IP地址或端口——那么攻击者可以导致服务器发送无状态重置。为了防御这种拒绝服务攻击,共享无状态重置静态密钥的端点(参见第10.3.2节)必须进行安排,以确保具有给定连接ID的数据包始终到达具有连接状态的实例,除非该连接不再活跃。
更一般地说,如果使用相同静态密钥的任何端点上可能存在具有相应连接ID的活跃连接,服务器绝对不能生成无状态重置。
在使用动态负载均衡的集群情况下,可能会在活跃实例仍保持连接状态时发生负载均衡器配置变更。即使实例保持连接状态,路由变更和由此产生的无状态重置也会导致连接被终止。如果数据包没有机会被路由到正确的实例,最好发送无状态重置而不是等待连接超时。然而,这只有在路由不能被攻击者影响的情况下才是可接受的。
使用首选地址的请求伪造
本文档定义了 QUIC Version Negotiation 数据包(第6节),可用于协商两个端点之间使用的 QUIC 版本。但是,本文档未指定如何在此版本与后续未来版本之间执行此协商。特别是,Version Negotiation 数据包不包含任何防止版本降级攻击的机制。使用 Version Negotiation 数据包的未来 QUIC 版本必须定义一种能够抵御版本降级攻击的健壮机制。
伪造迁移的请求伪造
部署应该限制攻击者将新连接定向到特定服务器实例的能力。理想情况下,路由决策应独立于客户端选择的值,包括地址。一旦选择了实例,就可以选择一个连接ID,以便后续数据包路由到同一实例。
请求伪造与版本协商
QUIC 数据包的长度可能会泄露这些数据包内容长度的信息。提供 PADDING 帧是为了让端点具有某种能力来模糊数据包内容的长度;请参见第 19.1 节。
击败流量分析是一项挑战性工作,也是活跃研究的主题。长度并不是信息可能泄露的唯一方式。端点还可能通过其他侧信道泄露敏感信息,例如数据包的时序。
Relay Security
以下是对 SSU1 中 Relay Request、Relay Response、Relay Intro 和 Hole Punch 的分析。
约束条件:Relay 必须快速,这一点很重要。应该最小化往返次数。带宽和 CPU 不那么重要。
SSU 1: Alice 首先连接到介绍者 Bob,Bob 将请求转发给 Charlie(Charlie 位于防火墙后)。在打洞完成后,Alice 和 Charlie 之间建立会话,就像直接建立连接一样。
Alice Bob Charlie
1. RelayRequest ---------------------->
2. <-------------- RelayResponse RelayIntro ----------->
3. <-------------------------------------------- HolePunch
4. SessionRequest -------------------------------------------->
5. <-------------------------------------------- SessionCreated
6. SessionConfirmed ------------------------------------------>
身份验证:Relay Request 和 Relay Response 没有安全的未认证机制,因为 Alice 和 Bob 通常没有现有的会话;这些消息使用已发布的介绍密钥。如果会话存在,则允许并首选会话内的 Relay Request/Response。
从 Bob 到 Charlie 的中继介绍需要在现有会话中进行,因此被认为是安全的。
Bob 可能会伪造 Relay Intro 或更改 Relay Request 中的 IP/端口。没有机制能够以加密方式将请求绑定到 intro,或者以其他方式防止或检测恶意的 Bob。
Bob的router hash目前未在Charlie的Router Info中发布,因此如果我们希望Alice-Bob消息被认证,就必须添加它。此外,其他SSU2参数也必须在Charlie的Router Info中发布,否则Alice就必须在网络数据库中查找Bob的Router Info,这会增加额外的延迟。认证会在Alice和Bob之间增加一次往返。
通过将Alice的router哈希转发给Charlie,Charlie可以通过检查本地封禁列表来更容易地确定是否愿意接收来自Alice的连接。没有机制让Charlie通过Bob向Alice发送拒绝消息来拒绝中继。也没有机制让Charlie通过Bob向Alice发送接受消息来接受中继。Alice必须等待HolePunch,或者直接盲目发送SessionRequest。由于NAT的存在,HolePunch可能来自与Alice预期不同的端口,这可能使识别HolePunch来自哪个router变得更困难。
Alice 可以在 Relay Request 中向 Bob 发送她的完整 Router Info,并在 Relay Intro 中转发给 Charlie。
Relay Request 不包含时间戳,因此没有重放攻击防护。源 IP 可以被伪造,导致 Charlie 向任意 IP/端口发送 Hole Punch。Relay Request 未经签名,即使经过签名和时间戳验证,Charlie 也没有完整的 Router Identity 来验证签名。
该协议定义了一个可变长度为 0-255 字节的质询字段。Relay Request 中的质询会在 Relay Intro 中传递给 Charlie。然而,协议并未指定如何创建、使用或验证质询,且该功能尚未实现。如果 HolePunch 包含质询,Alice 就能够轻易地将 HolePunch 与 Charlie 关联起来。
四字节 nonce 可能需要被八字节连接 ID 替换或补充。
空的 Hole Punch 消息是唯一的,可能被路径上的观察者用来识别协议,这应该被改变。
额外的 DPI 讨论
以下是对 SSU1 中 Peer Test 的分析。
约束条件:Peer Tests 的速度快慢、带宽占用多少或CPU使用率高低并不是特别重要,除了在router启动时,我们希望router能够相当快速地发现其可达性。
SSU 1:
Alice Bob Charlie
1. PeerTest ------------------->
2. PeerTest-------------------->
3. <-------------------PeerTest
4. <-------------------PeerTest
5. <------------------------------------------PeerTest
6. PeerTest------------------------------------------>
7. <------------------------------------------PeerTest
由于 SSU1 规范难以理解,我们在下面记录消息内容。
| Message | Path | Alice IP incl? | Intro Key |
|---|---|---|---|
| 1 | A->B session | no | Alice |
| 2 | B->C session | yes | Alice |
| 3 | C->B session | yes | Charlie |
| 4 | B->A session | yes | Charlie |
| 5 | C->A | yes | Charlie |
| 6 | A->C | no | Alice |
| 7 | C->A | yes | Charlie |
| 身份验证:Alice 将始终选择具有现有会话的 Bob。Bob 将拒绝来自没有已建立会话的对等节点的 PeerTest。消息 1 在会话中发送。因此,消息 1 是安全且经过身份验证的。 |
Bob 选择一个与他有现有会话的 Charlie。消息 2 和 3 在会话中发送。因此,消息 2 和 3 是安全且经过身份验证的。
消息4应该在会话内发送;然而,SSU 1规范之前规定它使用Alice的已发布intro key发送,这意味着不在会话内。在0.9.52版本之前,Java I2P确实使用intro key发送。从0.9.52版本开始,规范规定应该使用session key,Java I2P从0.9.52版本开始在会话内发送消息。
Alice 不能与 Charlie 已有现存会话才能进行测试;如果 Bob 选择了一个与 Alice 已有会话的 Charlie,Alice 会终止测试。因此,消息 5-7 不是安全和认证的。
所有 Peer Test 消息都包含一个由 Alice 选择的 4 字节 nonce。这个 nonce 不用于加密用途。
消息 5-7 上可能存在的攻击:有待研究。
Alice的router hash对Charlie来说是未知的。Charlie的router hash对Alice来说是未知的。如果我们希望Alice-Charlie消息经过身份验证,这些必须添加到协议中。此外,其他SSU2参数必须在Peer Test消息中提供,或者Charlie必须在netDb中查找Alice的Router Info,这会增加额外的延迟。身份验证会在Charlie和Alice之间增加一个往返通信。
通过将 Alice 的 router hash 转发给 Charlie,Charlie 可以通过检查本地禁止列表来更容易地确定他是否愿意与 Alice 参与 Peer Test。
四字节 nonce 可能需要被 8 字节连接 ID 替换或补充。
Relay and Peer Test Design Goals
Relay 和 Peer Test 具有相似的结构。在这两种情况下,Alice 请求 Bob 将服务请求转发给 Charlie,然后 Charlie 对该请求进行处理。
当前 SSU1 对等节点测试问题:
- Peer Test 对恶意的 Bob 没有任何防护措施
- Peer Test 没有办法让 Bob 或 Charlie 拒绝请求
- Peer Test 没有办法让 Alice 知道 Charlie 的身份 或让 Alice 拒绝某个 Charlie
- Peer Test 没有办法让 Charlie 知道 Alice 的身份 或让 Charlie 拒绝某个 Alice
- Peer Test 有自己的临时重传方案
- Peer Test 需要复杂的状态机来知道 哪个消息对应哪个状态
- 在不知道 Charlie 已经拒绝了她的情况下, Alice 会将测试视为失败。
当前 SSU1 中继问题:
上述列出的大部分 Peer Test 问题也适用于 Peer Test。
我们在改进 Relay 和 Peer Test 安全性方面有以下目标:
Charlie应该在netDb中发布足够的关于其介绍者(Bob们)的信息,以便Alice在必要时能够验证这些信息。例如,为每个介绍者发布一个路由器哈希值将使Alice在时间允许的情况下能够从netDb中获取路由器信息。
防范可能欺骗、篡改、伪造或重放从 Alice 到 Bob 请求的地址欺骗或路径威胁。 Bob 必须确保 Alice 是一个真实的 I2P router,并且所提供的请求和测试地址是有效的。
防范恶意的 Bob 可能会伪造、篡改、伪造或重放 转发给 Charlie 的请求。 Charlie 必须确保 Alice 和 Bob 都是真实的 I2P router,并且 所提供的请求和测试地址都是有效的。
Bob必须从Alice那里接收到足够的信息,以便能够验证 请求并接受或拒绝它。 Bob必须有一个机制将接受或拒绝的结果发送回 Alice。 Bob绝不能被要求执行所请求的操作。
Charlie必须从Bob那里接收足够的信息,以便能够验证 请求,然后接受或拒绝它。 Charlie必须有一种机制将接受或拒绝的结果发送回 Bob,以便转发给Alice。 Charlie绝不能被要求执行所请求的操作。
Alice 必须能够验证通过 Bob 转发的响应确实源自 Charlie。
Alice和Charlie必须能够验证他们后续的直接消息(不通过Bob中继)来自预期的源,并且是真实的I2P router。
以下机制可能有助于实现这些目标:
时间戳
使用路由器签名密钥的签名
使用请求中包含的挑战数据
使用路由器加密密钥进行加密
发送路由器哈希值、Router Identity 或 Router Info, 而不仅仅是 IP 和端口。
通过查询网络数据库验证router信息
检查路由器信息、IP 和端口是否在封禁列表中
速率限制
需要建立会话
这些可能的机制可能会增加中继或对等测试功能的处理时间和延迟。必须评估所有影响。
如果可能的话,还应该支持跨版本中继和对等节点测试。这将促进从 SSU 1 到 SSU 2 的逐步过渡。可能的版本组合有:
| Alice/Bob | Bob/Charlie | Alice/Charlie | Supported |
|---|---|---|---|
| 1 | 1 | 1 | SSU 1 |
| 1 | 1 | 2 | no, use 1/1/1 |
| 1 | 2 | 1 | Relay: yes? Peer Test: no |
| 1 | 2 | 2 | no, use 1/2/1 |
| 2 | 1 | 1 | Relay: yes? Peer Test: no |
| 2 | 1 | 2 | Relay: yes? Peer Test: no |
| 2 | 2 | 1 | no, use 2/2/2 |
| 2 | 2 | 2 | yes |
安全目标
Summary
我们依赖多个现有协议,包括I2P内部协议和外部标准,来获得灵感、指导和代码复用:
威胁模型:来自 NTCP2 NTCP2,以及 QUIC RFC 9000 RFC 9001 分析的与 UDP 传输相关的重要额外威胁。
密码学选择:来自 NTCP2。
握手:来自 NTCP2 和 NOISE 的 Noise XK。由于 UDP 提供的封装(固有消息边界),可以对 NTCP2 进行重大简化。
标头用作 AEAD 关联数据,如 ECIES 中所述。
消息:改编自 SSU
I2NP 分片:改编自 SSU
中继和对等测试:改编自 SSU
Relay和Peer Test数据的签名:来自通用结构规范 Common
Acks, nacks:改编自 QUIC RFC 9000。
流量控制:TBD
没有在I2P中未曾使用过的新的密码学基元。
地址验证
与其他I2P传输协议NTCP、NTCP2和SSU 1一样,此传输协议不是用于按序传递字节流的通用设施。它是专为传输I2NP消息而设计的。不提供"流"抽象。
此外,就SSU而言,它包含了用于对等体辅助NAT穿越和可达性测试(入站连接)的附加功能。
至于 SSU 1,它不提供 I2NP 消息的按序传递。它也不提供 I2NP 消息的可靠传递保证。出于效率考虑,或者由于 UDP 数据报的乱序传递或丢失,I2NP 消息可能会乱序传递到远端,或者可能根本无法传递。如有必要,I2NP 消息可能会被多次重传,但传递最终可能会失败,而不会导致整个连接断开。此外,即使在对其他 I2NP 消息进行重传(丢失恢复)时,新的 I2NP 消息也可能继续被发送。
此协议并不能完全防止I2NP消息的重复传递。router应该强制执行I2NP过期机制,并使用布隆过滤器或其他基于I2NP消息ID的机制。请参阅下面的I2NP消息重复部分。
Noise Protocol Framework
此提案基于 Noise Protocol Framework NOISE(修订版 33,2017-10-04)提供要求。Noise 具有与 Station-To-Station 协议类似的特性,Station-To-Station (STS) 协议是 SSU 协议的基础。在 Noise 术语中,Alice 是发起方,Bob 是响应方。
SSU2 基于 Noise 协议 Noise_XK_25519_ChaChaPoly_SHA256。(用于初始密钥派生函数的实际标识符是"Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256"以表示 I2P 扩展 - 请参见下面的 KDF 1 部分)
注意:此标识符与用于 NTCP2 的标识符不同,因为所有三个握手消息都使用头部作为关联数据。
此 Noise 协议使用以下基本组件:
Handshake Pattern: XK Alice 将她的密钥传输给 Bob (X) Alice 已经知道 Bob 的静态密钥 (K)
DH Function: X25519 X25519 DH,密钥长度为32字节,如RFC 7748中所规定。
密码函数:ChaChaPoly AEAD_CHACHA20_POLY1305 如 RFC 7539 第 2.8 节所规定。 12 字节随机数,前 4 个字节设置为零。
Hash Function: SHA256 标准32字节哈希,已在I2P中广泛使用。
Additions to the Framework
该提议定义了对 Noise_XK_25519_ChaChaPoly_SHA256 的以下增强。这些增强通常遵循 NOISE 第 13 节的指导原则。
握手消息(Session Request、Created、Confirmed)包含一个 16 或 32 字节的头部。
握手消息的头部(Session Request、Created、Confirmed)在加密/解密之前用作 mixHash() 的输入,以将头部绑定到消息。
头部信息经过加密和保护。
明文临时密钥使用已知密钥和IV通过ChaCha20加密进行混淆。这比elligator2更快。
负载格式为消息 1、消息 2 和数据阶段定义。当然,这在 Noise 中没有定义。
数据阶段使用与 Noise 数据阶段类似但不兼容的加密方式。
Processing overhead estimate
待定
Definitions
我们定义以下与所使用的加密构建块相对应的函数。
ZEROLEN
zero-length byte array
H(p, d)
SHA-256 hash function that takes a personalization string p and data d, and
produces an output of length 32 bytes.
As defined in [NOISE](https://noiseprotocol.org/noise.html).
|| below means append.
Use SHA-256 as follows::
H(p, d) := SHA-256(p || d)
MixHash(d)
SHA-256 hash function that takes a previous hash h and new data d,
and produces an output of length 32 bytes.
|| below means append.
Use SHA-256 as follows::
MixHash(d) := h = SHA-256(h || d)
STREAM
The ChaCha20/Poly1305 AEAD as specified in [RFC 7539](https://tools.ietf.org/html/rfc7539).
S_KEY_LEN = 32 and S_IV_LEN = 12.
ENCRYPT(k, n, plaintext, ad)
Encrypts plaintext using the cipher key k, and nonce n which MUST be unique for
the key k.
Associated data ad is optional.
Returns a ciphertext that is the size of the plaintext + 16 bytes for the HMAC.
The entire ciphertext must be indistinguishable from random if the key is secret.
DECRYPT(k, n, ciphertext, ad)
Decrypts ciphertext using the cipher key k, and nonce n.
Associated data ad is optional.
Returns the plaintext.
DH
X25519 public key agreement system. Private keys of 32 bytes, public keys of 32
bytes, produces outputs of 32 bytes. It has the following
functions:
GENERATE_PRIVATE()
Generates a new private key.
DERIVE_PUBLIC(privkey)
Returns the public key corresponding to the given private key.
DH(privkey, pubkey)
Generates a shared secret from the given private and public keys.
HKDF(salt, ikm, info, n)
A cryptographic key derivation function which takes some input key material ikm (which
should have good entropy but is not required to be a uniformly random string), a salt
of length 32 bytes, and a context-specific 'info' value, and produces an output
of n bytes suitable for use as key material.
Use HKDF as specified in [RFC 5869](https://tools.ietf.org/html/rfc5869), using the HMAC hash function SHA-256
as specified in [RFC 2104](https://tools.ietf.org/html/rfc2104). This means that SALT_LEN is 32 bytes max.
MixKey(d)
Use HKDF() with a previous chainKey and new data d, and
sets the new chainKey and k.
As defined in [NOISE](https://noiseprotocol.org/noise.html).
Use HKDF as follows::
MixKey(d) := output = HKDF(chainKey, d, "", 64)
chainKey = output[0:31]
k = output[32:63]
Messages
每个 UDP 数据报恰好包含一条消息。数据报的长度(在 IP 和 UDP 头部之后)就是消息的长度。填充(如果有的话)包含在消息内部的填充块中。在本文档中,我们大多数情况下交替使用术语"数据报"和"数据包"。每个数据报(或数据包)包含单条消息(与 QUIC 不同,QUIC 的数据报可能包含多个 QUIC 数据包)。“数据包头部"是指 IP/UDP 头部之后的部分。
例外:Session Confirmed 消息的独特之处在于它可能会分片到多个数据包中。有关更多信息,请参阅下面的 Session Confirmed 分片部分。
所有SSU2消息的长度至少为40字节。任何长度为1-39字节的消息都是无效的。所有SSU2消息的长度小于或等于1472字节(IPv4)或1452字节(IPv6)。消息格式基于Noise消息,并针对帧和不可区分性进行了修改。使用标准Noise库的实现必须将接收到的消息预处理为标准Noise消息格式。所有加密字段都是AEAD密文。
定义了以下消息:
| Type | Message | Header Length | Header Encr. Length |
|---|---|---|---|
| 0 | SessionRequest | 32 | 64 |
| 1 | SessionCreated | 32 | 64 |
| 2 | SessionConfirmed | 16 | 16 |
| 6 | Data | 16 | 16 |
| 7 | PeerTest | 32 | 32 |
| 9 | Retry | 32 | 32 |
| 10 | Token Request | 32 | 32 |
| 11 | HolePunch | 32 | 32 |
Session Establishment
当Alice拥有之前从Bob接收到的有效token时,标准建立序列如下:
Alice Bob
SessionRequest ------------------->
<------------------- SessionCreated
SessionConfirmed ----------------->
当 Alice 没有有效的 token 时,建立序列如下:
Alice Bob
TokenRequest --------------------->
<--------------------------- Retry
SessionRequest ------------------->
<------------------- SessionCreated
SessionConfirmed ----------------->
当 Alice 认为她拥有有效的令牌,但 Bob 拒绝了它时(可能因为 Bob 重启了),建立序列如下:
Alice Bob
SessionRequest ------------------->
<--------------------------- Retry
SessionRequest ------------------->
<------------------- SessionCreated
SessionConfirmed ----------------->
Bob 可以通过回复包含带有原因代码的 Termination 块的 Retry 消息来拒绝 Session 或 Token Request。基于原因代码,Alice 应该在一段时间内不尝试另一个请求:
Alice Bob
SessionRequest ------------------->
<--------------------------- Retry containing a Termination block
or
TokenRequest --------------------->
<--------------------------- Retry containing a Termination block
使用 Noise 术语,建立和数据序列如下:(负载安全属性)
XK(s, rs): Authentication Confidentiality
<- s
...
-> e, es 0 2
<- e, ee 2 1
-> s, se 2 5
<- 2 5
一旦会话建立,Alice 和 Bob 就可以交换数据消息。
Packet Header
所有数据包都以混淆(加密)头部开始。头部有两种类型,长头部和短头部。请注意,前13个字节(目标连接ID、数据包编号和类型)对于所有头部都是相同的。
通用请求伪造对抗措施
长报头为32字节。它用于会话创建之前的Token Request、SessionRequest、SessionCreated和Retry。它还用于会话外的Peer Test和Hole Punch消息。
在 header 加密之前:
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type| ver| id |flag|
+----+----+----+----+----+----+----+----+
| Source Connection ID |
+----+----+----+----+----+----+----+----+
| Token |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: 8 bytes, unsigned big endian integer
Packet Number :: 4 bytes, unsigned big endian integer
type :: The message type = 0, 1, 7, 9, 10, or 11
ver :: The protocol version, equal to 2
id :: 1 byte, the network ID (currently 2, except for test networks)
flag :: 1 byte, unused, set to 0 for future compatibility
Source Connection ID :: 8 bytes, unsigned big endian integer
Token :: 8 bytes, unsigned big endian integer
Slowloris 攻击
短头部长度为16字节。它用于Session Created和Data消息。未认证的消息,如Session Request、Retry和Peer Test将始终使用长头部。
需要16字节,因为接收方必须解密前16字节来获取消息类型,然后如果消息类型表明这实际上是一个长header,则必须再解密额外的16字节。
对于会话确认,在头部加密之前:
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type|frag| flags |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: 8 bytes, unsigned big endian integer
Packet Number :: 4 bytes, all zeros
type :: The message type = 2
frag :: 1 byte fragment info:
bit order: 76543210 (bit 7 is MSB)
bits 7-4: fragment number 0-14, big endian
bits 3-0: total fragments 1-15, big endian
flags :: 2 bytes, unused, set to 0 for future compatibility
有关 frag 字段的更多信息,请参阅下面的会话确认分片部分。
对于数据消息,在消息头加密之前:
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type|flag|moreflags|
+----+----+----+----+----+----+----+----+
Destination Connection ID :: 8 bytes, unsigned big endian integer
Packet Number :: 4 bytes, unsigned big endian integer
type :: The message type = 6
flag :: 1 byte flags:
bit order: 76543210 (bit 7 is MSB)
bits 7-1: unused, set to 0 for future compatibility
bits 0: when set to 1, immediate ack requested
moreflags :: 2 bytes, unused, set to 0 for future compatibility
流分片和重组攻击
连接 ID 必须随机生成。源 ID 和目标 ID 不得相同,这样路径上的攻击者就无法捕获数据包并将其发送回看起来有效的发起者。不要使用计数器来生成连接 ID,这样路径上的攻击者就无法生成看起来有效的数据包。
与 QUIC 不同,我们不会在握手期间或握手之后更改连接 ID,即使在重试消息之后也是如此。ID 从第一条消息(令牌请求或会话请求)到最后一条消息(带终止的数据)保持不变。此外,连接 ID 在路径质询或连接迁移期间或之后也不会更改。
与QUIC的另一个不同之处是,报头中的连接ID总是经过报头加密的。见下文。
流量承诺攻击
如果在握手过程中没有发送 First Packet Number 块,数据包在单个会话中按方向进行编号,从 0 开始,最大到 (2**32 -1)。必须在发送的数据包数量接近最大值之前终止会话并创建新会话。
如果在握手过程中发送了首个数据包编号块,则在单个会话中,对于该方向,数据包从该数据包编号开始编号。数据包编号可能在会话期间回绕。当发送了最多 2**32 个数据包时,数据包编号会回绕到第一个数据包编号,此时该会话不再有效。必须在发送最大数据包数量之前终止会话并创建新会话。
TODO 密钥轮换,减少最大数据包数量?
确定丢失的握手数据包会整个重传,包含相同的头部(包括数据包编号)。握手消息 Session Request、Session Created 和 Session Confirmed 必须使用相同的数据包编号和相同的加密内容进行重传,以便使用相同的链式哈希来加密响应。Retry 消息永远不会被传输。
被确定为丢失的数据阶段数据包永远不会被整体重传(终止除外,见下文)。这同样适用于丢失数据包中包含的块。相反,块中可能携带的信息会根据需要在新数据包中重新发送。数据包永远不会使用相同的数据包编号进行重传。数据包内容的任何重传(无论内容是否保持不变)都必须使用下一个未使用的数据包编号。
按原样重新传输未更改的完整数据包(使用相同的数据包编号)是不被允许的,原因有几个。背景信息请参见 QUIC RFC 9000 第 12.3 节。
- 存储数据包以供重传是低效的
- 新数据包对路径上的观察者来说看起来不同,无法判断它是重传的
- 新数据包会携带更新的确认块,而不是旧的确认块
- 你只重传必要的内容。一些片段可能已经被重传过一次并得到确认
- 如果有更多待处理的内容,你可以在每个重传数据包中放入尽可能多的内容
- 为了检测重复而跟踪所有单独数据包的端点面临累积过多状态的风险。 通过维护一个最小数据包编号(低于该编号的所有数据包都会立即丢弃) 可以限制检测重复所需的数据。
- 这种方案更加灵活
新数据包用于承载被确定已丢失的信息。一般来说,当包含某信息的数据包被确定丢失时,该信息会被重新发送,而当包含该信息的数据包被确认收到时,发送就会停止。
例外:包含终止块的数据阶段数据包可以(但不要求)作为整体原样重传。请参阅下面的会话终止部分。
以下数据包包含一个被忽略的随机数据包编号:
- Session Request
- Session Created
- Token Request
- Retry
- Peer Test
- Hole Punch
对于 Alice,出站数据包编号从 Session Confirmed 开始为 0。对于 Bob,出站数据包编号从第一个 Data 数据包开始为 0,该数据包应该是对 Session Confirmed 的 ACK。标准握手示例中的数据包编号将为:
Alice Bob
SessionRequest (r) ------------>
<------------- SessionCreated (r)
SessionConfirmed (0) ------------>
<------------- Data (0) (Ack-only)
Data (1) ------------> (May be sent before Ack is received)
<------------- Data (1)
Data (2) ------------>
Data (3) ------------>
Data (4) ------------>
<------------- Data (2)
r = random packet number (ignored)
Token Request, Retry, and Peer Test
also have random packet numbers.
任何握手消息(SessionRequest、SessionCreated 或 SessionConfirmed)的重传都必须保持不变地重新发送,使用相同的数据包编号。在重传这些消息时,不要使用不同的临时密钥或更改载荷。
对等节点拒绝服务攻击
头部(在混淆和保护之前)总是包含在AEAD函数的关联数据中,以密码学方式将头部绑定到数据上。
显式拥塞通知攻击
头部加密有几个目标。请参见上面的"额外 DPI 讨论"部分了解背景和假设。
- 防止在线深度包检测(DPI)识别协议
- 防止同一连接中一系列消息的模式, 除了握手重传
- 防止不同连接中相同类型消息的模式
- 防止在不知道 netDb 中介绍密钥的情况下 解密握手头部
- 防止在不知道 netDb 中介绍密钥的情况下 识别 X25519 临时密钥
- 防止任何在线或离线攻击者 解密数据阶段包编号和类型
- 防止路径上或路径外观察者在不知道 netDb 中介绍密钥的情况下 注入有效的握手数据包
- 防止路径上或路径外观察者注入有效的数据包
- 允许快速有效地分类传入数据包
- 提供"探测"抗性,使得对错误的会话请求没有响应, 或者如果有重试响应, 在不知道 netDb 中介绍密钥的情况下该响应无法被识别为 I2P
- 目标连接 ID 不是关键数据, 如果知道 netDb 中介绍密钥的观察者能够解密它是可以接受的
- 数据阶段包的包编号是 AEAD nonce,属于关键数据。 即使观察者知道 netDb 中的介绍密钥, 也不能被其解密。 参见 Nonces。
头部使用在网络数据库中发布或稍后计算的已知密钥进行加密。在握手阶段,这仅用于抵抗 DPI,因为密钥是公开的,并且密钥和随机数被重复使用,所以这实际上只是混淆。请注意,头部加密也用于混淆临时密钥 X(在 Session Request 中)和 Y(在 Session Created 中)。
请参阅下面的入站数据包处理部分以获取更多指导。
所有报文头的前 0-15 字节都使用报文头保护方案进行加密,通过与使用已知密钥计算出的数据进行异或运算,使用 ChaCha20,类似于 QUIC RFC 9001 和 Nonces。这确保了加密的短报文头和长报文头的第一部分看起来是随机的。
对于 Session Request 和 Session Created,长头部的第 16-31 字节和 32 字节的 Noise 临时密钥使用 ChaCha20 进行加密。未加密的数据是随机的,因此加密后的数据看起来也是随机的。
对于重试,长头部的字节16-31使用ChaCha20加密。未加密的数据是随机的,因此加密后的数据看起来也是随机的。
与 QUIC RFC 9001 头部保护方案不同,所有头部的所有部分,包括目标和源连接 ID,都是加密的。QUIC RFC 9001 和 Nonces 主要专注于加密头部的"关键"部分,即数据包编号(ChaCha20 nonce)。虽然加密会话 ID 使传入数据包分类变得稍微复杂一些,但它使某些攻击更加困难。QUIC 为不同阶段以及路径质询和连接迁移定义了不同的连接 ID。在这里我们始终使用相同的连接 ID,因为它们是加密的。
有七个头部保护密钥阶段:
- Session Request 和 Token Request
- Session Created
- 重试
- Session Confirmed
- 数据阶段
- Peer Test
- Hole Punch
| Message | Key k_header_1 | Key k_header_2 |
|---|---|---|
| Token Request | Bob Intro Key | Bob Intro Key |
| Session Request | Bob Intro Key | Bob Intro Key |
| Session Created | Bob Intro Key | See Session Request K |
| Session Confirmed | Bob Intro Key | See Session Created K |
| Retry | Bob Intro Key | Bob Intro Key |
| Data | Alice/Bob Intro Key | See data phase KDF |
| Peer Test 5,7 | Alice Intro Key | Alice Intro Key |
| Peer Test 6 | Charlie Intro Key | Charlie Intro Key |
| Hole Punch | Alice Intro Key | Alice Intro Key |
| Header encryption 的设计目的是允许对入站数据包进行快速分类,而无需复杂的启发式方法或回退机制。这是通过对几乎所有入站消息使用相同的 k_header_1 密钥来实现的。即使当连接的源 IP 或端口由于实际 IP 变化或 NAT 行为而发生改变时,数据包也可以通过对连接 ID 的单次查找快速映射到会话。 |
请注意,Session Created 和 Retry 是唯一需要对 k_header_1 进行回退处理以解密 Connection ID 的消息,因为它们使用发送方(Bob)的介绍密钥。所有其他消息都使用接收方的介绍密钥作为 k_header_1。回退处理只需要通过源 IP/端口查找待处理的出站连接。
如果按源IP/端口进行的后备处理未能找到待处理的出站连接,可能有以下几种原因:
- 不是 SSU2 消息
- 损坏的 SSU2 消息
- 回复被攻击者伪造或修改
- Bob 具有对称 NAT
- Bob 在处理消息期间更改了 IP 或端口
- Bob 通过不同接口发送回复
虽然可以通过额外的回退处理来尝试查找待处理的出站连接,并使用该连接的 k_header_1 来解密连接 ID,但这可能并非必要。如果 Bob 的 NAT 或数据包路由存在问题,最好让连接失败。此设计依赖于端点在握手期间保持稳定的地址。
请参阅下面的入站数据包处理部分以获取更多指导原则。
请参阅下面各个 KDF 部分,了解该阶段 header 加密密钥的派生过程。
无状态重置预言机
// incoming encrypted packet
packet = incoming encrypted packet
len = packet.length
// take the next-to-last 12 bytes of the packet
iv = packet[len-24:len-13]
k_header_1 = header encryption key 1
data = {0, 0, 0, 0, 0, 0, 0, 0}
mask = ChaCha20.encrypt(k_header_1, iv, data)
// encrypt the first part of the header by XORing with the mask
packet[0:7] ^= mask[0:7]
// take the last 12 bytes of the packet
iv = packet[len-12:len-1]
k_header_2 = header encryption key 2
data = {0, 0, 0, 0, 0, 0, 0, 0}
mask = ChaCha20.encrypt(k_header_2, iv, data)
// encrypt the second part of the header by XORing with the mask
packet[8:15] ^= mask[0:7]
// For Session Request and Session Created only:
iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
// encrypt the third part of the header and the ephemeral key
packet[16:63] = ChaCha20.encrypt(k_header_2, iv, packet[16:63])
// For Retry, Token Request, Peer Test, and Hole Punch only:
iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
// encrypt the third part of the header
packet[16:31] = ChaCha20.encrypt(k_header_2, iv, packet[16:31])
此 KDF 使用数据包的最后 24 字节作为两个 ChaCha20 操作的 IV。由于所有数据包都以 16 字节 MAC 结尾,这要求所有数据包载荷最少为 8 字节。此要求在下面的消息章节中也有额外说明。
版本降级
解密头部前8个字节后,接收方将知道目标连接ID。从那里开始,接收方根据会话的密钥阶段知道对头部剩余部分使用什么头部加密密钥。
解密头部的接下来8个字节将会显示消息类型,并能够确定这是短头部还是长头部。如果是长头部,接收方必须验证version和netid字段。如果version != 2,或者netid != 预期值(通常为2,测试网络除外),接收方应该丢弃该消息。
Packet Integrity
所有消息都包含三个或四个部分:
- 消息头部
- 仅适用于会话请求和会话创建,一个临时密钥
- ChaCha20 加密的载荷
- Poly1305 MAC
在所有情况下,header(以及如果存在的话,ephemeral key)都绑定到认证MAC,以确保整个消息的完整性。
- 对于握手消息 Session Request、Session Created 和 Session Confirmed, 消息头在 Noise 处理阶段之前进行 mixHash() 操作
- 临时密钥(如果存在)由标准 Noise misHash() 覆盖
- 对于 Noise 握手之外的消息,消息头用作 ChaCha20/Poly1305 加密的关联数据
入站数据包处理程序必须始终解密 ChaCha20 载荷并在处理消息前验证 MAC,但有一个例外:为了减轻来自包含无效令牌的伪造 Session Request 消息的地址欺骗数据包的 DoS 攻击,处理程序无需尝试解密和验证完整消息(除了 ChaCha20/Poly1305 解密外还需要昂贵的 DH 操作)。处理程序可以使用在 Session Request 消息头部中找到的值回复一个 Retry 消息。
Authenticated Encryption
有三个独立的认证加密实例(CipherStates)。一个在握手阶段使用,两个(发送和接收)在数据阶段使用。每个都有来自KDF的自己的密钥。
加密/认证的数据将表示为
+----+----+----+----+----+----+----+----+
| |
+ +
| Encrypted and authenticated data |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
通过路由的定向攻击
加密和认证的数据格式。
加密/解密函数的输入:
k :: 32 byte cipher key, as generated from KDF
nonce :: Counter-based nonce, 12 bytes.
Starts at 0 and incremented for each message.
First four bytes are always zero.
Last eight bytes are the counter, little-endian encoded.
Maximum value is 2**64 - 2.
Connection must be dropped and restarted after
it reaches that value.
The value 2**64 - 1 must never be sent.
ad :: In handshake phase:
Associated data, 32 bytes.
The SHA256 hash of all preceding data.
In data phase:
The packet header, 16 bytes.
data :: Plaintext data, 0 or more bytes
加密函数的输出,解密函数的输入:
+----+----+----+----+----+----+----+----+
| |
+ +
| ChaCha20 encrypted data |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
| Poly1305 Message Authentication Code |
+ (MAC) +
| 16 bytes |
+----+----+----+----+----+----+----+----+
encrypted data :: Same size as plaintext data, 0 - 65519 bytes
MAC :: Poly1305 message authentication code, 16 bytes
对于 ChaCha20,这里描述的内容对应 RFC 7539,这也在 TLS RFC 7905 中类似使用。
流量分析
由于 ChaCha20 是流密码,明文无需填充。 额外的密钥流字节将被丢弃。
密码的密钥(256位)通过SHA256 KDF协商确定。 每个消息的KDF详细信息在下面的单独章节中说明。
AEAD Error Handling
在所有消息中,AEAD 消息大小是预先已知的。 当 AEAD 认证失败时,接收方必须停止进一步的消息处理并 丢弃该消息。
Bob应该维护一个IP黑名单,记录 反复失败的IP地址。
KDF for Session Request
密钥派生函数 (KDF) 使用 HMAC-SHA256(key, data)(如 RFC 2104 中定义)从 DH 结果生成握手阶段密码密钥 k。这些是 InitializeSymmetric()、MixHash() 和 MixKey() 函数,与 Noise 规范中定义的完全相同。
KDF for Initial ChainKey
// Define protocol_name.
Set protocol_name = "Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256"
(52 bytes, US-ASCII encoded, no NULL termination).
// Define Hash h = 32 bytes
h = SHA256(protocol_name);
Define ck = 32 byte chaining key. Copy the h data to ck.
Set ck = h
// MixHash(null prologue)
h = SHA256(h);
// up until here, can all be precalculated by Alice for all outgoing connections
// Bob's X25519 static keys
// bpk is published in routerinfo
bsk = GENERATE_PRIVATE()
bpk = DERIVE_PUBLIC(bsk)
// Bob static key
// MixHash(bpk)
// || below means append
h = SHA256(h || bpk);
// Bob introduction key
// bik is published in routerinfo
bik = RANDOM(32)
// up until here, can all be precalculated by Bob for all incoming connections
KDF for Session Request
// MixHash(header)
h = SHA256(h || header)
This is the "e" message pattern:
// Alice's X25519 ephemeral keys
aesk = GENERATE_PRIVATE()
aepk = DERIVE_PUBLIC(aesk)
// Alice ephemeral key X
// MixHash(aepk)
h = SHA256(h || aepk);
// h is used as the associated data for the AEAD in Session Request
// Retain the Hash h for the Session Created KDF
End of "e" message pattern.
This is the "es" message pattern:
// DH(e, rs) == DH(s, re)
sharedSecret = DH(aesk, bpk) = DH(bsk, aepk)
// MixKey(DH())
//[chainKey, k] = MixKey(sharedSecret)
// ChaChaPoly parameters to encrypt/decrypt
keydata = HKDF(chainKey, sharedSecret, "", 64)
chainKey = keydata[0:31]
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
ciphertext = ENCRYPT(k, n, payload, ad)
// retain the chainKey for Session Created KDF
End of "es" message pattern.
// Header encryption keys for this message
// bik = Bob's intro key
k_header_1 = bik
k_header_2 = bik
// Header encryption keys for next message (Session Created)
k_header_1 = bik
k_header_2 = HKDF(chainKey, ZEROLEN, "SessCreateHeader", 32)
// Header encryption keys for next message (Retry)
k_header_1 = bik
k_header_2 = bik
SessionRequest (Type 0)
Alice 发送给 Bob,要么作为握手中的第一条消息,要么响应 Retry 消息。Bob 用 Session Created 消息响应。大小:80 + payload 大小。最小大小:88
如果 Alice 没有有效的令牌,Alice 应该发送 Token Request 消息而不是 Session Request,以避免生成 Session Request 时的非对称加密开销。
长标头。Noise 内容:Alice 的临时密钥 X Noise 载荷:DateTime 和其他块 最大载荷大小:MTU - 108 (IPv4) 或 MTU - 128 (IPv6)。对于 1280 MTU:最大载荷为 1172 (IPv4) 或 1152 (IPv6)。对于 1500 MTU:最大载荷为 1392 (IPv4) 或 1372 (IPv6)。
载荷安全属性:
XK(s, rs): Authentication Confidentiality
-> e, es 0 2
Authentication: None (0).
This payload may have been sent by any party, including an active attacker.
Confidentiality: 2.
Encryption to a known recipient, forward secrecy for sender compromise
only, vulnerable to replay. This payload is encrypted based only on DHs
involving the recipient's static key pair. If the recipient's static
private key is compromised, even at a later date, this payload can be
decrypted. This message can also be replayed, since there's no ephemeral
contribution from the recipient.
"e": Alice generates a new ephemeral key pair and stores it in the e
variable, writes the ephemeral public key as cleartext into the
message buffer, and hashes the public key along with the old h to
derive a new h.
"es": A DH is performed between the Alice's ephemeral key pair and the
Bob's static key pair. The result is hashed along with the old ck to
derive a new ck and k, and n is set to zero.
X 值经过加密以确保载荷的不可区分性和唯一性,这些是必要的 DPI 对策。我们使用 ChaCha20 加密来实现这一点,而不是使用更复杂和更慢的替代方案,如 elligator2。对 Bob 的 router 公钥进行非对称加密会过于缓慢。ChaCha20 加密使用在网络数据库中发布的 Bob 的 intro key。
ChaCha20 加密仅用于抵抗 DPI(深度包检测)。任何知道 Bob 的介绍密钥的一方,该密钥发布在 netDb 中,都可以解密此消息中的头部和 X 值。
原始内容:
+----+----+----+----+----+----+----+----+
| Long Header bytes 0-15, ChaCha20 |
+ encrypted with Bob intro key +
| See Header Encryption KDF |
+----+----+----+----+----+----+----+----+
| Long Header bytes 16-31, ChaCha20 |
+ encrypted with Bob intro key n=0 +
| |
+----+----+----+----+----+----+----+----+
| |
+ X, ChaCha20 encrypted +
| with Bob intro key n=0 |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| |
+ +
| ChaCha20 encrypted data |
+ (length varies) +
| k defined in KDF for Session Request |
+ n = 0 +
| see KDF for associated data |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
X :: 32 bytes, ChaCha20 encrypted X25519 ephemeral key, little endian
key: Bob's intro key
n: 1
data: 48 bytes (bytes 16-31 of the header, followed by encrypted X)
未加密数据(未显示 Poly1305 认证标签):
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type| ver| id |flag|
+----+----+----+----+----+----+----+----+
| Source Connection ID |
+----+----+----+----+----+----+----+----+
| Token |
+----+----+----+----+----+----+----+----+
| |
+ +
| X |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| Noise payload (block data) |
+ (length varies) +
| see below for allowed blocks |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: Randomly generated by Alice
id :: 1 byte, the network ID (currently 2, except for test networks)
ver :: 2
type :: 0
flag :: 1 byte, unused, set to 0 for future compatibility
Packet Number :: Random 4 byte number generated by Alice, ignored
Source Connection ID :: Randomly generated by Alice,
must not be equal to Destination Connection ID
Token :: 0 if not previously received from Bob
X :: 32 bytes, X25519 ephemeral key, little endian
Payload
- DateTime 块
- Options 块(可选)
- Relay Tag Request 块(可选)
- Padding 块(可选)
最小载荷大小为 8 字节。由于 DateTime 块只有 7 字节,因此必须至少存在一个其他块。
Notes
初始 ChaCha20 块中的唯一 X 值确保每个会话的密文都不相同。
为了提供探测抗性,Bob不应该发送重试消息来响应会话请求消息,除非会话请求消息中的消息类型、协议版本和网络ID字段都是有效的。
Bob必须拒绝时间戳值与当前时间相差过大的连接。将最大时间差称为"D”。Bob必须维护一个本地缓存,存储之前使用过的握手值并拒绝重复值,以防止重放攻击。缓存中的值必须至少具有2*D的生存时间。缓存值取决于具体实现,但可以使用32字节的X值(或其加密等价值)。 通过发送包含零token和终止块的Retry消息来拒绝连接。
Diffie-Hellman 临时密钥绝不能重复使用,以防止密码学攻击, 重复使用将被视为重放攻击而拒绝。
“KE” 和 “auth” 选项必须兼容,即共享密钥 K 必须具有适当的大小。如果添加更多 “auth” 选项,这可能会隐式改变 “KE” 标志的含义,使其使用不同的 KDF 或不同的截断大小。
Bob 必须验证 Alice 的临时密钥在此处是曲线上的有效点。
填充应限制在合理的数量内。Bob可能会拒绝具有过多填充的连接。 Bob将在Session Created中指定他的填充选项。 最小/最大准则待定。从0到31字节的随机大小最小值? (分布待确定,见附录A。) TODO 除非为PMTU强制执行最小数据包大小。
在大多数错误情况下,包括 AEAD、DH、明显重放或密钥验证失败,Bob 应该停止进一步的消息处理并丢弃消息而不响应。
Bob 可以发送包含零令牌和带有时钟偏移原因代码的终止块的重试消息,如果 DateTime 块中的时间戳偏移过大。
DoS 缓解:DH 是一个相对昂贵的操作。与之前的 NTCP 协议一样, 路由器应采取所有必要措施防止 CPU 或连接耗尽。 对最大活跃连接数和正在进行的最大连接建立数设置限制。 强制执行读取超时(包括单次读取和"慢速攻击"的总时间)。 限制来自同一源的重复或同时连接。 维护重复失败源的黑名单。 不要响应 AEAD 失败。或者,在 DH 操作和 AEAD 验证之前 使用重试消息响应。
“ver” 字段:整体 Noise 协议、扩展和 SSU2 协议 包括载荷规范,表示 SSU2。 此字段可用于表示对未来更改的支持。
network ID 字段用于快速识别跨网络连接。 如果此字段与 Bob 的 network ID 不匹配, Bob 应断开连接并阻止未来的连接。
如果源连接ID等于目标连接ID,Bob必须丢弃该消息。
KDF for Session Created and Session Confirmed part 1
// take h saved from Session Request KDF
// MixHash(ciphertext)
h = SHA256(h || encrypted Noise payload from Session Request)
// MixHash(header)
h = SHA256(h || header)
This is the "e" message pattern:
// Bob's X25519 ephemeral keys
besk = GENERATE_PRIVATE()
bepk = DERIVE_PUBLIC(besk)
// h is from KDF for Session Request
// Bob ephemeral key Y
// MixHash(bepk)
h = SHA256(h || bepk);
// h is used as the associated data for the AEAD in Session Created
// Retain the Hash h for the Session Confirmed KDF
End of "e" message pattern.
This is the "ee" message pattern:
// MixKey(DH())
//[chainKey, k] = MixKey(sharedSecret)
sharedSecret = DH(aesk, bepk) = DH(besk, aepk)
keydata = HKDF(chainKey, sharedSecret, "", 64)
chainKey = keydata[0:31]
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
ciphertext = ENCRYPT(k, n, payload, ad)
// retain the chaining key ck for Session Confirmed KDF
End of "ee" message pattern.
// Header encryption keys for this message
// bik = Bob's intro key
k_header_1 = bik
k_header_2: See Session Request KDF above
// Header protection keys for next message (Session Confirmed)
k_header_1 = bik
k_header_2 = HKDF(chainKey, ZEROLEN, "SessionConfirmed", 32)
连接迁移
Bob 向 Alice 发送,作为对 Session Request 消息的响应。Alice 用 Session Confirmed 消息回应。大小:80 + 载荷大小。最小大小:88
Noise 内容:Bob 的临时密钥 Y Noise 有效载荷:DateTime、Address 和其他块 最大有效载荷大小:MTU - 108 (IPv4) 或 MTU - 128 (IPv6)。对于 1280 MTU:最大有效载荷为 1172 (IPv4) 或 1152 (IPv6)。对于 1500 MTU:最大有效载荷为 1392 (IPv4) 或 1372 (IPv6)。
载荷安全属性:
XK(s, rs): Authentication Confidentiality
<- e, ee 2 1
Authentication: 2.
Sender authentication resistant to key-compromise impersonation (KCI).
The sender authentication is based on an ephemeral-static DH ("es" or "se")
between the sender's static key pair and the recipient's ephemeral key pair.
Assuming the corresponding private keys are secure, this authentication cannot be forged.
Confidentiality: 1.
Encryption to an ephemeral recipient.
This payload has forward secrecy, since encryption involves an ephemeral-ephemeral DH ("ee").
However, the sender has not authenticated the recipient,
so this payload might be sent to any party, including an active attacker.
"e": Bob generates a new ephemeral key pair and stores it in the e variable,
writes the ephemeral public key as cleartext into the message buffer,
and hashes the public key along with the old h to derive a new h.
"ee": A DH is performed between the Bob's ephemeral key pair and the Alice's ephemeral key pair.
The result is hashed along with the old ck to derive a new ck and k, and n is set to zero.
Y 值被加密以确保载荷的不可区分性和唯一性,这些是必要的 DPI 对抗措施。我们使用 ChaCha20 加密来实现这一点,而不是使用更复杂和更慢的替代方案,如 elligator2。对 Alice 的 router 公钥进行非对称加密会太慢。ChaCha20 加密使用 Bob 的 intro key,如网络数据库中发布的那样。
ChaCha20加密仅用于抵抗DPI(深度包检测)。任何知道Bob的intro key(已发布在netDb中)并捕获到Session Request前32字节的一方,都可以解密此消息中的Y值。
原始内容:
+----+----+----+----+----+----+----+----+
| Long Header bytes 0-15, ChaCha20 |
+ encrypted with Bob intro key and +
| derived key, see Header Encryption KDF|
+----+----+----+----+----+----+----+----+
| Long Header bytes 16-31, ChaCha20 |
+ encrypted with derived key n=0 +
| See Header Encryption KDF |
+----+----+----+----+----+----+----+----+
| |
+ Y, ChaCha20 encrypted +
| with derived key n=0 |
+ (32 bytes) +
| See Header Encryption KDF |
+ +
| |
+----+----+----+----+----+----+----+----+
| ChaCha20 data |
+ Encrypted and authenticated data +
| length varies |
+ k defined in KDF for Session Created +
| n = 0; see KDF for associated data |
+ +
| |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
Y :: 32 bytes, ChaCha20 encrypted X25519 ephemeral key, little endian
key: Bob's intro key
n: 1
data: 48 bytes (bytes 16-31 of the header, followed by encrypted Y)
未加密数据(未显示 Poly1305 认证标签):
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type| ver| id |flag|
+----+----+----+----+----+----+----+----+
| Source Connection ID |
+----+----+----+----+----+----+----+----+
| Token |
+----+----+----+----+----+----+----+----+
| |
+ +
| Y |
+ (32 bytes) +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| Noise payload (block data) |
+ (length varies) +
| see below for allowed blocks |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: The Source Connection ID
received from Alice in Session Request
id :: 1 byte, the network ID (currently 2, except for test networks)
ver :: 2
type :: 0
flag :: 1 byte, unused, set to 0 for future compatibility
Packet Number :: Random 4 byte number generated by Bob, ignored
Source Connection ID :: The Destination Connection ID
received from Alice in Session Request
Token :: 0 (unused)
Y :: 32 bytes, X25519 ephemeral key, little endian
Payload
- DateTime 块
- Address 块
- Relay Tag 块(可选)
- New Token 块(可选)
- First Packet Number 块(可选)
- Options 块(可选)
- Termination 块(不推荐,建议在重试消息中发送)
- Padding 块(可选)
最小载荷大小为 8 字节。由于 DateTime 和 Address 块的总大小超过了这个要求,因此仅这两个块就能满足要求。
Notes
Alice 必须在此处验证 Bob 的临时密钥是曲线上的有效点。
填充应限制在合理的数量内。 Alice 可能会拒绝填充过多的连接。 Alice 将在 Session Confirmed 中指定她的填充选项。 最小/最大指导方针待定。从 0 到 31 字节的随机大小最小值? (分布待定,见附录 A。) TODO 除非为 PMTU 强制执行最小数据包大小。
在任何错误情况下,包括 AEAD、DH、时间戳、明显重放或密钥验证失败,Alice 必须停止进一步的消息处理并关闭连接而不响应。
Alice 必须拒绝时间戳值与当前时间相差过大的连接。将最大时间差称为"D"。Alice 必须维护一个先前使用过的握手值的本地缓存,并拒绝重复值,以防止重放攻击。缓存中的值必须具有至少 2*D 的生存期。缓存值依赖于具体实现,但可以使用 32 字节的 Y 值(或其加密等价物)。
如果源 IP 和端口与 Session Request 的目标 IP 和端口不匹配,Alice 必须丢弃该消息。
如果 Destination 和 Source Connection ID 与 Session Request 的 Source 和 Destination Connection ID 不匹配,Alice 必须丢弃该消息。
如果Alice在会话请求中要求,Bob会发送一个relay tag块。
Issues
- 在此包含最小/最大填充选项?
KDF for Session Confirmed part 1, using Session Created KDF
// take h saved from Session Created KDF
// MixHash(ciphertext)
h = SHA256(h || encrypted Noise payload from Session Created)
// MixHash(header)
h = SHA256(h || header)
// h is used as the associated data for the AEAD in Session Confirmed part 1, below
This is the "s" message pattern:
// Alice's X25519 static keys
ask = GENERATE_PRIVATE()
apk = DERIVE_PUBLIC(ask)
// AEAD parameters
// k is from Session Request
n = 1
ad = h
ciphertext = ENCRYPT(k, n++, apk, ad)
// MixHash(ciphertext)
h = SHA256(h || ciphertext);
// h is used as the associated data for the AEAD in Session Confirmed part 2
End of "s" message pattern.
// Header encryption keys for this message
See Session Confirmed part 2 below
KDF for Session Confirmed part 2
This is the "se" message pattern:
// DH(ask, bepk) == DH(besk, apk)
sharedSecret = DH(ask, bepk) = DH(besk, apk)
// MixKey(DH())
//[chainKey, k] = MixKey(sharedSecret)
keydata = HKDF(chainKey, sharedSecret, "", 64)
chainKey = keydata[0:31]
// AEAD parameters
k = keydata[32:63]
n = 0
ad = h
ciphertext = ENCRYPT(k, n, payload, ad)
// h from Session Confirmed part 1 is used as the associated data for the AEAD in Session Confirmed part 2
// MixHash(ciphertext)
h = SHA256(h || ciphertext);
// retain the chaining key ck for the data phase KDF
// retain the hash h for the data phase KDF
End of "se" message pattern.
// Header encryption keys for this message
// bik = Bob's intro key
k_header_1 = bik
k_header_2: See Session Created KDF above
// Header protection keys for data phase
See data phase KDF below
SessionConfirmed (Type 2)
Alice 发送给 Bob,作为对 Session Created 消息的响应。Bob 立即用包含 ACK 块的 Data 消息进行响应。大小:80 + 载荷大小。最小大小:约 500(最小 router info 块大小约为 420 字节)
Noise内容:Alice的静态密钥 Noise载荷部分1:无 Noise载荷部分2:Alice的RouterInfo和其他块 最大载荷大小:MTU - 108(IPv4)或 MTU - 128(IPv6)。对于1280 MTU:最大载荷为1172(IPv4)或1152(IPv6)。对于1500 MTU:最大载荷为1392(IPv4)或1372(IPv6)。
载荷安全属性:
XK(s, rs): Authentication Confidentiality
-> s, se 2 5
Authentication: 2.
Sender authentication resistant to key-compromise impersonation (KCI). The
sender authentication is based on an ephemeral-static DH ("es" or "se")
between the sender's static key pair and the recipient's ephemeral key
pair. Assuming the corresponding private keys are secure, this
authentication cannot be forged.
Confidentiality: 5.
Encryption to a known recipient, strong forward secrecy. This payload is
encrypted based on an ephemeral-ephemeral DH as well as an ephemeral-static
DH with the recipient's static key pair. Assuming the ephemeral private
keys are secure, and the recipient is not being actively impersonated by an
attacker that has stolen its static private key, this payload cannot be
decrypted.
"s": Alice writes her static public key from the s variable into the
message buffer, encrypting it, and hashes the output along with the old h
to derive a new h.
"se": A DH is performed between the Alice's static key pair and the Bob's
ephemeral key pair. The result is hashed along with the old ck to derive a
new ck and k, and n is set to zero.
这包含两个 ChaChaPoly 帧。第一个是 Alice 加密的静态公钥。第二个是 Noise 有效载荷:Alice 加密的 RouterInfo、可选选项和可选填充。它们使用不同的密钥,因为在两者之间调用了 MixKey() 函数。
原始内容:
+----+----+----+----+----+----+----+----+
| Short Header 16 bytes, ChaCha20 |
+ encrypted with Bob intro key and +
| derived key, see Header Encryption KDF|
+----+----+----+----+----+----+----+----+
| ChaCha20 frame (32 bytes) |
+ Encrypted and authenticated data +
+ Alice static key S +
| k defined in KDF for Session Created |
+ n = 1 +
| |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
| |
+ Length varies (remainder of packet) +
| |
+ ChaChaPoly frame +
| Encrypted and authenticated |
+ see below for allowed blocks +
| |
+ k defined in KDF for +
| Session Confirmed part 2 |
+ n = 0 +
| see KDF for associated data |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
S :: 32 bytes, ChaChaPoly encrypted Alice's X25519 static key, little endian
inside 48 byte ChaChaPoly frame
未加密数据(未显示 Poly1305 认证标签):
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type|frag| flags |
+----+----+----+----+----+----+----+----+
| |
+ +
| S |
+ Alice static key +
| (32 bytes) |
+ +
| |
+ +
+----+----+----+----+----+----+----+----+
| |
+ +
| Noise Payload |
+ (length varies) +
| see below for allowed blocks |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: As sent in Session Request,
or one received in Session Confirmed?
Packet Number :: 0 always, for all fragments, even if retransmitted
type :: 2
frag :: 1 byte fragment info:
bit order: 76543210 (bit 7 is MSB)
bits 7-4: fragment number 0-14, big endian
bits 3-0: total fragments 1-15, big endian
flags :: 2 bytes, unused, set to 0 for future compatibility
S :: 32 bytes, Alice's X25519 static key, little endian
Payload
- RouterInfo 块(必须是第一个块)
- Options 块(可选)
- New Token 块(可选)
- Relay Request 块(可选)
- Peer Test 块(可选)
- First Packet Number 块(可选)
- I2NP、First Fragment 或 Follow-on Fragment 块(可选,但可能没有空间)
- Padding 块(可选)
最小载荷大小为 8 字节。由于 RouterInfo 块的大小远超过这个要求,仅凭该块就能满足要求。
Notes
Bob必须执行常规的Router Info验证。 确保签名类型受支持,验证签名, 验证时间戳在范围内,以及任何其他必要的检查。 有关处理分片Router Info的注意事项,请参见下文。
Bob 必须验证在第一帧中收到的 Alice 的静态密钥与 Router Info 中的静态密钥匹配。Bob 必须首先在 Router Info 中搜索具有匹配版本 (v) 选项的 NTCP 或 SSU2 Router Address。 请参见下面的 Published Router Info 和 Unpublished Router Info 部分。 有关处理分片 Router Info 的注意事项,请参见下文。
如果 Bob 在他的 netDb 中有 Alice 的 RouterInfo 的旧版本,请验证 router info 中的静态密钥在两者中是否相同(如果存在),以及旧版本是否少于 XXX 天(见下面的密钥轮换时间)
Bob必须在此验证Alice的静态密钥是曲线上的有效点。
应包含选项,以指定填充参数。
在任何错误情况下,包括 AEAD、RI、DH、时间戳或密钥验证失败, Bob 必须停止进一步的消息处理并关闭连接,不进行响应。
Message 3 part 2 帧内容:此帧的格式与数据阶段帧的格式相同,除了帧的长度由 Alice 在 Session Request 中发送。数据阶段帧格式见下文。 该帧必须按以下顺序包含 1 到 4 个块:
- Alice 的 Router Info 块(必需)
- Options 块(可选)
- I2NP 块(可选)
- Padding 块(可选) 此帧绝不能包含任何其他块类型。 TODO:relay 和 peer test 怎么办?
建议使用消息 3 第 2 部分填充块。
根据 MTU 和 Router Info 大小,可能没有空间或只有少量空间可用于 I2NP 块。如果 Router Info 被分片,请不要包含 I2NP 块。 最简单的实现可能是永远不在 Session Confirmed 消息中包含 I2NP 块, 而是在后续的 Data 消息中发送所有 I2NP 块。 有关最大块大小,请参见下面的 Router Info 块部分。
Session Confirmed Fragmentation
Session Confirmed 消息必须包含来自 Alice 的完整签名 Router Info,以便 Bob 可以执行几个必需的检查:
- RI 中的静态密钥 “s” 与握手中的静态密钥匹配
- 必须提取并验证 RI 中的介绍密钥 “i”,以便在数据阶段使用
- RI 签名有效
不幸的是,Router Info 即使在 RI 块中经过 gzip 压缩,也可能超过 MTU。因此,Session Confirmed 可能会被分片到两个或更多数据包中。这是 SSU2 协议中唯一一种 AEAD 保护的载荷被分片到两个或更多数据包的情况。
每个数据包的头部构造如下:
- 所有头部都是短头部,具有相同的数据包编号 0
- 所有头部都包含一个 “frag” 字段,包含分片编号和分片总数
- 分片 0 的未加密头部是 “jumbo” 消息的关联数据 (AD)
- 每个头部使用该数据包中最后 24 字节的数据进行加密
按如下方式构造数据包序列:
- 创建单个 RI block(RI block frag 字段中第 1 个分片的第 0 个)。 我们不使用 RI block 分片,那是用于解决同一问题的替代方法。
- 使用 RI block 和其他要包含的 block 创建"jumbo"负载
- 计算总数据大小(不包括头部), 即负载大小 + 64 字节的静态密钥和两个 MAC
- 计算每个数据包的可用空间,即 MTU 减去 IP 头部(20 或 40),减去 UDP 头部(8), 减去 SSU2 短头部(16)。每个数据包的总开销是 44(IPv4)或 64(IPv6)。
- 计算数据包数量。
- 计算最后一个数据包中的数据大小。它必须大于等于 24 字节,这样头部加密才能工作。 如果太小,要么添加填充 block,要么如果已存在则增加 填充 block 的大小,要么减少其他数据包之一的大小, 使最后一个数据包足够大。
- 为第一个数据包创建未加密头部,在 frag 字段中包含分片总数, 并使用 Noise 加密"jumbo"负载,像往常一样使用头部作为 AD。
- 将加密的 jumbo 数据包分割成分片
- 为每个分片 1-n 添加未加密头部
- 为每个分片 0-n 加密头部。每个头部使用与上述 Session Confirmed KDF 中 定义的相同 k_header_1 和 k_header_2。
- 传输所有分片
重组过程:
当 Bob 收到任何 Session Confirmed 消息时,他解密消息头,检查 frag 字段,并确定该 Session Confirmed 是分片的。在收到并重组所有分片之前,他不会(也无法)解密该消息。
- 保留片段 0 的头部,因为它用作 Noise AD
- 在重组之前丢弃其他片段的头部
- 重组"jumbo"载荷,使用片段 0 的头部作为 AD, 并使用 Noise 解密
- 像往常一样验证 RI 块
- 进入数据阶段并发送 ACK 0,像往常一样
Bob没有机制来确认单个片段。当Bob接收到所有片段、重组、解密并验证内容后,Bob像往常一样执行split(),进入数据阶段,并发送数据包编号0的ACK。
如果 Alice 没有收到数据包编号 0 的 ACK,她必须按原样重传所有会话确认数据包。
示例:
对于IPv6上1500字节的MTU,最大载荷为1372字节,RI块开销为5字节,最大(gzip压缩)RI数据大小为1367字节(假设没有其他块)。使用两个数据包时,第二个数据包的开销为64字节,因此可以容纳另外1436字节的载荷。所以两个数据包足以容纳最大2803字节的压缩RI。
在当前网络中看到的最大压缩 RI 约为 1400 字节;因此,在实践中,即使在最小 1280 MTU 的情况下,两个片段应该就足够了。该协议最多允许 15 个片段。
安全分析:
分段 Session Confirmed 的完整性和安全性与未分段的相同。对任何分段的任何更改都会导致重新组装后 Noise AEAD 失败。分段 0 之后的分段头部仅用于识别分段。即使路径上的攻击者拥有用于加密头部的 k_header_2 密钥(不太可能,从握手中派生),这也不允许攻击者替换有效的分段。
KDF for data phase
数据阶段使用报头作为关联数据。
KDF 从链接密钥 ck 生成两个密码密钥 k_ab 和 k_ba,使用 RFC 2104 中定义的 HMAC-SHA256(key, data)。这是 split() 函数,与 Noise 规范中定义的完全一致。
// split()
// chainKey = from handshake phase
keydata = HKDF(chainKey, ZEROLEN, "", 64)
k_ab = keydata[0:31]
k_ba = keydata[32:63]
// key is k_ab for Alice to Bob
// key is k_ba for Bob to Alice
keydata = HKDF(key, ZEROLEN, "HKDFSSU2DataKeys", 64)
k_data = keydata[0:31]
k_header_2 = keydata[32:63]
// AEAD parameters
k = k_data
n = 4 byte packet number from header
ad = 16 byte header, before header encryption
ciphertext = ENCRYPT(k, n, payload, ad)
// Header encryption keys for data phase
// aik = Alice's intro key
// bik = Bob's intro key
k_header_1 = Receiver's intro key (aik or bik)
k_header_2: from above
Data Message (Type 6)
Noise 负载:允许所有块类型 最大负载大小:MTU - 60(IPv4)或 MTU - 80(IPv6)。对于 1500 MTU:最大负载为 1440(IPv4)或 1420(IPv6)。
从 Session Confirmed 的第二部分开始,所有消息都在经过身份验证和加密的 ChaChaPoly 载荷内。所有填充都在消息内部。载荷内部采用标准格式,包含零个或多个"块"。每个块都有一个字节的类型和两个字节的长度。类型包括日期/时间、I2NP 消息、选项、终止和填充。
注意:Bob 可以但不是必须在数据阶段向 Alice 发送第一条消息时将他的 RouterInfo 发送给 Alice。
载荷安全属性:
XK(s, rs): Authentication Confidentiality
<- 2 5
-> 2 5
Authentication: 2.
Sender authentication resistant to key-compromise impersonation (KCI).
The sender authentication is based on an ephemeral-static DH ("es" or "se")
between the sender's static key pair and the recipient's ephemeral key pair.
Assuming the corresponding private keys are secure, this authentication cannot be forged.
Confidentiality: 5.
Encryption to a known recipient, strong forward secrecy.
This payload is encrypted based on an ephemeral-ephemeral DH as well as
an ephemeral-static DH with the recipient's static key pair.
Assuming the ephemeral private keys are secure, and the recipient is not being actively impersonated
by an attacker that has stolen its static private key, this payload cannot be decrypted.
Notes
- 路由器必须丢弃带有 AEAD 错误的消息。
+----+----+----+----+----+----+----+----+
| Short Header 16 bytes, ChaCha20 |
+ encrypted with intro key and +
| derived key, see Data Phase KDF |
+----+----+----+----+----+----+----+----+
| ChaCha20 data |
+ Encrypted and authenticated data +
| length varies |
+ k defined in Data Phase KDF +
| n = packet number from header |
+ +
| |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
未加密数据(未显示 Poly1305 认证标签):
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type| flags |
+----+----+----+----+----+----+----+----+
| Noise payload (block data) |
+ (length varies) +
| |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: As specified in session setup
Packet Number :: 4 byte big endian integer
type :: 6
flags :: 3 bytes, unused, set to 0 for future compatibility
Notes
最小载荷大小为 8 字节。任何 ACK、I2NP、First Fragment 或 Follow-on Fragment 块都将满足此要求。 如果不满足此要求,则必须包含 Padding 块。
每个数据包编号只能使用一次。 当重传 I2NP 消息或片段时, 必须使用新的数据包编号。
KDF for Peer Test
// AEAD parameters
// bik = Bob's intro key
k = bik
n = 4 byte packet number from header
ad = 32 byte header, before header encryption
ciphertext = ENCRYPT(k, n, payload, ad)
// Header encryption keys for this message
k_header_1 = bik
k_header_2 = bik
Peer Test (Type 7)
Charlie 发送给 Alice,Alice 发送给 Charlie,仅适用于 Peer Test 阶段 5-7。Peer Test 阶段 1-4 必须在会话内使用 Data 消息中的 Peer Test 块发送。更多信息请参见下面的 Peer Test 块和 Peer Test 过程部分。
大小:48 + payload 大小。
Noise 载荷:见下文。
原始内容:
+----+----+----+----+----+----+----+----+
| Long Header bytes 0-15, ChaCha20 |
+ encrypted with Alice or Charlie +
| intro key |
+----+----+----+----+----+----+----+----+
| Long Header bytes 16-31, ChaCha20 |
+ encrypted with Alice or Charlie +
| intro key |
+----+----+----+----+----+----+----+----+
| |
+ +
| ChaCha20 encrypted data |
+ (length varies) +
| |
+ see KDF for key and n +
| see KDF for associated data |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
未加密数据(未显示 Poly1305 身份验证标签):
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type| ver| id |flag|
+----+----+----+----+----+----+----+----+
| Source Connection ID |
+----+----+----+----+----+----+----+----+
| Token |
+----+----+----+----+----+----+----+----+
| ChaCha20 payload (block data) |
+ (length varies) +
| see below for allowed blocks |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: See below
type :: 7
ver :: 2
id :: 1 byte, the network ID (currently 2, except for test networks)
flag :: 1 byte, unused, set to 0 for future compatibility
Packet Number :: Random number generated by Alice or Charlie
Source Connection ID :: See below
Token :: Randomly generated by Alice or Charlie, ignored
长标题
- DateTime 块
- Address 块(消息 6 和 7 必需,见下方说明)
- Peer Test 块
- Padding 块(可选)
最小载荷大小为8字节。由于Peer Test块的总大小超过了这个要求,仅使用此块就能满足要求。
在消息5和7中,Peer Test块可能与会话内消息3和4中的块相同,包含由Charlie签名的协议,或者可能重新生成。签名是可选的。
在消息6中,Peer Test块可能与会话内消息1和2中的块相同,包含由Alice签名的请求,或者可能重新生成。签名是可选的。
连接ID:两个连接ID源自测试随机数。对于Charlie发送给Alice的消息5和7,目标连接ID是4字节大端序测试随机数的两个副本,即((nonce « 32) | nonce)。源连接ID是目标连接ID的反码,即~((nonce « 32) | nonce)。对于Alice发送给Charlie的消息6,交换这两个连接ID。
地址块内容:
- 在消息 5 中:不需要。
- 在消息 6 中:从 Charlie 的 RI 中选择的 Charlie 的 IP 和端口。
- 在消息 7 中:接收到消息 6 的 Alice 的实际 IP 和端口。
KDF for Retry
Retry消息的要求是Bob不需要解密Session Request消息就能生成Retry消息作为响应。同时,这个消息必须能够快速生成,仅使用对称加密。
// AEAD parameters
// bik = Bob's intro key
k = bik
n = 4 byte packet number from header
ad = 32 byte header, before header encryption
ciphertext = ENCRYPT(k, n, payload, ad)
// Header encryption keys for this message
k_header_1 = bik
k_header_2 = bik
Retry (Type 9)
Bob 发送给 Alice,作为对 Session Request 或 Token Request 消息的响应。Alice 使用新的 Session Request 进行回应。大小:48 + payload 大小。
如果包含终止块,也可作为终止消息(即"不要重试")。
Noise payload:见下文。
原始内容:
+----+----+----+----+----+----+----+----+
| Long Header bytes 0-15, ChaCha20 |
+ encrypted with Bob intro key +
| |
+----+----+----+----+----+----+----+----+
| Long Header bytes 16-31, ChaCha20 |
+ encrypted with Bob intro key +
| |
+----+----+----+----+----+----+----+----+
| |
+ +
| ChaCha20 encrypted data |
+ (length varies) +
| |
+ see KDF for key and n +
| see KDF for associated data |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
未加密数据(未显示 Poly1305 认证标签):
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type| ver| id |flag|
+----+----+----+----+----+----+----+----+
| Source Connection ID |
+----+----+----+----+----+----+----+----+
| Token |
+----+----+----+----+----+----+----+----+
| ChaCha20 payload (block data) |
+ (length varies) +
| see below for allowed blocks |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: The Source Connection ID
received from Alice in Token Request
or Session Request
Packet Number :: Random number generated by Bob
type :: 9
ver :: 2
id :: 1 byte, the network ID (currently 2, except for test networks)
flag :: 1 byte, unused, set to 0 for future compatibility
Source Connection ID :: The Destination Connection ID
received from Alice in Token Request
or Session Request
Token :: 8 byte unsigned integer, randomly generated by Bob, nonzero,
or zero if session is rejected and a termination block is included
短标头
- DateTime 块
- Address 块
- Options 块(可选)
- Termination 块(可选,如果会话被拒绝)
- Padding 块(可选)
最小负载大小为8字节。由于DateTime和Address块的总大小超过了这个要求,仅使用这两个块就能满足要求。
连接 ID 编号
为了提供探测抗性,路由器不应发送重试消息来响应会话请求或令牌请求消息,除非请求消息中的消息类型、协议版本和网络ID字段有效。
为了限制使用伪造源地址进行的任何放大攻击的规模, Retry 消息不得包含大量填充数据。 建议 Retry 消息的大小不超过其所回应消息大小的三倍。 或者,可以使用简单的方法,如添加 1-64 字节范围内的随机填充量。
KDF for Token Request
此消息必须快速生成,仅使用对称加密。
// AEAD parameters
// bik = Bob's intro key
k = bik
n = 4 byte packet number from header
ad = 32 byte header, before header encryption
ciphertext = ENCRYPT(k, n, payload, ad)
// Header encryption keys for this message
k_header_1 = bik
k_header_2 = bik
Token Request (Type 10)
Alice 发送给 Bob。Bob 用 Retry 消息回应。大小:48 + payload 大小。
如果 Alice 没有有效的令牌,Alice 应该发送此消息而不是会话请求,以避免生成会话请求时的非对称加密开销。
Noise payload:见下文。
原始内容:
+----+----+----+----+----+----+----+----+
| Long Header bytes 0-15, ChaCha20 |
+ encrypted with Bob intro key +
| |
+----+----+----+----+----+----+----+----+
| Long Header bytes 16-31, ChaCha20 |
+ encrypted with Bob intro key +
| |
+----+----+----+----+----+----+----+----+
| |
+ +
| ChaCha20 encrypted data |
+ (length varies) +
| |
+ see KDF for key and n +
| see KDF for associated data |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
未加密数据(未显示 Poly1305 认证标签):
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type| ver| id |flag|
+----+----+----+----+----+----+----+----+
| Source Connection ID |
+----+----+----+----+----+----+----+----+
| Token |
+----+----+----+----+----+----+----+----+
| ChaCha20 payload (block data) |
+ (length varies) +
| see below for allowed blocks |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: Randomly generated by Alice
Packet Number :: Random number generated by Alice
type :: 10
ver :: 2
id :: 1 byte, the network ID (currently 2, except for test networks)
flag :: 1 byte, unused, set to 0 for future compatibility
Source Connection ID :: Randomly generated by Alice,
must not be equal to Destination Connection ID
Token :: zero
数据包编号
- DateTime 块
- Padding 块
最小载荷大小为 8 字节。
头部绑定
为了提供探测抗性,router 不应发送 Retry 消息来响应 Token Request 消息,除非 Token Request 消息中的消息类型、协议版本和网络 ID 字段是有效的。
这不是标准的 Noise 消息,也不是握手的一部分。 除了通过连接 ID 之外,它与 Session Request 消息没有绑定关系。
在大多数错误情况下,包括 AEAD 或明显的重放攻击 Bob 应该停止进一步的消息处理并 丢弃消息而不响应。
Bob必须拒绝时间戳值与当前时间相差过大的连接。将最大时间差称为"D"。Bob必须维护一个本地缓存来存储之前使用过的握手值,并拒绝重复值,以防止重放攻击。缓存中的值必须具有至少2*D的生存期。缓存值依赖于具体实现,不过可以使用32字节的X值(或其加密等价物)。
如果 DateTime 块中的时间戳偏差过大,Bob 可以发送一个包含零 token 和带有时钟偏差原因代码的 Termination 块的 Retry 消息。
最小大小:待定,与 Session Created 相同的规则?
KDF for Hole Punch
此消息必须快速生成,仅使用对称加密。
// AEAD parameters
// aik = Alice's intro key
k = aik
n = 4 byte packet number from header
ad = 32 byte header, before header encryption
ciphertext = ENCRYPT(k, n, payload, ad)
// Header encryption keys for this message
k_header_1 = aik
k_header_2 = aik
Hole Punch (Type 11)
Charlie 发送给 Alice,作为对从 Bob 收到的 Relay Intro 的响应。Alice 用新的 Session Request 进行回应。大小:48 + 载荷大小。
Noise 载荷:见下文。
原始内容:
+----+----+----+----+----+----+----+----+
| Long Header bytes 0-15, ChaCha20 |
+ encrypted with Alice intro key +
| |
+----+----+----+----+----+----+----+----+
| Long Header bytes 16-31, ChaCha20 |
+ encrypted with Alice intro key +
| |
+----+----+----+----+----+----+----+----+
| |
+ +
| ChaCha20 encrypted data |
+ (length varies) +
| |
+ see KDF for key and n +
| see KDF for associated data |
+----+----+----+----+----+----+----+----+
| |
+ Poly1305 MAC (16 bytes) +
| |
+----+----+----+----+----+----+----+----+
未加密数据(未显示 Poly1305 认证标签):
+----+----+----+----+----+----+----+----+
| Destination Connection ID |
+----+----+----+----+----+----+----+----+
| Packet Number |type| ver| id |flag|
+----+----+----+----+----+----+----+----+
| Source Connection ID |
+----+----+----+----+----+----+----+----+
| Token |
+----+----+----+----+----+----+----+----+
| ChaCha20 payload (block data) |
+ (length varies) +
| see below for allowed blocks |
+----+----+----+----+----+----+----+----+
Destination Connection ID :: See below
Packet Number :: Random number generated by Charlie
type :: 11
ver :: 2
id :: 1 byte, the network ID (currently 2, except for test networks)
flag :: 1 byte, unused, set to 0 for future compatibility
Source Connection ID :: See below
Token :: 8 byte unsigned integer, randomly generated by Charlie, nonzero.
报头加密
- DateTime 块
- Address 块
- Relay Response 块
- Padding 块(可选)
最小载荷大小为8字节。由于DateTime和Address块的总大小超过了这个要求,仅这两个块就满足了要求。
连接 ID:两个连接 ID 都从中继随机数派生而来。目标连接 ID 是 4 字节大端序中继随机数的两个副本,即 ((nonce « 32) | nonce)。源连接 ID 是目标连接 ID 的反码,即 ~((nonce « 32) | nonce)。
Alice 应该忽略头部中的令牌。要在会话请求中使用的令牌位于中继响应块中。
Noise Payload
每个 Noise 负载包含零个或多个"块"。
这使用与 NTCP2 和 ECIES 规范中定义的相同块格式。各个块类型的定义不同。QUIC RFC 9000 中的等价术语是"帧"。
有人担心鼓励实现者共享代码可能会导致解析问题。实现者应该仔细考虑共享代码的好处和风险,并确保两种情况下的排序和有效块规则是不同的。
安全注意事项
加密载荷中有一个或多个块。块是简单的标签-长度-值 (TLV) 格式。每个块包含一个单字节标识符、一个双字节长度和零个或多个数据字节。此格式与 NTCP2 和 ECIES 中的格式相同,但块定义有所不同。
为了可扩展性,接收方必须忽略具有未知标识符的块,并将它们视为填充。
(未显示 Poly1305 认证标签):
+----+----+----+----+----+----+----+----+
|blk | size | data |
+----+----+----+ +
| |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
|blk | size | data |
+----+----+----+ +
| |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
~ . . . ~
blk :: 1 byte, see below
size :: 2 bytes, big endian, size of data to follow, 0 - TBD
data :: the data
头部加密使用数据包的最后24字节作为两个ChaCha20操作的IV。由于所有数据包都以16字节MAC结尾,这要求所有数据包载荷最少为8字节。如果载荷不能满足此要求,则必须包含一个填充块。
最大 ChaChaPoly 载荷根据消息类型、MTU 和 IPv4 或 IPv6 地址类型而变化。最大载荷为 IPv4 的 MTU - 60,IPv6 的 MTU - 80。最大载荷数据为 IPv4 的 MTU - 63,IPv6 的 MTU - 83。上限约为 IPv4 1440 字节,1500 MTU,数据消息。最大总块大小是最大载荷大小。最大单块大小是最大总块大小。块类型为 1 字节。块长度为 2 字节。最大单块数据大小是最大单块大小减去 3。
注意事项:
实现者必须确保在读取块时, 格式错误或恶意数据不会导致读取操作 越界到下一个块或超出有效载荷边界。
为了向前兼容性,实现应该忽略未知的块类型。
区块类型:
| Payload Block Type | Type Number | Block Length |
|---|---|---|
| DateTime | 0 | 7 |
| Options | 1 | 15+ |
| Router Info | 2 | varies |
| I2NP Message | 3 | varies |
| First Fragment | 4 | varies |
| Follow-on Fragment | 5 | varies |
| Termination | 6 | 9 typ. |
| Relay Request | 7 | varies |
| Relay Response | 8 | varies |
| Relay Intro | 9 | varies |
| Peer Test | 10 | varies |
| Next Nonce | 11 | TBD |
| ACK | 12 | varies |
| Address | 13 | 9 or 21 |
| reserved | 14 | – |
| Relay Tag Request | 15 | 3 |
| Relay Tag | 16 | 7 |
| New Token | 17 | 15 |
| Path Challenge | 18 | varies |
| Path Response | 19 | varies |
| First Packet Number | 20 | 7 |
| Congestion | 21 | 4 |
| reserved for experimental features | 224-253 | |
| Padding | 254 | varies |
| reserved for future extension | 255 |
Block Ordering Rules
在 Session Confirmed 中,Router Info 必须是第一个块。
在所有其他消息中,顺序未指定,除了以下要求:填充(Padding)如果存在,必须是最后一个块。终止(Termination)如果存在,除了填充(Padding)之外必须是最后一个块。单个载荷中不允许多个填充(Padding)块。
Block Specifications
报文头加密密钥派生函数
用于时间同步:
+----+----+----+----+----+----+----+
| 0 | 4 | timestamp |
+----+----+----+----+----+----+----+
blk :: 0
size :: 2 bytes, big endian, value = 4
timestamp :: Unix timestamp, unsigned seconds.
Wraps around in 2106
注意事项:
- 与 SSU 1 不同,SSU 2 数据阶段的数据包头部中没有时间戳。
- 实现应该在数据阶段定期发送 DateTime 块。
- 实现必须四舍五入到最接近的秒数,以防止网络中的时钟偏差。
头部验证
传递更新的选项。选项包括:最小和最大填充。
选项块的长度是可变的。
+----+----+----+----+----+----+----+----+
| 1 | size |tmin|tmax|rmin|rmax|tdmy|
+----+----+----+----+----+----+----+----+
|tdmy| rdmy | tdelay | rdelay | |
~----+----+----+----+----+----+----+ ~
| more_options |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 1
size :: 2 bytes, big endian, size of options to follow, 12 bytes minimum
tmin, tmax, rmin, rmax :: requested padding limits
tmin and rmin are for desired resistance to traffic analysis.
tmax and rmax are for bandwidth limits.
tmin and tmax are the transmit limits for the router sending this options block.
rmin and rmax are the receive limits for the router sending this options block.
Each is a 4.4 fixed-point float representing 0 to 15.9375
(or think of it as an unsigned 8-bit integer divided by 16.0).
This is the ratio of padding to data. Examples:
Value of 0x00 means no padding
Value of 0x01 means add 6 percent padding
Value of 0x10 means add 100 percent padding
Value of 0x80 means add 800 percent (8x) padding
Alice and Bob will negotiate the minimum and maximum in each direction.
These are guidelines, there is no enforcement.
Sender should honor receiver's maximum.
Sender may or may not honor receiver's minimum, within bandwidth constraints.
tdmy: Max dummy traffic willing to send, 2 bytes big endian, bytes/sec average
rdmy: Requested dummy traffic, 2 bytes big endian, bytes/sec average
tdelay: Max intra-message delay willing to insert, 2 bytes big endian, msec average
rdelay: Requested intra-message delay, 2 bytes big endian, msec average
Padding distribution specified as additional parameters?
Random delay specified as additional parameters?
more_options :: Format TBD
选项问题:
- 选项协商待定。
RouterInfo
将Alice的RouterInfo传递给Bob。仅用于Session Confirmed第2部分载荷。不应在数据阶段使用;请改用I2NP DatabaseStore消息。
最小尺寸:约420字节,除非router info中的router identity和signature可以压缩,但这种情况不太可能发生。
注意:Router Info 块永远不会被分片。frag 字段始终为 0/1。有关更多信息,请参见上面的 Session Confirmed 分片部分。
+----+----+----+----+----+----+----+----+
| 2 | size |flag|frag| |
+----+----+----+----+----+ +
| |
+ Router Info fragment +
| (Alice RI in Session Confirmed) |
+ (Alice, Bob, or third-party +
| RI in data phase) |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 2
size :: 2 bytes, big endian, 2 + fragment size
flag :: 1 byte flags
bit order: 76543210 (bit 7 is MSB)
bit 0: 0 for local store, 1 for flood request
bit 1: 0 for uncompressed, 1 for gzip compressed
bits 7-2: Unused, set to 0 for future compatibility
frag :: 1 byte fragment info:
bit order: 76543210 (bit 7 is MSB)
bits 7-4: fragment number, always 0
bits 3-0: total fragments, always 1, big endian
routerinfo :: Alice's or Bob's RouterInfo
注意事项:
Router Info 可选择使用 gzip 压缩, 由标志位 1 表示。 这与 NTCP2 不同,在 NTCP2 中它从不被压缩, 也与 DatabaseStore Message 不同,在 DatabaseStore Message 中它总是被压缩。 压缩是可选的,因为对于小型 Router Info 通常收益很少, 因为可压缩内容较少, 但对于包含多个可压缩 Router Address 的大型 Router Info 非常有益。 如果压缩能让 Router Info 适合放入单个 Session Confirmed 数据包而无需分片, 则建议使用压缩。
Session Confirmed 消息中第一个或唯一片段的最大大小: IPv4 为 MTU - 113,或 IPv6 为 MTU - 133。 假设默认 MTU 为 1500 字节,且消息中没有其他块, IPv4 为 1387,或 IPv6 为 1367。 97% 的当前 router info 在不使用 gzip 压缩时小于 1367。 99.9% 的当前 router info 在使用 gzip 压缩时小于 1367。 假设最小 MTU 为 1280 字节,且消息中没有其他块, IPv4 为 1167,或 IPv6 为 1147。 94% 的当前 router info 在不使用 gzip 压缩时小于 1147。 97% 的当前 router info 在使用 gzip 压缩时小于 1147。
frag 字节现在未使用,Router Info 块从不分片。 frag 字节必须设置为片段 0,总片段数 1。 有关更多信息,请参见上面的 Session Confirmed Fragmentation 部分。
除非 RouterInfo 中有已发布的 RouterAddresses,否则不得请求 flooding。接收路由器除非其中有已发布的 RouterAddresses,否则不得 flood 该 RouterInfo。
此协议不提供 RouterInfo 已被存储或泛洪的确认。 如果需要确认,且接收方是 floodfill, 发送方应发送带有回复令牌的标准 I2NP DatabaseStoreMessage。
I2NP Message
带有修改标头的完整 I2NP 消息。
这使用与 NTCP2 中相同的 9 字节作为 I2NP 头部(类型、消息 id、短期过期时间)。
+----+----+----+----+----+----+----+----+
| 3 | size |type| msg id |
+----+----+----+----+----+----+----+----+
| short exp | message |
+----+----+----+----+ +
| |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 3
size :: 2 bytes, big endian, size of type + msg id + exp + message to follow
I2NP message body size is (size - 9).
type :: 1 byte, I2NP msg type, see I2NP spec
msg id :: 4 bytes, big endian, I2NP message ID
short exp :: 4 bytes, big endian, I2NP message expiration, Unix timestamp, unsigned seconds.
Wraps around in 2106
message :: I2NP message body
注意事项:
这与 NTCP2 中使用的 9 字节 I2NP 头格式相同。
这与第一个分片块的格式完全相同, 但块类型表明这是一个完整的消息。
包括9字节I2NP头在内的最大大小对于IPv4是MTU - 63,对于IPv6是MTU - 83。
ChaCha20/Poly1305
带有修改后头部的I2NP消息的第一个分片(分片#0)。
这使用与 NTCP2 中相同的 9 字节 I2NP 头部(类型、消息 ID、短期到期时间)。
未指定片段总数。
+----+----+----+----+----+----+----+----+
| 4 | size |type| msg id |
+----+----+----+----+----+----+----+----+
| short exp | |
+----+----+----+----+ +
| partial message |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 4
size :: 2 bytes, big endian, size of data to follow
Fragment size is (size - 9).
type :: 1 byte, I2NP msg type, see I2NP spec
msg id :: 4 bytes, big endian, I2NP message ID
short exp :: 4 bytes, big endian, I2NP message expiration, Unix timestamp, unsigned seconds.
Wraps around in 2106
message :: Partial I2NP message body, bytes 0 - (size - 10)
注意事项:
这与 NTCP2 中使用的 9 字节 I2NP 头格式相同。
这与 I2NP Message 块的格式完全相同, 但块类型表明这是消息的第一个片段。
部分消息长度必须大于零。
与 SSU 1 中一样,建议首先发送最后一个片段, 这样接收方就能知道片段的总数,并可以高效地分配接收缓冲区。
包括9字节I2NP头在内的最大大小为IPv4的MTU - 63和IPv6的MTU - 83。
注意事项
I2NP 消息的附加片段(片段编号大于零)。
+----+----+----+----+----+----+----+----+
| 5 | size |frag| msg id |
+----+----+----+----+----+----+----+----+
| |
+ +
| partial message |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 5
size :: 2 bytes, big endian, size of data to follow
Fragment size is (size - 5).
frag :: Fragment info:
Bit order: 76543210 (bit 7 is MSB)
bits 7-1: fragment number 1 - 127 (0 not allowed)
bit 0: isLast (1 = true)
msg id :: 4 bytes, big endian, I2NP message ID
message :: Partial I2NP message body
注意事项:
部分消息长度必须大于零。
与 SSU 1 中一样,建议首先发送最后一个分片, 这样接收方就能知道分片的总数量,并可以 高效地分配接收缓冲区。
与 SSU 1 一样,最大片段编号是 127,但实际限制是 63 或更少。实现可能会将最大值限制为对于约 64 KB 的最大 I2NP 消息大小实际可行的值,这在 1280 最小 MTU 下约为 55 个片段。请参见下面的最大 I2NP 消息大小部分。
最大部分消息大小(不包括分片和消息ID)对于IPv4是MTU - 68,对于IPv6是MTU - 88。
AEAD 错误处理
断开连接。这必须是载荷中最后一个非填充块。
+----+----+----+----+----+----+----+----+
| 6 | size | valid data packets |
+----+----+----+----+----+----+----+----+
received | rsn| addl data |
+----+----+----+----+ +
~ . . . ~
+----+----+----+----+----+----+----+----+
blk :: 6
size :: 2 bytes, big endian, value = 9 or more
valid data packets received :: The number of valid packets received
(current receive nonce value)
0 if error occurs in handshake phase
8 bytes, big endian
rsn :: reason, 1 byte:
0: normal close or unspecified
1: termination received
2: idle timeout
3: router shutdown
4: data phase AEAD failure
5: incompatible options
6: incompatible signature type
7: clock skew
8: padding violation
9: AEAD framing error
10: payload format error
11: Session Request error
12: Session Created error
13: Session Confirmed error
14: Timeout
15: RI signature verification fail
16: s parameter missing, invalid, or mismatched in RouterInfo
17: banned
18: bad token
19: connection limits
20: incompatible version
21: wrong net ID
22: replaced by new session
addl data :: optional, 0 or more bytes, for future expansion, debugging,
or reason text.
Format unspecified and may vary based on reason code.
注意事项:
- 并非所有原因都会被实际使用,这取决于具体实现。 大多数失败通常会导致消息被丢弃,而不是连接终止。 请参阅上面握手消息部分中的说明。 列出的其他原因是为了保持一致性、日志记录、调试,或者如果策略发生变化。
- 建议在终止块中包含一个 ACK 块。
- 在数据阶段,对于除"收到终止"之外的任何原因, 对等节点应该响应一个原因为"收到终止"的终止块。
RelayRequest
在会话中通过数据消息发送,从 Alice 到 Bob。请参阅下面的中继处理部分。
+----+----+----+----+----+----+----+----+
| 7 | size |flag| nonce |
+----+----+----+----+----+----+----+----+
| relay tag | timestamp |
+----+----+----+----+----+----+----+----+
| ver| asz|AlicePort| Alice IP address |
+----+----+----+----+----+----+----+----+
| signature |
+ length varies +
| 64 bytes for Ed25519 |
~ ~
| . . . |
+----+----+----+----+----+----+----+----+
blk :: 7
size :: 2 bytes, big endian, size of data to follow
flag :: 1 byte flags, Unused, set to 0 for future compatibility
The data below here is covered
by the signature, and Bob forwards it unmodified.
nonce :: 4 bytes, randomly generated by Alice
relay tag :: 4 bytes, the itag from Charlie's RI
timestamp :: Unix timestamp, unsigned seconds.
Wraps around in 2106
ver :: 1 byte SSU version to be used for the introduction:
1: SSU 1
2: SSU 2
asz :: 1 byte endpoint (port + IP) size (6 or 18)
AlicePort :: 2 byte Alice's port number, big endian
Alice IP :: (asz - 2) byte representation of Alice's IP address,
network byte order
signature :: length varies, 64 bytes for Ed25519.
Signature of prologue, Bob's hash,
and signed data above, as signed by
Alice.
注意事项:
- IP地址始终包含在内(与SSU 1不同) 且可能与会话使用的IP不同。
签名:
Alice 对请求进行签名并将其包含在此数据块中;Bob 在 Relay Intro 数据块中将其转发给 Charlie。签名算法:使用 Alice 的 router 签名密钥对以下数据进行签名:
- prologue: 16 字节 “RelayRequestData”,不以 null 结尾(不包含在消息中)
- bhash: Bob 的 32 字节 router 哈希(不包含在消息中)
- chash: Charlie 的 32 字节 router 哈希(不包含在消息中)
- nonce: 4 字节随机数
- relay tag: 4 字节中继标签
- timestamp: 4 字节时间戳(秒)
- ver: 1 字节 SSU 版本
- asz: 1 字节端点(端口 + IP)大小(6 或 18)
- AlicePort: 2 字节 Alice 的端口号
- Alice IP: (asz - 2) 字节 Alice IP 地址
初始 ChainKey 的 KDF
在会话中通过数据消息发送,从 Charlie 到 Bob 或从 Bob 到 Alice,同时也在从 Charlie 到 Alice 的打洞消息中发送。请参见下面的中继流程部分。
+----+----+----+----+----+----+----+----+
| 8 | size |flag|code| nonce
+----+----+----+----+----+----+----+----+
| timestamp | ver| csz|Char
+----+----+----+----+----+----+----+----+
Port| Charlie IP addr | |
+----+----+----+----+----+ +
| signature |
+ length varies +
| 64 bytes for Ed25519 |
~ ~
| . . . |
+----+----+----+----+----+----+----+----+
| Token |
+----+----+----+----+----+----+----+----+
blk :: 8
size :: 2 bytes, 6
flag :: 1 byte flags, Unused, set to 0 for future compatibility
code :: 1 byte status code:
0: accept
1: rejected by Bob, reason unspecified
2: rejected by Bob, Charlie is banned
3: rejected by Bob, limit exceeded
4: rejected by Bob, signature failure
5: rejected by Bob, relay tag not found
6: rejected by Bob, Alice RI not found
7-63: other rejected by Bob codes TBD
64: rejected by Charlie, reason unspecified
65: rejected by Charlie, unsupported address
66: rejected by Charlie, limit exceeded
67: rejected by Charlie, signature failure
68: rejected by Charlie, Alice is already connected
69: rejected by Charlie, Alice is banned
70: rejected by Charlie, Alice is unknown
71-127: other rejected by Charlie codes TBD
128: reject, source and reason unspecified
129-255: other reject codes TBD
The data below is covered by the signature if the code is 0 (accept).
Bob forwards it unmodified.
nonce :: 4 bytes, as received from Bob or Alice
The data below is present only if the code is 0 (accept).
timestamp :: Unix timestamp, unsigned seconds.
Wraps around in 2106
ver :: 1 byte SSU version to be used for the introduction:
1: SSU 1
2: SSU 2
csz :: 1 byte endpoint (port + IP) size (0 or 6 or 18)
may be 0 for some rejection codes
CharliePort :: 2 byte Charlie's port number, big endian
not present if csz is 0
Charlie IP :: (csz - 2) byte representation of Charlie's IP address,
network byte order
not present if csz is 0
signature :: length varies, 64 bytes for Ed25519.
Signature of prologue, Bob's hash,
and signed data above, as signed by
Charlie.
Not present if rejected by Bob.
token :: Token generated by Charlie for Alice to use
in the Session Request.
Only present if code is 0 (accept)
注意事项:
该令牌必须立即被Alice在会话请求中使用。
签名:
如果 Charlie 同意(响应代码 0)或拒绝(响应代码 64 或更高),Charlie 对响应进行签名并将其包含在此块中;Bob 在 Relay Response 块中将其转发给 Alice。签名算法:使用 Charlie 的 router 签名密钥对以下数据进行签名:
- prologue: 16 字节 “RelayAgreementOK”,非空终止(不包含在消息中)
- bhash: Bob 的 32 字节路由器哈希(不包含在消息中)
- nonce: 4 字节随机数
- timestamp: 4 字节时间戳(秒)
- ver: 1 字节 SSU 版本
- csz: 1 字节端点(端口 + IP)大小(0 或 6 或 18)
- CharliePort: 2 字节 Charlie 的端口号(如果 csz 为 0 则不存在)
- Charlie IP: (csz - 2) 字节 Charlie IP 地址(如果 csz 为 0 则不存在)
如果 Bob 拒绝(响应代码 1-63),Bob 对响应进行签名并将其包含在此块中。签名算法:使用 Bob 的 router 签名密钥对以下数据进行签名:
- prologue:16 字节 “RelayAgreementOK”,非空终止符(不包含在消息中)
- bhash:Bob 的 32 字节 router 哈希值(不包含在消息中)
- nonce:4 字节随机数
- timestamp:4 字节时间戳(秒)
- ver:1 字节 SSU 版本
- csz:1 字节 = 0
会话请求的 KDF
在会话中通过数据消息发送,从 Bob 到 Charlie。请参阅下面的中继过程部分。
必须在前面有一个 RouterInfo 块,或包含 Alice 的 Router Info 的 I2NP DatabaseStore 消息块(或片段),可以在同一负载中(如果有空间),或在之前的消息中。
+----+----+----+----+----+----+----+----+
| 9 | size |flag| |
+----+----+----+----+ +
| |
+ +
| Alice Router Hash |
+ 32 bytes +
| |
+ +----+----+----+----+
| | nonce |
+----+----+----+----+----+----+----+----+
| relay tag | timestamp |
+----+----+----+----+----+----+----+----+
| ver| asz|AlicePort| Alice IP address |
+----+----+----+----+----+----+----+----+
| signature |
+ length varies +
| 64 bytes for Ed25519 |
~ ~
| . . . |
+----+----+----+----+----+----+----+----+
blk :: 9
size :: 2 bytes, big endian, size of data to follow
flag :: 1 byte flags, Unused, set to 0 for future compatibility
hash :: Alice's 32-byte router hash,
The data below here is covered
by the signature, as received from Alice in the Relay Request,
and Bob forwards it unmodified.
nonce :: 4 bytes, as received from Alice
relay tag :: 4 bytes, the itag from Charlie's RI
timestamp :: Unix timestamp, unsigned seconds.
Wraps around in 2106
ver :: 1 byte SSU version to be used for the introduction:
1: SSU 1
2: SSU 2
asz :: 1 byte endpoint (port + IP) size (6 or 18)
AlicePort :: 2 byte Alice's port number, big endian
Alice IP :: (asz - 2) byte representation of Alice's IP address,
network byte order
signature :: length varies, 64 bytes for Ed25519.
Signature of prologue, Bob's hash,
and signed data above, as signed by
Alice.
注意事项:
对于 IPv4,Alice 的 IP 地址总是 4 字节,因为 Alice 试图通过 IPv4 连接到 Charlie。 支持 IPv6,Alice 的 IP 地址可能是 16 字节。
对于IPv4,此消息必须通过已建立的IPv4连接发送, 因为只有这样Bob才能知道Charlie的IPv4地址以便在RelayResponse_中返回给Alice。 支持IPv6,此消息可以通过已建立的IPv6连接发送。
任何使用介绍者发布的 SSU 地址必须在 “caps” 选项中包含 “4” 或 “6”。
签名:
Alice 对请求进行签名,Bob 在此块中将其转发给 Charlie。验证算法:使用 Alice 的 router 签名密钥验证以下数据:
- prologue: 16 字节 “RelayRequestData”,不以 null 结尾(不包含在消息中)
- bhash: Bob 的 32 字节 router hash(不包含在消息中)
- chash: Charlie 的 32 字节 router hash(不包含在消息中)
- nonce: 4 字节随机数
- relay tag: 4 字节中继标签
- timestamp: 4 字节时间戳(秒)
- ver: 1 字节 SSU 版本
- asz: 1 字节端点(端口 + IP)大小(6 或 18)
- AlicePort: 2 字节 Alice 的端口号
- Alice IP: (asz - 2) 字节 Alice IP 地址
PeerTest
通过会话中的 Data 消息或会话外的 Peer Test 消息发送。请参见下面的 Peer Test 过程部分。
对于消息 2,必须前面有一个 RouterInfo 块,或 I2NP DatabaseStore 消息块(或片段),包含 Alice 的 Router Info,要么在同一有效载荷中(如果有空间),要么在之前的消息中。
对于消息4,如果中继被接受(原因代码为0),必须在前面加上一个RouterInfo块,或包含Charlie的Router Info的I2NP DatabaseStore消息块(或片段),要么在同一有效载荷中(如果有空间),要么在之前的消息中。
+----+----+----+----+----+----+----+----+
| 10 | size | msg|code|flag| |
+----+----+----+----+----+----+ +
| Alice router hash (message 2 only) |
+ or +
| Charlie router hash (message 4 only) |
+ or all zeros if rejected by Bob +
| Not present in messages 1,3,5,6,7 |
+ +----+----+
| | ver|
+----+----+----+----+----+----+----+----+
nonce | timestamp | asz|
+----+----+----+----+----+----+----+----+
|AlicePort| Alice IP address | |
+----+----+----+----+----+----+ +
| signature |
+ length varies +
| 64 bytes for Ed25519 |
~ ~
| . . . |
+----+----+----+----+----+----+----+----+
blk :: 10
size :: 2 bytes, big endian, size of data to follow
msg :: 1 byte message number 1-7
code :: 1 byte status code:
0: accept
1: rejected by Bob, reason unspecified
2: rejected by Bob, no Charlie available
3: rejected by Bob, limit exceeded
4: rejected by Bob, signature failure
5: rejected by Bob, address unsupported
6-63: other rejected by Bob codes TBD
64: rejected by Charlie, reason unspecified
65: rejected by Charlie, unsupported address
66: rejected by Charlie, limit exceeded
67: rejected by Charlie, signature failure
68: rejected by Charlie, Alice is already connected
69: rejected by Charlie, Alice is banned
70: rejected by Charlie, Alice is unknown
70-127: other rejected by Charlie codes TBD
128: reject, source and reason unspecified
129-255: other reject codes TBD
reject codes only allowed in messages 3 and 4
flag :: 1 byte flags, Unused, set to 0 for future compatibility
hash :: Alice's or Charlie's 32-byte router hash,
only present in messages 2 and 4.
All zeros (fake hash) in message 4 if rejected by Bob.
For messages 1-4, the data below here is covered
by the signature, if present, and Bob forwards it unmodified.
ver :: 1 byte SSU version:
1: SSU 1 (not supported)
2: SSU 2 (required)
nonce :: 4 byte test nonce, big endian
timestamp :: Unix timestamp, unsigned seconds.
Wraps around in 2106
asz :: 1 byte endpoint (port + IP) size (6 or 18)
AlicePort :: 2 byte Alice's port number, big endian
Alice IP :: (asz - 2) byte representation of Alice's IP address,
network byte order
signature :: length varies, 64 bytes for Ed25519.
Signature of prologue, Bob's hash,
and signed data above, as signed by
Alice or Charlie.
Only present for messages 1-4.
Optional in message 5-7.
注意事项:
与 SSU 1 不同,消息 1 必须包含 Alice 的 IP 地址和端口。
支持 IPv6 地址测试, 如果 Bob 和 Charlie 在其发布的 IPv6 地址中通过 ‘B’ capability 标识支持, 则 Alice-Bob 和 Alice-Charlie 通信可以通过 IPv6 进行。 详见 Proposal 126。
Alice 通过她希望测试的传输协议(IPv4 或 IPv6)使用现有会话向 Bob 发送请求。当 Bob 通过 IPv4 收到来自 Alice 的请求时,Bob 必须选择一个公布 IPv4 地址的 Charlie。当 Bob 通过 IPv6 收到来自 Alice 的请求时,Bob 必须选择一个公布 IPv6 地址的 Charlie。实际的 Bob-Charlie 通信可能通过 IPv4 或 IPv6 进行(即独立于 Alice 的地址类型)。
消息 1-4 必须包含在现有会话的 Data 消息中。
Bob必须在发送消息2之前将Alice的RI发送给Charlie。
如果接受(原因代码 0),Bob 必须在发送消息 4 之前将 Charlie 的 RI 发送给 Alice。
消息 5-7 必须包含在会话外的 Peer Test 消息中。
消息 5 和 7 可能包含与消息 3 和 4 中发送的相同签名数据,或者可能使用新时间戳重新生成。签名是可选的。
消息 6 可能包含与消息 1 和 2 中发送的相同签名数据,或者可能使用新的时间戳重新生成。签名是可选的。
签名:
Alice 签署请求并将其包含在消息 1 中;Bob 在消息 2 中将其转发给 Charlie。Charlie 签署响应并将其包含在消息 3 中;Bob 在消息 4 中将其转发给 Alice。签名算法:使用 Alice 或 Charlie 的签名密钥对以下数据进行签名或验证:
- prologue: 16 字节 “PeerTestValidate”,非空结尾(不包含在消息中)
- bhash: Bob 的 32 字节 router hash(不包含在消息中)
- ahash: Alice 的 32 字节 router hash (仅在消息 3 和 4 的签名中使用;不包含在消息 3 或 4 中)
- ver: 1 字节 SSU 版本
- nonce: 4 字节测试随机数
- timestamp: 4 字节时间戳(秒)
- asz: 1 字节端点(端口 + IP)大小(6 或 18)
- AlicePort: 2 字节 Alice 的端口号
- Alice IP: (asz - 2)字节 Alice IP 地址
有效载荷
TODO 仅当我们轮换密钥时
+----+----+----+----+----+----+----+----+
| 11 | size | TBD |
+----+----+----+ +
| |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 11
size :: 2 bytes, big endian, size of data to follow
注意事项
4 字节确认通过,后跟确认计数和零个或多个否定确认/确认范围。
这个设计改编并简化自 QUIC。设计目标如下:
- 我们希望高效编码一个"bitfield",它是一个表示已确认数据包的比特序列。
- bitfield 主要由 1 组成。1 和 0 通常都以连续的"块"形式出现。
- 数据包中可用于确认的空间大小是可变的。
- 最重要的比特是编号最高的那个。编号较低的比特重要性较低。在距离最高位比特一定距离以下,最旧的比特将被"遗忘",不再发送。
下面指定的编码通过发送设置为1的最高位的位数,以及低于该位且也设置为1的额外连续位来实现这些设计目标。之后,如果有空间,还会发送一个或多个"范围",指定连续0位和低于该位的连续1位的数量。更多背景信息请参见QUIC RFC 9000 第13.2.3节。
+----+----+----+----+----+----+----+----+
| 12 | size | Ack Through |acnt|
+----+----+----+----+----+----+----+----+
| range | range | . . . |
+----+----+----+----+ +
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 12
size :: 2 bytes, big endian, size of data to follow,
5 minimum
ack through :: highest packet number acked
acnt :: number of acks lower than ack through also acked,
0-255
range :: If present,
1 byte nack count followed by 1 byte ack count,
0-255 each
示例:
我们只想对数据包 10 进行 ACK:
- Ack Through: 10
- acnt: 0
- 未包含范围
我们只想要 ACK 数据包 8-10:
- Ack Through: 10
- acnt: 2
- 未包含范围
我们想要 ACK 10 9 8 6 5 2 1 0,并且 NACK 7 4 3。ACK Block 的编码是:
- Ack Through: 10
- acnt: 2 (ack 9 8)
- range: 1 2 (nack 7, ack 6 5)
- range: 2 3 (nack 4 3, ack 2 1 0)
注意事项:
- 范围可能不存在。范围的最大数量未指定,可能有尽可能多的范围以适应数据包大小。
- 如果确认超过 255 个连续数据包,范围 nack 可能为零。
- 如果拒绝确认超过 255 个连续数据包,范围 ack 可能为零。
- 范围 nack 和 ack 不能同时为零。
- 在最后一个范围之后,数据包既不被确认也不被拒绝确认。 ack 块的长度以及如何处理过期的 acks/nacks 由 ack 块的发送方决定。 有关讨论请参见下面的 ack 部分。
- ack through 应该是收到的最高数据包编号, 任何更高编号的数据包都未收到。 然而,在有限的情况下,它可能更低,比如 确认一个"填补空隙"的单个数据包,或者简化的 不维护所有已接收数据包状态的实现。 在最高接收编号之上,数据包既不被确认也不被拒绝确认, 但在几个 ack 块之后,进入快速重传模式可能是合适的。
- 此格式是 QUIC 中格式的简化版本。 它旨在高效编码大量 ACK, 以及突发的 NACK。
- ACK 块用于确认数据阶段数据包。 它们仅应包含在会话内数据阶段数据包中。
Address
2 字节端口和 4 或 16 字节 IP 地址。Alice 的地址,由 Bob 发送给 Alice,或 Bob 的地址,由 Alice 发送给 Bob。
+----+----+----+----+----+----+----+----+
| 13 | 6 or 18 | Port | IP Address
+----+----+----+----+----+----+----+----+
|
+----+
blk :: 13
size :: 2 bytes, big endian, 6 or 18
port :: 2 bytes, big endian
ip :: 4 byte IPv4 or 16 byte IPv6 address,
big endian (network byte order)
Relay Tag Request
这可以由 Alice 在 Session Request、Session Confirmed 或 Data 消息中发送。在 Session Created 消息中不支持,因为 Bob 还没有 Alice 的 RI,也不知道 Alice 是否支持中继。此外,如果 Bob 正在接收传入连接,他可能不需要介绍者(除了可能需要其他类型的 ipv4/ipv6)。
当在Session Request中发送时,Bob可能在Session Created消息中用Relay Tag响应,或者可能选择等到在Session Confirmed中收到Alice的RouterInfo来验证Alice的身份后再在Data消息中响应。如果Bob不希望为Alice中继,他就不发送Relay Tag块。
+----+----+----+
| 15 | 0 |
+----+----+----+
blk :: 15
size :: 2 bytes, big endian, value = 0
载荷
这可能由 Bob 在 Session Confirmed 或 Data 消息中发送,以响应来自 Alice 的 Relay Tag Request。
当 Relay Tag Request 在 Session Request 中发送时,Bob 可以在 Session Created 消息中用 Relay Tag 响应,或者可以选择等待接收 Alice 的 RouterInfo 在 Session Confirmed 中以验证 Alice 的身份,然后在 Data 消息中响应。如果 Bob 不希望为 Alice 进行中继,他不会发送 Relay Tag 块。
+----+----+----+----+----+----+----+
| 16 | 4 | relay tag |
+----+----+----+----+----+----+----+
blk :: 16
size :: 2 bytes, big endian, value = 4
relay tag :: 4 bytes, big endian, nonzero
注意事项
用于后续连接。通常包含在 Session Created 和 Session Confirmed 消息中。如果之前的令牌过期,也可能在长期会话的 Data 消息中再次发送。
+----+----+----+----+----+----+----+----+
| 17 | 12 | expires |
+----+----+----+----+----+----+----+----+
token |
+----+----+----+----+----+----+----+
blk :: 17
size :: 2 bytes, big endian, value = 12
expires :: Unix timestamp, unsigned seconds.
Wraps around in 2106
token :: 8 bytes, big endian
问题
一个包含任意数据的 Ping,该数据将在 Path Response 中返回,用作保持连接或验证 IP/端口变更。
+----+----+----+----+----+----+----+----+
| 18 | size | Arbitrary Data |
+----+----+----+ +
| |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 18
size :: 2 bytes, big endian, size of data to follow
data :: Arbitrary data to be returned in a Path Response
length as selected by sender
注意事项:
建议但不要求最小数据大小为8字节,包含随机数据。
Path Response
一个包含从路径质询中接收到的数据的 Pong,作为对路径质询的回复,用于保持连接活跃或验证 IP/端口变更。
+----+----+----+----+----+----+----+----+
| 19 | size | |
+----+----+----+ +
| Data received in Path Challenge |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 19
size :: 2 bytes, big endian, size of data to follow
data :: As received in a Path Challenge
First Packet Number
可选择在每个方向的握手中包含,用于指定将要发送的第一个数据包编号。这为头部加密提供了更多安全性,类似于 TCP。
未完全定义,当前不支持。
+----+----+----+----+----+----+----+
| 20 | size | First pkt number |
+----+----+----+----+----+----+----+
blk :: 20
size :: 4
pkt num :: The first packet number to be sent in the data phase
Congestion
这个区块被设计为一种可扩展的方法来交换拥塞控制信息。拥塞控制可能很复杂,并且可能随着我们在实时测试中获得更多协议经验,或在全面部署后而演进。
这样可以将任何拥塞信息排除在高使用率的 I2NP、First Fragment、Followon Fragment 和 ACK 块之外,这些块中没有为标志分配空间。虽然 Data 数据包头部有三个字节的未使用标志,但这也为扩展性提供了有限的空间,并且加密保护较弱。
虽然使用4字节块来存储两位信息有些浪费,但通过将其放在单独的块中,我们可以轻松地用额外数据扩展它,如当前窗口大小、测量的RTT或其他标志。经验表明,仅使用标志位对于实现高级拥塞控制方案往往是不够的且实现起来很笨拙。试图在ACK块中添加对任何可能的拥塞控制功能的支持,会浪费空间并增加该块解析的复杂性。
实现不应假设其他 router 支持此处包含的任何特定标志位或特性,除非未来版本的规范要求实现。
这个块应该是载荷中最后一个非填充块。
+----+----+----+----+
| 21 | size |flag|
+----+----+----+----+
blk :: 21
size :: 1 (or more if extended)
flag :: 1 byte flags
bit order: 76543210 (bit 7 is MSB)
bit 0: 1 to request immediate ack
bit 1: 1 for explicit congestion notification (ECN)
bits 7-2: Unused, set to 0 for future compatibility
载荷
这用于 AEAD payload 内部的填充。所有消息的填充都在 AEAD payload 内部。
填充应大致遵守协商的参数。Bob 在 Session Created 中发送了他请求的 tx/rx 最小/最大参数。Alice 在 Session Confirmed 中发送了她请求的 tx/rx 最小/最大参数。更新的选项可以在数据阶段发送。参见上面的选项块信息。
如果存在,这必须是载荷中的最后一个块。
+----+----+----+----+----+----+----+----+
|254 | size | padding |
+----+----+----+ +
| |
~ . . . ~
| |
+----+----+----+----+----+----+----+----+
blk :: 254
size :: 2 bytes, big endian, size of padding to follow
padding :: random data
注意事项:
Size = 0 是允许的。
填充策略待定。
最小填充待定。
允许仅填充的载荷。
填充默认值待定。
参见选项块中的填充参数协商
参见选项块中的最小/最大填充参数
不要超过 MTU。如果需要更多填充,发送多个消息。
Router 对违反协商填充的响应取决于具体实现。
填充长度应该根据每条消息的具体情况来决定,并基于长度分布的估算,或者应该添加随机延迟。这些对策措施应该被包含进来以抵御 DPI(深度包检测),因为消息大小会暴露传输协议正在承载 I2P 流量。确切的填充方案是未来工作的一个领域,NTCP2 的附录 A 提供了关于这个主题的更多信息。
Replay Prevention
SSU2 的设计旨在最大限度地减少攻击者重放消息的影响。
Token Request、Retry、Session Request、Session Created、Hole Punch 和 out-of-session Peer Test 消息必须包含 DateTime 块。
Alice 和 Bob 都会验证这些消息的时间是否在有效的偏差范围内(建议 +/- 2 分钟)。为了"探测抗性",如果偏差无效,Bob 不应回复 Token Request 或 Session Request 消息,因为这些消息可能是重放或探测攻击。
Bob 可以选择拒绝重复的令牌请求和重试消息,即使时间偏差是有效的,可以通过布隆过滤器或其他机制来实现。但是,回复这些消息的大小和 CPU 成本都很低。在最坏的情况下,重放的令牌请求消息可能会使之前发送的令牌失效。
token系统极大地减少了重放Session Request消息的影响。由于token只能使用一次,重放的Session Request消息永远不会有有效的token。Bob可以选择拒绝重复的Session Request消息,即使偏差是有效的,可通过布隆过滤器或其他机制来实现。然而,用Retry消息回复的大小和CPU成本都很低。在最坏的情况下,发送Retry消息可能会使先前发送的token失效。
重复的 Session Created 和 Session Confirmed 消息将不会通过验证,因为 Noise 握手状态不会处于正确状态来解密它们。最坏的情况下,对等节点可能会重新传输 Session Confirmed 来响应明显重复的 Session Created。
重放的 Hole Punch 和 Peer Test 消息应该几乎没有或完全没有影响。
Router必须使用数据消息包编号来检测和丢弃重复的数据阶段消息。每个包编号应该只使用一次。重放的消息必须被忽略。
Handshake Retransmission
Session Request
如果Alice没有收到Session Created或Retry:
保持相同的源ID和连接ID、临时密钥以及数据包编号0。或者,只需保留并重传相同的加密数据包。数据包编号不得递增,因为这会改变用于加密Session Created消息的链式哈希值。
推荐的重传间隔:1.25、2.5 和 5 秒(首次发送后的 1.25、3.75 和 8.75 秒)。推荐的超时时间:总计 15 秒
Session Created
如果 Bob 没有收到 Session Confirmed:
保持相同的源和连接ID、临时密钥以及数据包编号0。或者,仅保留加密数据包。数据包编号不得递增,因为这会改变用于加密Session Confirmed消息的链式哈希值。
推荐的重传间隔:1、2 和 4 秒(首次发送后的 1、3 和 7 秒)。推荐超时时间:总共 12 秒
Session Confirmed
在 SSU 1 中,Alice 不会转换到数据阶段,直到从 Bob 接收到第一个数据包。这使得 SSU 1 成为两个往返的建立过程。
对于 SSU 2,推荐的会话确认重传间隔:1.25、2.5 和 5 秒(首次发送后的 1.25、3.75 和 8.75 秒)。
有几种替代方案。所有方案都是 1 RTT:
Alice 假设 Session Confirmed 已被接收,立即发送数据消息,从不重传 Session Confirmed。乱序接收的数据包(在 Session Confirmed 之前)将无法解密,但会被重传。如果 Session Confirmed 丢失,所有已发送的数据消息都将被丢弃。
如1)中所述,立即发送数据消息,但同时重传Session Confirmed直到收到数据消息为止。
我们可以使用 IK 而不是 XK,因为它在握手中只有两条消息,但它使用了额外的 DH(4 个而不是 3 个)。
推荐的实现方式是选项 2)。Alice 必须保留重传 Session Confirmed 消息所需的信息。Alice 还应该在重传 Session Confirmed 消息后重传所有 Data 消息。
重传 Session Confirmed 时,保持相同的源和连接 ID、ephemeral key 和数据包编号 1。或者,仅保留加密的数据包。数据包编号不得递增,因为这会改变链式哈希值,而该值是 split() 函数的输入。
Bob 可以保留(排队)在收到 Session Confirmed 消息之前接收到的数据消息。在收到 Session Confirmed 消息之前,header protection keys 和 decryption keys 都不可用,所以 Bob 不知道它们是数据消息,但可以推测它们是。在收到 Session Confirmed 消息后,Bob 能够解密并处理排队的数据消息。如果这过于复杂,Bob 可以直接丢弃无法解密的数据消息,因为 Alice 会重新传输它们。
注意:如果会话确认数据包丢失,Bob 将重传会话创建。会话创建头部将无法用 Alice 的介绍密钥解密,因为它是用 Bob 的介绍密钥设置的(除非使用 Bob 的介绍密钥执行回退解密)。如果之前未确认,Bob 可能会立即重传会话确认数据包,并且收到了无法解密的数据包。
Token Request
如果 Alice 没有收到 Retry:
保持相同的源和连接 ID。实现可以生成新的随机数据包编号并加密新数据包;或者可以重用相同的数据包编号,或者仅保留并重传相同的加密数据包。数据包编号不得递增,因为这会改变用于加密 Session Created 消息的链式哈希值。
推荐的重传间隔:3秒和6秒(首次发送后的3秒和9秒)。推荐超时时间:总计15秒
Retry
如果 Bob 没有收到 Session Request:
Retry 消息在超时时不会重传,以减少伪造源地址的影响。
但是,如果收到带有原始(无效)令牌的重复会话请求消息,或者收到重复的令牌请求消息,则可能会重新传输 Retry 消息。无论哪种情况,这都表明 Retry 消息已丢失。
如果收到第二个Session Request消息,其令牌与之前不同但仍然无效,则丢弃待处理的会话并不予回应。
如果重新发送重试消息:保持相同的源和连接ID以及token。实现可以生成新的随机数据包编号并加密新的数据包;或者可以重用相同的数据包编号或仅保留并重传相同的加密数据包。
Total Timeout
建议握手的总超时时间为 20 秒。
Duplicates and Error Handling
三个 Noise 握手消息 Session Request、Session Created 和 Session Confirmed 的重复消息必须在消息头的 MixHash() 之前被检测到。虽然在那之后 Noise AEAD 处理可能会失败,但握手哈希值已经被破坏了。
如果三个消息中的任何一个损坏并且 AEAD 失败,握手就无法通过重传来恢复,因为已经对损坏的消息调用了 MixHash()。
Tokens
Session Request 头部中的 Token 用于 DoS 缓解、防止源地址欺骗,以及抵抗重放攻击。
如果 Bob 不接受 Session Request 消息中的令牌,Bob 不会解密该消息,因为这需要昂贵的 DH 操作。Bob 只需发送带有新令牌的 Retry 消息。
如果随后收到带有该token的Session Request消息,Bob将继续解密该消息并进行握手。
如果令牌生成器存储这些值以及关联的IP和端口(内存中或持久存储),令牌必须是随机生成的8字节值。生成器不得生成不透明值,例如,使用IP、端口和当前小时或天的SipHash(使用密钥种子K0、K1)来创建无需保存在内存中的令牌,因为这种方法使得拒绝重复使用的令牌和重放攻击变得困难。
令牌只能使用一次。Bob 在重试消息中发送给 Alice 的令牌必须立即使用,并在几秒钟内过期。在已建立会话中的新令牌块中发送的令牌可以在后续连接中使用,它会在该块中指定的时间过期。过期时间由发送方指定;建议值为最少一小时,最多几小时。
如果router的IP或端口发生变化,它必须删除旧IP或端口的所有已保存token(包括入站和出站),因为它们不再有效。token可以选择性地在router重启时持久化,具体取决于实现。不保证接受未过期的token;如果Bob忘记或删除了他保存的token,他会向Alice发送Retry。router可以选择限制token存储,并移除最旧的已存储token,即使它们尚未过期。
新的 Token 块可以从 Alice 发送到 Bob,或从 Bob 发送到 Alice。它们通常会发送一次,在会话建立期间或之后不久。token 可以在到期前或到期后重新发送并带有新的到期时间,或者可以发送新的 token。Router 应该假定只有最后接收到的 token 是有效的;不需要为同一个 IP/端口存储多个入站或出站 token。
token绑定到源IP/端口和目标IP/端口的组合。在IPv4上接收到的token不能用于IPv6,反之亦然。
如果会话期间任一对等节点迁移到新的 IP 或端口(参见连接迁移部分),之前交换的所有令牌都将失效,必须交换新的令牌。
实现可以(但不是必须)将令牌保存到磁盘并在重启时重新加载它们。如果进行持久化,实现必须确保在重新加载令牌之前,IP地址和端口自关闭以来没有发生变化。
I2NP Message Fragmentation
与 SSU 1 的差异
注意:与 SSU 1 中一样,初始片段不包含关于片段总数或总长度的信息。后续片段不包含关于其偏移量的信息。这为发送方提供了根据数据包中可用空间"即时"分片的灵活性。(Java I2P 不会这样做;它在发送第一个片段之前进行"预分片")然而,这确实给接收方带来了负担,需要存储无序接收的片段并延迟重组,直到接收到所有片段。
与 SSU 1 中一样,片段的任何重传都必须保持片段先前传输的长度(和隐含偏移量)。
SSU 2 确实将三种情况(完整消息、初始片段和后续片段)分离为三种不同的块类型,以提高处理效率。
I2NP Message Duplication
该协议并不能完全防止 I2NP 消息的重复传递。IP 层的重复或重放攻击将在 SSU2 层被检测到,因为每个数据包编号只能使用一次。
然而,当 I2NP 消息或片段在新数据包中重传时,这在 SSU2 层是无法检测到的。路由器应该强制执行 I2NP 过期检查(既包括过旧的也包括过于未来的),并使用布隆过滤器或其他基于 I2NP 消息 ID 的机制。
router或SSU2实现可能使用其他机制来检测重复消息。例如,SSU2可以维护一个最近接收消息ID的缓存。这取决于具体的实现。
Congestion Control
该提案规定了数据包编号和 ACK 块的协议。这为发送方实现高效且响应迅速的拥塞控制算法提供了充分的实时信息,同时在实现中允许灵活性和创新。本节讨论实现目标并提供建议。一般指导可在 RFC 9002 中找到。另请参阅 RFC 6298 获取重传定时器的指导。
仅包含ACK的数据包不应计入传输中的字节数或包数,且不受拥塞控制。与TCP不同,SSU2可以检测到这些数据包的丢失,该信息可用于调整拥塞状态。但是,本文档未指定执行此操作的机制。
如果需要的话,包含某些其他非数据块的数据包也可能被排除在拥塞控制之外,这取决于具体实现。例如:
- 对等测试
- 中继请求/介绍/响应
- 路径挑战/响应
建议拥塞控制基于字节计数而非数据包计数,遵循TCP RFC和QUIC RFC 9002的指导原则。额外的数据包计数限制也可能有用,以防止内核或中间设备中的缓冲区溢出,这取决于具体实现,尽管这可能会增加显著的复杂性。如果按会话和/或总数据包输出进行带宽限制和/或节拍控制,这可能会减少对数据包计数限制的需求。
Packet Numbers
在 SSU 1 中,ACK 和 NACK 包含 I2NP 消息编号和分片位掩码。发送方跟踪出站消息(及其分片)的 ACK 状态,并根据需要重传分片。
在 SSU 2 中,ACK 和 NACK 包含数据包编号。发送方必须维护一个数据结构,将数据包编号映射到其内容。当数据包被 ACK 或 NACK 时,发送方必须确定该数据包中包含哪些 I2NP 消息和片段,以决定需要重传什么。
Session Confirmed ACK
Bob 发送数据包 0 的 ACK,确认 Session Confirmed 消息并允许 Alice 进入数据阶段,同时丢弃为可能重传而保存的大型 Session Confirmed 消息。这替代了在 SSU 1 中由 Bob 发送的 DeliveryStatusMessage。
Bob 应该在收到 Session Confirmed 消息后尽快发送 ACK。可以接受少量延迟(不超过 50 毫秒),因为至少一个 Data 消息应该在 Session Confirmed 消息之后几乎立即到达,这样 ACK 可以同时确认 Session Confirmed 和 Data 消息。这将防止 Bob 必须重新传输 Session Confirmed 消息。
Generating ACKs
定义:Ack-eliciting packets(引发确认的数据包):包含引发确认块的数据包会在最大确认延迟时间内从接收方引发ACK,这些数据包被称为ack-eliciting packets。
Router 会确认它们接收和处理的所有数据包。然而,只有需要确认的数据包会导致在最大确认延迟时间内发送 ACK 块。不需要确认的数据包只有在因其他原因发送 ACK 块时才会被确认。
当因任何原因发送数据包时,如果最近没有发送过 ACK 块,端点应该尝试包含一个 ACK 块。这样做有助于对等节点及时检测丢失。
一般来说,接收方频繁的反馈能够改善丢包和拥塞响应,但这必须与接收方对每个需要确认的数据包都发送 ACK 块所产生的过度负载之间取得平衡。下面提供的指导旨在实现这种平衡。
包含除以下情况之外任何块的会话内数据包都会引发确认:
- ACK block
- Address block
- DateTime block
- Padding block
- Termination block
- 与 Termination block 在同一数据包中的任何 block
- 其他?
包含终止块且原因不是"termination received"的数据包,会用包含终止块且原因为"termination received"的数据包进行确认。
会话外数据包,包括握手消息和对等测试消息5-7,拥有自己的确认机制。见下文。
Handshake ACKs
这些是特殊情况:
- Token Request 由 Retry 隐式确认
- Session Request 由 Session Created 或 Retry 隐式确认
- Retry 由 Session Request 隐式确认
- Session Created 由 Session Confirmed 隐式确认
- Session Confirmed 应立即被确认
Sending ACK Blocks
ACK 块用于确认数据阶段数据包。它们只应包含在会话内数据阶段数据包中。
每个数据包都应该至少被确认一次,而引发确认的数据包必须在最大延迟时间内至少被确认一次。
端点必须在其最大延迟时间内立即确认所有需要确认的握手数据包,但有以下例外。在握手确认之前,端点可能没有用于解密接收到的数据包的包头加密密钥。因此,它可能会缓冲这些数据包,并在获得必要的密钥后对其进行确认。
由于仅包含 ACK 块的数据包不受拥塞控制,端点在响应接收到的 ack-eliciting 数据包时,不得发送超过一个此类数据包。
端点不得发送非确认引发包来响应非确认引发包,即使在接收到的包之前存在包间隙。这避免了确认的无限反馈循环,这种循环可能会阻止连接进入空闲状态。当端点发送 ACK 块来响应其他事件时,非确认引发包最终会被确认。
仅发送ACK块的端点不会从其对等端接收确认,除非这些确认包含在带有引发确认块的数据包中。当有新的引发确认数据包需要确认时,端点应该将ACK块与其他块一起发送。当只有非引发确认数据包需要确认时,端点可以选择在收到引发确认数据包之前不将ACK块与出站块一起发送。
仅发送非确认引发数据包的端点可能会选择偶尔向这些数据包添加确认引发块,以确保它收到确认。在这种情况下,端点绝对不能在所有原本为非确认引发的数据包中发送确认引发块,以避免确认的无限反馈循环。
为了协助发送方的丢包检测,当端点在以下任何情况下收到需要确认的数据包时,应该立即生成并发送一个 ACK 块:
当接收到的数据包的包号小于另一个已接收到的需要确认的数据包时
当数据包的包号大于已接收的最高编号确认引发数据包的包号,且该数据包与此数据包之间存在丢失的数据包时。
当数据包头中的 ack-immediate 标志被设置时
这些算法预期能够抵御不遵循上述指导原则的接收方。但是,实现方只应在仔细考虑更改对端点建立的连接以及网络其他用户的性能影响后,才偏离这些要求。
ACK Frequency
接收方决定响应需要确认的数据包时发送确认的频率。这个决定涉及一个权衡。
端点依赖及时确认来检测丢失。基于窗口的拥塞控制器依赖确认来管理其拥塞窗口。在这两种情况下,延迟确认都可能对性能产生不利影响。
另一方面,减少仅携带确认信息的数据包频率可以降低两端的数据包传输和处理成本。这可以改善严重不对称链路上的连接吞吐量,并减少使用返回路径容量的确认流量;参见 RFC 3449 第 3 节。
接收方在收到至少两个需要确认的数据包后应发送ACK块。此建议具有通用性,与TCP端点行为建议 RFC 5681 一致。对网络条件的了解、对对等方拥塞控制器的了解,或进一步的研究和实验可能会提出具有更好性能特征的替代确认策略。
接收方可以在确定是否发送 ACK 块作为响应之前处理多个可用数据包。通常情况下,接收方不应将 ACK 延迟超过 RTT / 6,或最多 150 毫秒。
数据包头部中的 ack-immediate 标志是一个请求,要求接收方在接收后尽快发送确认,通常在几毫秒内。一般来说,接收方不应将立即 ACK 延迟超过 RTT / 16,或最多 5 毫秒。
Immediate ACK Flag
接收方不知道发送方的发送窗口大小,因此不知道在发送ACK之前应该延迟多长时间。数据包头中的立即ACK标志是通过最小化有效RTT来维持最大吞吐量的重要方式。立即ACK标志位于头部字节13的第0位,即(header[13] & 0x01)。当设置时,请求立即ACK。详情请参见上面的短头部章节。
发送方可以使用几种可能的策略来确定何时设置 immediate-ack 标志:
- 每 N 个数据包设置一次,其中 N 为较小值
- 在一连串数据包的最后一个上设置
- 当发送窗口接近满载时设置,例如超过 2/3 满载时
- 在所有包含重传片段的数据包上设置
立即 ACK 标志应该仅在包含 I2NP 消息或消息片段的数据包中是必需的。
ACK Block Size
当发送ACK块时,会包含一个或多个已确认数据包的范围。包含对较旧数据包的确认可以减少因丢失先前发送的ACK块而导致的虚假重传的可能性,但代价是ACK块变得更大。
ACK 块应始终确认最近接收到的数据包,数据包越乱序,越需要快速发送更新的 ACK 块,以防止对等节点将数据包声明为丢失并错误地重传其包含的块。一个 ACK 块必须适合单个数据包。如果不适合,则省略较旧的范围(具有最小数据包编号的范围)。
接收方限制其记住并在 ACK 块中发送的 ACK 范围数量,这既是为了限制 ACK 块的大小,也是为了避免资源耗尽。在收到对 ACK 块的确认后,接收方应停止跟踪这些已确认的 ACK 范围。发送方可以期望大多数数据包都能收到确认,但此协议不保证接收方处理的每个数据包都会收到确认。
保留大量 ACK 范围可能会导致 ACK 块变得过大。接收方可以丢弃未确认的 ACK 范围来限制 ACK 块大小,代价是发送方会增加重传次数。如果 ACK 块太大而无法装入数据包中,这种做法是必要的。接收方还可以进一步限制 ACK 块大小,以便为其他块保留空间或限制确认消息消耗的带宽。
接收方必须保留 ACK 范围,除非它能够确保不会随后接受该范围内数字的数据包。维护一个随着范围丢弃而增加的最小数据包号码是以最小状态实现这一目标的一种方法。
接收方可以丢弃所有 ACK 范围,但必须保留已成功处理的最大数据包编号,因为这个编号用于从后续数据包中恢复数据包编号。
以下部分描述了一种示例性方法,用于确定在每个ACK块中应该确认哪些数据包。尽管该算法的目标是为每个处理的数据包生成确认,但确认仍然可能会丢失。
Limiting Ranges by Tracking ACK Blocks
当发送包含 ACK 块的数据包时,可以保存该块中的 Ack Through 字段。当包含 ACK 块的数据包被确认时,接收方可以停止确认小于或等于已发送 ACK 块中 Ack Through 字段的数据包。
只发送非确认触发数据包(如 ACK 块)的接收方可能在很长一段时间内都收不到确认。这可能导致接收方长时间维护大量 ACK 块的状态,并且它发送的 ACK 块可能会变得不必要地庞大。在这种情况下,接收方可以偶尔发送 PING 或其他小的确认触发块,比如每个往返时间发送一次,来引发对等方发送 ACK。
在没有ACK块丢失的情况下,该算法允许最少1个RTT的重排序。在存在ACK块丢失和重排序的情况下,这种方法不能保证发送方在每个确认不再包含在ACK块中之前都能看到它。数据包可能会乱序接收,并且包含它们的所有后续ACK块都可能丢失。在这种情况下,丢失恢复算法可能会导致虚假重传,但发送方将继续向前推进。
Congestion
I2P传输不保证I2NP消息的有序传递。因此,包含一个或多个I2NP消息或片段的Data消息的丢失不会阻止其他I2NP消息的传递;不存在队头阻塞。如果发送窗口允许,实现应该在丢失恢复阶段继续发送新消息。
Retransmission
发送方不应保留消息的完整内容以便进行相同的重传(握手消息除外,见上文)。发送方必须在每次发送消息时组装包含最新信息(ACK、NACK 和未确认数据)的消息。发送方应避免重传已确认消息中的信息。这包括在被声明丢失后又被确认的消息,这种情况在网络重排序时可能发生。
Window
待定。通用指导可在 RFC 9002 中找到。
Connection Migration
在会话生存期内,对等节点的IP或端口可能会发生变化。IP变化可能由IPv6临时地址轮换、ISP驱动的定期IP更改、移动客户端在WiFi和蜂窝网络IP之间切换或其他本地网络变化引起。端口变化可能由于先前绑定超时后的NAT重新绑定而引起。
对等节点的IP或端口可能由于各种路径上和路径外攻击而出现变化,包括修改或注入数据包。
连接迁移是验证新源端点(IP+端口)的过程,同时防止未经验证的更改。此过程是 QUIC RFC 9000 中定义流程的简化版本。此过程仅针对会话的数据阶段定义。在握手期间不允许迁移。所有握手数据包都必须验证来自与先前发送和接收数据包相同的 IP 和端口。换句话说,对等方的 IP 和端口在握手期间必须保持不变。
Threat Model
(改编自 QUIC RFC 9000)
注意事项
对等节点可能伪造其源地址,导致端点向不情愿的主机发送过量数据。如果端点发送的数据量明显超过伪造的对等节点,连接迁移可能被用来放大攻击者向受害者生成的数据量。
Session Confirmed Fragmentation(会话确认分片)
路径上的攻击者可以通过复制并转发一个带有伪造地址的数据包来导致虚假的连接迁移,使其在原始数据包之前到达。带有伪造地址的数据包会被视为来自正在迁移的连接,而原始数据包会被视为重复数据包并被丢弃。在虚假迁移之后,源地址验证将失败,因为源地址处的实体没有必要的加密密钥来读取或响应发送给它的 Path Challenge,即使它想要这样做也无法做到。
Off-Path Packet Forwarding
能够观察数据包的路径外攻击者可能会将真实数据包的副本转发给端点。如果复制的数据包在真实数据包之前到达,这将表现为NAT重新绑定。任何真实数据包都会被作为重复包丢弃。如果攻击者能够继续转发数据包,它可能能够导致迁移到通过攻击者的路径上。这会将攻击者置于路径上,使其能够观察或丢弃所有后续数据包。
Privacy Implications
QUIC RFC 9000 规定在更改网络路径时应更改连接ID。在多个网络路径上使用稳定的连接ID会允许被动观察者关联这些路径之间的活动。在网络间移动的端点可能不希望除了对等方以外的任何实体关联其活动。然而,QUIC不会加密标头中的连接ID。SSU2确实会这样做,因此隐私泄露需要被动观察者同时访问netDb以获取解密连接ID所需的介绍密钥。即使有了介绍密钥,这也不是一个强攻击,我们在SSU2中迁移后不会更改连接ID,因为这会带来显著的复杂性。
Initiating Path Validation
在数据阶段,对等节点必须检查每个接收到的数据包的源IP和端口。如果IP或端口与之前接收到的不同,并且数据包不是重复的包序号,并且数据包成功解密,会话将进入路径验证阶段。
此外,节点必须验证新的IP和端口根据本地验证规则是有效的(未被阻止,不是非法端口等)。节点不需要支持IPv4和IPv6之间的迁移,并且可能将其他地址族中的新IP视为无效,因为这不是预期的行为,可能会增加显著的实现复杂性。在接收到来自无效IP/端口的数据包时,实现可能会简单地丢弃它,或者可能使用旧的IP/端口启动路径验证。
进入路径验证阶段后,请执行以下步骤:
- 启动一个持续几秒钟的路径验证超时计时器, 或当前RTO的几倍(待定)
- 将拥塞窗口减少到最小值
- 将PMTU减少到最小值(1280)
- 发送一个包含Path Challenge块、 Address块(包含新的IP/端口)、 以及通常还有ACK块的数据包到新的IP和端口。 此数据包使用与当前会话相同的连接ID和加密密钥。 Path Challenge块数据必须包含足够的熵 (至少8字节),以防止被伪造。
- 可选地,也向旧的IP/端口发送一个Path Challenge, 使用不同的块数据。见下文。
- 基于当前RTO启动一个Path Response超时计时器 (通常为RTT加上RTTdev的倍数)
在路径验证阶段,会话可以继续处理传入的数据包。无论是来自旧的还是新的 IP/端口。会话也可以继续发送和确认数据包。但是,在路径验证阶段,拥塞窗口和 PMTU 必须保持在最小值,以防止通过向伪造地址发送大量流量来进行拒绝服务攻击。
实现可以但不是必须同时尝试验证多条路径。这可能不值得增加复杂性。实现可以但不是必须记住之前的 IP/端口已经被验证过,如果对等节点返回到其之前的 IP/端口,则跳过路径验证。
如果收到了Path Response,其中包含在Path Challenge中发送的相同数据,则路径验证已成功。Path Response消息的源IP/端口不需要与Path Challenge发送到的地址相同。
如果在路径响应计时器到期之前未收到路径响应,则发送另一个路径质询并将路径响应计时器加倍。
如果在路径验证计时器过期之前未收到路径响应,则路径验证失败。
Message Contents
Data 消息应包含以下数据块。除了填充(Padding)必须在最后之外,顺序没有特定要求:
- Path Validation 或 Path Response 块。 Path Validation 包含不透明数据,建议最少 8 字节。 Path Response 包含来自 Path Validation 的数据。
- 包含接收方表面 IP 的地址块
- DateTime 块
- ACK 块
- 填充块
不建议在消息中包含任何其他块(例如,I2NP)。
允许在包含路径响应的消息中包含路径验证块,以启动另一个方向的验证。
Path Challenge 和 Path Response 块是需要 ACK 确认的。Path Challenge 将通过包含 Path Response 和 ACK 块的 Data 消息进行 ACK 确认。Path Response 应该通过包含 ACK 块的 Data 消息进行 ACK 确认。
Routing during Path Validation
QUIC规范在路径验证期间数据包发送位置方面并不明确——是发送到旧的IP/端口还是新的IP/端口?在快速响应IP/端口变化与避免向伪造地址发送流量之间需要取得平衡。同时,伪造的数据包不得对现有会话产生实质性影响。仅端口变化很可能是由于空闲期后的NAT重新绑定造成的;IP变化可能发生在一个或两个方向的高流量阶段。
策略需要进行研究和改进。可能的方案包括:
- 在验证之前不向新的 IP/端口发送数据包
- 继续向旧的 IP/端口发送数据包,直到 新的 IP/端口通过验证
- 同时重新验证旧的 IP/端口
- 在旧的或新的 IP/端口通过验证之前不发送任何数据
- 仅端口变更与 IP 变更采用不同的策略
- 在同一个 /32 内的 IPv6 变更采用不同的策略,通常由 临时地址轮换引起
Responding to Path Challenge
收到路径挑战后,对等端必须用包含路径响应的数据包进行响应,其中包含来自路径挑战的数据。TODO 也许???:路径响应必须发送到接收路径挑战的IP/端口。这不一定是之前为对等端建立的IP/端口。这确保只有当路径在两个方向上都正常工作时,对等端的路径验证才会成功。请参见下面的"本地变更后验证"部分。
除非IP/端口与该peer先前已知的IP/端口不同,否则将Path Challenge视为简单的ping,并无条件地用Path Response进行响应。接收方不会基于收到的Path Challenge保留或更改任何状态。如果IP/端口不同,peer必须根据本地验证规则验证新的IP和端口是否有效(未被阻止、非非法端口等)。Peer不需要支持IPv4和IPv6之间的跨地址族响应,并且可以将其他地址族中的新IP视为无效,因为这不是预期的行为。
除非受到拥塞控制约束,否则应立即发送 Path Response。如有必要,实现应采取措施对 Path Response 进行速率限制或限制所使用的带宽。
Path Challenge 块通常会伴随同一消息中的 Address 块。如果地址块包含新的 IP/端口,对等节点可以验证该 IP/端口,并对该新 IP/端口发起与会话对等节点或任何其他对等节点的对等测试。如果对等节点认为自己处于防火墙后面,而只有端口发生变化,这种变化可能是由于 NAT 重新绑定造成的,进一步的对等测试可能不是必需的。
Successful Path Validation
在成功完成路径验证后,连接将完全迁移到新的IP/端口。成功时:
- 退出路径验证阶段
- 所有数据包都发送到新的 IP 和端口。
- 移除对拥塞窗口和 PMTU 的限制,允许它们增加。不要简单地恢复到 旧值,因为新路径可能具有不同的特征。
- 如果 IP 发生变化,将计算出的 RTT 和 RTO 设置为初始值。 由于仅端口变化通常是 NAT 重新绑定或其他中间盒活动的结果, 在这些情况下,对等方可能会保留其拥塞控制状态和往返时间估计, 而不是恢复到初始值。
- 删除(使无效)为旧 IP/端口发送或接收的任何令牌(可选)
- 为新 IP/端口发送新的令牌块(可选)
Cancelling Path Validation
在路径验证阶段,任何从旧IP/端口接收到的有效、非重复数据包,如果成功解密,都会导致路径验证被取消。重要的是,由伪造数据包引起的已取消路径验证不应导致有效会话被终止或显著中断。
关于取消的路径验证:
- 退出路径验证阶段
- 所有数据包都发送到旧的IP和端口。
- 移除对拥塞窗口和PMTU的限制,允许它们增加,或者可选地恢复之前的值
- 将之前发送到新IP/端口的任何数据包重传到旧IP/端口。
Failed Path Validation
重要的是,由伪造数据包导致的路径验证失败不应导致有效会话被终止或受到严重干扰。
在路径验证失败时:
- 退出路径验证阶段
- 所有数据包发送到旧的 IP 和端口。
- 移除对拥塞窗口和 PMTU 的限制,允许它们增加。
- 可选择在旧的 IP 和端口上启动路径验证。 如果失败,则终止会话。
- 否则,遵循标准会话超时和终止规则。
- 重传之前发送到新 IP/端口的任何数据包 到旧 IP/端口。
Validation After Local Change
上述过程是为从变更的IP/端口接收数据包的节点定义的。但是,它也可能由检测到自己IP或端口已变更的节点在另一个方向上发起。节点可能能够检测到其本地IP已变更;但是,由于NAT重新绑定,检测到端口变更的可能性要小得多。因此,这是可选的。
当接收到来自IP或端口已更改的对等方的路径质询时,另一个对等方应该向相反方向发起路径质询。
中继安全
Path Validation 和 Path Response 块可以随时用作 Ping/Pong 数据包。接收 Path Validation 块不会改变接收方的任何状态,除非从不同的 IP/端口接收到。
Multiple Sessions
节点不应与同一对等方建立多个会话,无论是 SSU 1 还是 2,也不应与相同或不同的 IP 地址建立多个会话。但是,这种情况可能会发生,可能是由于错误、之前的会话终止消息丢失,或者在终止消息尚未到达的竞争条件下。
如果 Bob 与 Alice 已有现存会话,当 Bob 从 Alice 接收到 Session Confirmed 消息,完成握手并建立新会话时,Bob 应当:
- 将任何未发送或未确认的出站 I2NP 消息从旧会话迁移到新会话
- 在旧会话上发送带有原因代码 22 的终止消息
- 移除旧会话并用新会话替换它
Session Termination
对等节点测试安全性
握手阶段的会话通常通过超时或不再响应来简单地终止。可选地,它们可以通过在响应中包含终止块来终止,但由于缺乏加密密钥,大多数错误无法响应。即使有可用的密钥来生成包含终止块的响应,通常也不值得消耗CPU来执行响应的DH运算。一个例外可能是重试消息中的终止块,这种生成成本较低。
中继和对等测试设计目标
数据阶段的会话通过发送包含 Termination 块的数据消息来终止。此消息还应包含 ACK 块。如果会话持续时间足够长,使得之前发送的令牌已过期或即将过期,则可能包含 New Token 块。此消息不引发确认。当接收到包含除"Termination Received"之外任何原因的 Termination 块时,对等节点会响应一个包含原因为"Termination Received"的 Termination 块的数据消息。
发送或接收 Termination 块后,会话应进入关闭阶段一段最大时间(待定)。关闭状态是必要的,以防止包含 Termination 块的数据包丢失,以及处理另一方向的在途数据包。在关闭阶段中,不需要处理任何额外接收的数据包。处于关闭状态的会话会发送包含 Termination 块的数据包来响应它认为属于该会话的任何传入数据包。会话应限制其在关闭状态下生成数据包的速率。例如,会话可以等待逐渐增加数量的接收数据包或时间量,然后再响应接收的数据包。
为了最小化router在关闭会话时维护的状态,会话可以(但不是必须)发送与接收到的数据包具有相同packet number的完全相同的数据包作为响应。注意:允许重传终止数据包是对每个数据包必须使用新packet number要求的例外。发送新packet number主要有利于丢失恢复和拥塞控制,而这些对于已关闭的连接预期是不相关的。重传最终数据包需要更少的状态。
在收到带有"Termination Received"原因的Termination块后,会话可能会退出关闭阶段。
Cleanup
在任何正常或异常终止时,router 应当清零所有内存中的临时数据,包括握手临时密钥、对称加密密钥以及相关信息。
MTU
要求因发布地址是否与 SSU 1 共享而有所不同。当前 SSU 1 IPv4 最小值为 620,这显然太小了。
最小 SSU2 MTU 对于 IPv4 和 IPv6 都是 1280,这与 RFC 9000 中指定的相同。见下文。通过增加最小 MTU,1 KB tunnel 消息和短 tunnel 构建消息将适合一个数据报,大大减少了典型的分片数量。这也允许增加最大 I2NP 消息大小。1820 字节的流消息应该适合两个数据报。
除非该地址的 MTU 至少为 1280,否则 router 不得启用 SSU2 或发布 SSU2 地址。
路由器必须在每个 SSU 或 SSU2 路由器地址中发布非默认的 MTU。
摘要
与 SSU 1 共享地址,必须遵循 SSU 1 规则。IPv4:默认值和最大值为 1484。最小值为 1292。(IPv4 MTU + 4) 必须是 16 的倍数。IPv6:必须发布,最小值为 1280,最大值为 1488。IPv6 MTU 必须是 16 的倍数。
传递保证
IPv4:默认值和最大值为1500。最小值为1280。IPv6:默认值和最大值为1500。最小值为1280。没有16的倍数规则,但应该至少是2的倍数。
Noise Protocol Framework
对于 SSU 1,当前的 Java I2P 通过从小数据包开始并逐渐增加大小,或基于接收到的数据包大小来增加大小的方式执行 PMTU 发现。这种方法比较粗糙,大大降低了效率。在 SSU 2 中是否继续这一功能尚待确定。
最近的研究 PMTU 表明,IPv4 最小值为 1200 或更高时,超过 99% 的连接都能正常工作。QUIC RFC 9000 要求最小 IP 数据包大小为 1280 字节。
引用 RFC 9000:
最大数据报大小定义为可以使用单个 UDP 数据报在网络路径上发送的 UDP 负载的最大大小。如果网络路径不能支持至少 1200 字节的最大数据报大小,则不得使用 QUIC。
QUIC 假设 IP 数据包的最小大小至少为 1280 字节。这是 IPv6 的最小大小 [IPv6],也被大多数现代 IPv4 网络所支持。假设 IPv6 的最小 IP 头部大小为 40 字节,IPv4 为 20 字节,UDP 头部大小为 8 字节,这导致 IPv6 的最大数据报大小为 1232 字节,IPv4 为 1252 字节。因此,现代 IPv4 和所有 IPv6 网络路径都应该能够支持 QUIC。
注意:这个支持 1200 字节 UDP 负载的要求限制了可用于 IPv6 扩展头的空间为 32 字节,或者如果路径仅支持 IPv6 最小 MTU 1280 字节时,IPv4 选项的可用空间为 52 字节。这会影响初始数据包和路径验证。
引用结束
框架的扩展
QUIC 要求双向的初始数据报至少为 1200 字节,以防止放大攻击并确保 PMTU 在两个方向上都支持它。
我们可以在 Session Request 和 Session Created 中要求这样做,但会大幅增加带宽成本。也许我们可以仅在没有 token 时或收到 Retry 消息后才这样做。待定
QUIC要求Bob发送的数据量不得超过收到数据量的三倍,直到客户端地址得到验证。SSU2本质上满足了这一要求,因为Retry消息与Token Request消息大小大致相同,且小于Session Request消息。此外,Retry消息只发送一次。
处理开销估算
QUIC 要求包含 PATH_CHALLENGE 或 PATH_RESPONSE 块的消息至少为 1200 字节,以防止放大攻击,并确保 PMTU 在两个方向上都支持它。
我们也可以要求这样做,但会以大量带宽为代价。不过,这些情况应该很少见。待定
Max I2NP Message Size
IPv4:假设不进行IP分片。IP + 数据报头部为28字节。这假设没有IPv4选项。最大消息大小为MTU - 28。数据阶段头部为16字节,MAC为16字节,总计32字节。载荷大小为MTU - 60。对于最大1500 MTU,最大数据阶段载荷为1440。对于最小1280 MTU,最大数据阶段载荷为1220。
IPv6:不允许 IP 分片。IP + 数据报头部为 48 字节。这假设没有 IPv6 扩展头部。最大消息大小为 MTU - 48。数据阶段头部为 16 字节,MAC 为 16 字节,总计 32 字节。载荷大小为 MTU - 80。对于最大 1500 MTU,最大数据阶段载荷为 1420。对于最小 1280 MTU,最大数据阶段载荷为 1200。
在 SSU 1 中,基于 64 个最大片段和 620 最小 MTU,I2NP 消息的严格最大限制约为 32 KB。由于捆绑的 LeaseSets 和会话密钥的开销,应用程序级别的实际限制约低 6KB,即约 26KB。SSU 1 协议允许 128 个片段,但当前实现将其限制为 64 个片段。
通过将最小 MTU 提高到 1280,数据阶段载荷约为 1200,SSU 2 消息在 64 个分片中可达约 76 KB,在 128 个分片中可达 152 KB。这可以轻松支持最大 64 KB。
由于tunnel中的分片以及SSU 2中的分片,消息丢失的概率随着消息大小呈指数级增长。我们继续建议在应用层对I2NP数据报保持大约10 KB的实际限制。
Peer Test Process
请参阅上文的对等测试安全性部分,了解对 SSU1 Peer Test 的分析以及 SSU2 Peer Test 的目标。
Alice Bob Charlie
1. PeerTest ------------------->
Alice RI ------------------->
2. PeerTest ------------------->
3. <------------------ PeerTest
<---------------- Charlie RI
4. <------------------ PeerTest
5. <----------------------------------------- PeerTest
6. PeerTest ----------------------------------------->
7. <----------------------------------------- PeerTest
当被 Bob 拒绝时:
Alice Bob Charlie
1. PeerTest ------------------->
4. <------------------ PeerTest (reject)
当被 Charlie 拒绝时:
Alice Bob Charlie
1. PeerTest ------------------->
Alice RI ------------------->
2. PeerTest ------------------->
3. <------------------ PeerTest (reject)
(optional: Bob could try another Charlie here)
4. <------------------ PeerTest (reject)
注意:RI可以通过I2NP块中的I2NP Database Store消息发送,或者作为RI块发送(如果足够小)。如果足够小,这些可以与对等测试块包含在同一数据包中。
消息 1-4 是会话内的,在数据消息中使用 Peer Test 块。消息 5-7 是会话外的,在 Peer Test 消息中使用 Peer Test 块。
注意:与 SSU 1 一样,消息 4 和消息 5 可能以任意顺序到达。如果 Alice 位于防火墙后,消息 5 和/或消息 7 可能根本不会收到。当消息 5 在消息 4 之前到达时,Alice 无法立即发送消息 6,因为她还没有 Charlie 的 intro key 来加密头部。当消息 4 在消息 5 之前到达时,Alice 不应该立即发送消息 6,因为她应该等待看看消息 5 是否会到达,而不是通过发送消息 6 来打开防火墙。
| Message | Path | Intro Key |
|---|---|---|
| 1 | A->B session | in-session |
| 2 | B->C session | in-session |
| 3 | C->B session | in-session |
| 4 | B->A session | in-session |
| 5 | C->A | Alice |
| 6 | A->C | Charlie |
| 7 | C->A | Alice |
Versions
不支持跨版本对等测试。唯一允许的版本组合是所有对等节点都是版本2。
| Alice/Bob | Bob/Charlie | Alice/Charlie | Supported |
|---|---|---|---|
| 1 | 1 | 1 | SSU 1 |
| 1 | 1 | 2 | no, use 1/1/1 |
| 1 | 2 | 1 | no, Bob must s |
| 1 | 2 | 2 | no, Bob must s |
| 2 | 1 | 1 | no, Bob must s |
| 2 | 1 | 2 | no, Bob must s |
| 2 | 2 | 1 | no, use 2/2/2 |
| 2 | 2 | 2 | yes |
会话建立
消息 1-4 在会话中,由数据阶段 ACK 和重传过程覆盖。Peer Test 块是引发确认的。
消息 5-7 可能会重传,保持不变。
数据包头
与 SSU 1 一样,支持 IPv6 地址测试,如果 Bob 和 Charlie 在其发布的 IPv6 地址中使用 ‘B’ 能力标识表示支持,则 Alice-Bob 和 Alice-Charlie 通信可以通过 IPv6 进行。详情请参见提案 126。
如在 0.9.50 版本之前的 SSU 1 中一样,Alice 通过她希望测试的传输协议(IPv4 或 IPv6)上的现有会话向 Bob 发送请求。当 Bob 通过 IPv4 收到来自 Alice 的请求时,Bob 必须选择一个公布 IPv4 地址的 Charlie。当 Bob 通过 IPv6 收到来自 Alice 的请求时,Bob 必须选择一个公布 IPv6 地址的 Charlie。实际的 Bob-Charlie 通信可以通过 IPv4 或 IPv6 进行(即独立于 Alice 的地址类型)。这不是 0.9.50 版本 SSU 1 的行为,在该版本中允许混合 IPv4/v6 请求。
Processing by Bob
与 SSU 1 不同,Alice 在消息 1 中指定请求的测试 IP 和端口。Bob 应该验证此 IP 和端口,如果无效则以代码 5 拒绝。推荐的 IP 验证是,对于 IPv4,它应匹配 Alice 的 IP,对于 IPv6,IP 的至少前 8 个字节应匹配。端口验证应拒绝特权端口和知名协议端口。
Relay Process
有关 SSU1 Relay 的分析和 SSU2 Relay 的目标,请参阅上文的中继安全性部分。
Alice Bob Charlie
lookup Bob RI
SessionRequest -------------------->
<------------ SessionCreated
SessionConfirmed ----------------->
1. RelayRequest ---------------------->
Alice RI ------------>
2. RelayIntro ----------->
3. <-------------- RelayResponse
4. <-------------- RelayResponse
5. <-------------------------------------------- HolePunch
6. SessionRequest -------------------------------------------->
7. <-------------------------------------------- SessionCreated
8. SessionConfirmed ------------------------------------------>
当被Bob拒绝时:
Alice Bob Charlie
lookup Bob RI
SessionRequest -------------------->
<------------ SessionCreated
SessionConfirmed ----------------->
1. RelayRequest ---------------------->
4. <-------------- RelayResponse
当被 Charlie 拒绝时:
Alice Bob Charlie
lookup Bob RI
SessionRequest -------------------->
<------------ SessionCreated
SessionConfirmed ----------------->
1. RelayRequest ---------------------->
Alice RI ------------>
2. RelayIntro ----------->
3. <-------------- RelayResponse
4. <-------------- RelayResponse
注意:RI可以通过I2NP块中的I2NP Database Store消息发送,或者作为RI块发送(如果足够小的话)。如果足够小,这些可能包含在与中继块相同的数据包中。
在 SSU 1 中,Charlie 的 router info 包含每个 introducer 的 IP、端口、intro key、relay tag 和过期时间。
在 SSU 2 中,Charlie 的 router info 包含每个 introducer 的 router hash、relay tag 和过期时间。
Alice 应该首先选择她已经建立连接的介绍者 (Bob) 来减少所需的往返次数。其次,如果没有这样的介绍者,则选择她已经拥有 router 信息的介绍者。
如果可能,还应该支持跨版本中继。这将促进从 SSU 1 到 SSU 2 的平稳过渡。允许的版本组合有(待定):
| Alice/Bob | Bob/Charlie | Alice/Charlie | Supported |
|---|---|---|---|
| 1 | 1 | 1 | SSU 1 |
| 1 | 1 | 2 | no, use 1/1/1 |
| 1 | 2 | 1 | yes? |
| 1 | 2 | 2 | no, use 1/2/1 |
| 2 | 1 | 1 | yes? |
| 2 | 1 | 2 | yes? |
| 2 | 2 | 1 | no, use 2/2/2 |
| 2 | 2 | 2 | yes |
Retransmissions
Relay Request、Relay Intro 和 Relay Response 都在会话中,并且都受到数据阶段 ACK 和重传过程的保护。Relay Request、Relay Intro 和 Relay Response 块都会引发确认。
打洞可能会重传,如在 SSU 1 中一样。
IPv4/v6
支持 SSU 1 relay 的所有功能,包括 Proposal 158 中记录并自 0.9.50 版本起支持的功能。支持 IPv4 和 IPv6 介绍。可以通过 IPv4 会话发送 Relay Request 进行 IPv6 介绍,也可以通过 IPv6 会话发送 Relay Request 进行 IPv4 介绍。
Processing by Alice
以下是与 SSU 1 的差异以及 SSU 2 实现的建议。
注意事项
在 SSU 1 中,引入相对成本较低,Alice 通常会向所有引入者发送中继请求。在 SSU 2 中,引入成本更高,因为必须首先与引入者建立连接。为了最小化引入延迟和开销,推荐的处理步骤如下:
- 基于地址中的 iexp 值忽略任何已过期的介绍者
- 如果已经与一个或多个介绍者建立了 SSU2 连接, 选择其中一个并仅向该介绍者发送中继请求。
- 否则,如果本地已知一个或多个介绍者的 Router Info, 选择其中一个并仅连接到该介绍者。
- 否则,查找所有介绍者的 Router Info, 连接到最先收到 Router Info 的介绍者。
注意事项
在 SSU 1 和 SSU 2 中,中继响应和打洞可能以任意顺序接收,或者可能根本不会被接收到。
在 SSU 1 中,Alice 通常在 Hole Punch(1.5 RTT)之前收到 Relay Response(1 RTT)。这在那些规范中可能没有很好地记录,但 Alice 必须在继续之前从 Bob 收到 Relay Response,以获得 Charlie 的 IP。如果先收到 Hole Punch,Alice 将无法识别它,因为它不包含数据且源 IP 无法识别。收到 Relay Response 后,Alice 应该等待收到来自 Charlie 的 Hole Punch 或短暂延迟(建议 500 毫秒),然后再与 Charlie 启动握手。
在 SSU 2 中,Alice 通常会在接收到 Relay Response(2 RTT)之前先接收到 Hole Punch(1 1/2 RTT)。SSU 2 的 Hole Punch 比 SSU 1 更容易处理,因为它是一个完整的消息,具有定义的连接 ID(从 relay nonce 派生)和内容,包括 Charlie 的 IP。Relay Response(数据消息)和 Hole Punch 消息包含相同的已签名 Relay Response 块。因此,Alice 可以在接收到来自 Charlie 的 Hole Punch 或接收到来自 Bob 的 Relay Response 之后启动与 Charlie 的握手。
Hole Punch 的签名验证包括介绍者(Bob)的 router 哈希。如果 Relay 请求已发送给多个介绍者,有几种选项来验证签名:
- 尝试发送请求的每个哈希
- 为每个introducer使用不同的nonces,并使用它来确定此Hole Punch是对哪个introducer的响应
- 如果内容与已收到的Relay Response中的内容相同,则不要重新验证签名
- 完全不验证签名
如果 Charlie 位于对称 NAT 之后,他在 Relay Response 和 Hole Punch 中报告的端口可能不准确。因此,Alice 应该检查 Hole Punch 消息的 UDP 源端口,如果与报告的端口不同,则使用该端口。
Tag Requests by Bob
在 SSU 1 中,只有 Alice 可以在会话请求中请求标签。Bob 永远无法请求标签,Alice 也无法为 Bob 中继。
在 SSU2 中,Alice 通常在 Session Request 中请求一个标签,但 Alice 或 Bob 也可能在数据阶段请求标签。Bob 在收到入站请求后通常不会被防火墙阻挡,但在中继之后可能会被阻挡,或者 Bob 的状态可能发生变化,或者他可能为其他地址类型(IPv4/v6)请求引荐者。因此,在 SSU2 中,Alice 和 Bob 有可能同时充当对方的中继。
Published Router Info
Address Properties
以下地址属性可能会被发布,与 SSU 1 保持不变,包括 Proposal 158 中的变更(自 API 0.9.50 起支持):
caps: [B,C,4,6] 能力
host: IP (IPv4 或 IPv6)。 允许使用缩短的 IPv6 地址(带有"::")。 如果有防火墙,该字段可能存在也可能不存在。 不允许使用主机名。
iexp[0-2]: 此介绍节点的过期时间。 ASCII 数字,自纪元以来的秒数。 仅在有防火墙且需要介绍节点时存在。 可选(即使此介绍节点的其他属性存在)。
ihost[0-2]: 介绍者的IP地址(IPv4或IPv6)。 允许使用缩短的IPv6地址(带"::")。 仅在防火墙阻隔且需要介绍者时出现。 不允许使用主机名。 仅限SSU地址。
ikey[0-2]: Introducer 的 Base 64 introduction key。 仅在防火墙后且需要 introducers 时存在。 仅限 SSU 地址。
iport[0-2]: Introducer 的端口 1024 - 65535。 仅在防火墙阻挡且需要 introducer 时出现。 仅限 SSU 地址。
itag[0-2]: Introducer 的标签 1 - (2**32 - 1) ASCII 数字。 仅当处于防火墙后且需要 introducer 时才存在。
key: Base 64 介绍密钥。
mtu: 可选。请参见上面的 MTU 部分。
port: 1024 - 65535 如果处于防火墙后可能存在也可能不存在。
Published Addresses
发布的 RouterAddress(RouterInfo 的一部分)将具有 “SSU” 或 “SSU2” 的协议标识符。
RouterAddress必须包含三个选项来指示SSU2支持:
s=(Base64 key) 此RouterAddress的当前Noise静态公钥(s)。 使用标准I2P Base 64字母表进行Base 64编码。 二进制格式32字节,Base 64编码格式44字节, 小端序X25519公钥。
i=(Base64 key) 当前用于加密此 RouterAddress 标头的引入密钥。 使用标准 I2P Base 64 字母表进行 Base 64 编码。 二进制为 32 字节,Base 64 编码为 44 字节, 大端序 ChaCha20 密钥。
v=2 当前版本 (2)。 当发布为 “SSU” 时,隐含对版本 1 的额外支持。 对未来版本的支持将使用逗号分隔的值, 例如 v=2,3 实现应该验证兼容性,如果存在逗号则包括多个 版本。逗号分隔的版本必须按数字顺序排列。
Alice必须验证所有三个选项都存在且有效,然后才能使用SSU2协议进行连接。
当发布为带有 “s”、“i” 和 “v” 选项的 “SSU”,并带有 “host” 和 “port” 选项时,router 必须在该主机和端口上接受 SSU 和 SSU2 协议的传入连接,并自动检测协议版本。
当以"SSU2"发布时,带有"s"、“i"和"v"选项,以及"host"和"port"选项,路由器仅接受该主机和端口上针对SSU2协议的传入连接。
如果路由器同时支持 SSU1 和 SSU2 连接,但没有实现传入连接的自动版本检测,则必须同时公布 “SSU” 和 “SSU2” 地址,并且仅在 “SSU2” 地址中包含 SSU2 选项。路由器应该在 “SSU2” 地址中设置较低的成本值(更高的优先级),而不是在 “SSU” 地址中,以便优先选择 SSU2。
如果在同一个RouterInfo中发布了多个SSU2 RouterAddresses(作为"SSU"或"SSU2”),用于额外的IP地址或端口,所有指定相同端口的地址必须包含相同的SSU2选项和值。特别是,所有地址都必须包含相同的静态密钥"s"和介绍密钥"i"。
Introducers
当以带有引荐者的 SSU 或 SSU2 发布时,存在以下选项:
ih[0-2]=(Base64 hash) introducer 的 router hash。 使用标准 I2P Base 64 字母表进行 Base 64 编码。 二进制格式为 32 字节,Base 64 编码格式为 44 字节
iexp[0-2]: 这个介绍者的过期时间。 与 SSU 1 保持不变。
itag[0-2]: Introducer 的标签 1 - (2**32 - 1) 与 SSU 1 相比无变化。
以下选项仅适用于 SSU,不用于 SSU2。在 SSU2 中,Alice 从 Charlie 的 RI 中获取此信息。
- ihost[0-2]
- ikey[0-2]
- itag[0-2]
router 在发布 introducers 时不得在地址中发布主机或端口。router 在发布 introducers 时必须在地址中发布 4 和/或 6 caps 以表明对 IPv4 和/或 IPv6 的支持。这与当前 SSU 1 地址的做法相同。
注意:如果以 SSU 发布,并且存在 SSU 1 和 SSU2 introducer 的混合,为了与较旧的 router 兼容,SSU 1 introducer 应该位于较低的索引,SSU2 introducer 应该位于较高的索引。
Unpublished SSU2 Address
如果 Alice 没有发布她的 SSU2 地址(作为 “SSU” 或 “SSU2”)用于接收连接,她必须发布一个只包含她的静态密钥和 SSU2 版本的 “SSU2” router 地址,这样 Bob 就可以在 Session Confirmed 第 2 部分中接收到 Alice 的 RouterInfo 后验证该密钥。
s=(Base64 key) 如上述已发布地址的定义。
i=(Base64 key) 如上所述,用于已发布地址。
v=2 如上所述,适用于已发布的地址。
此路由器地址将不包含"host"或"port"选项,因为出站 SSU2 连接不需要这些选项。此地址发布的成本并不严格重要,因为它仅用于入站连接;但是,如果成本设置得比其他地址更高(优先级更低),可能对其他路由器有帮助。建议值为 14。
Alice 也可以简单地将 “i” “s” 和 “v” 选项添加到现有的已发布 “SSU” 地址中。
数据包完整性
允许为 NTCP2 和 SSU2 使用相同的静态密钥,但不建议这样做。
由于RouterInfo的缓存机制,router在运行期间不得轮换静态公钥或IV,无论是否在已发布的地址中。Router必须持久存储此密钥和IV以便在立即重启后重用,这样传入连接将继续工作,且重启时间不会被暴露。Router必须持久存储或以其他方式确定上次关闭时间,以便在启动时计算之前的停机时间。
考虑到暴露重启时间的担忧,如果 router 之前宕机了一段时间(至少几天),router 可能会在启动时轮换此密钥或 IV。
如果 router 有任何已发布的 SSU2 RouterAddresses(作为 SSU 或 SSU2),轮换前的最小停机时间应该更长,例如一个月,除非本地 IP 地址已更改或 router “重新生成密钥”。
如果 router 有任何已发布的 SSU RouterAddresses,但没有 SSU2(作为 SSU 或 SSU2),轮换前的最小停机时间应该更长,例如一天,除非本地 IP 地址已更改或 router “rekeys”。即使已发布的 SSU 地址有 introducers,这也适用。
如果router没有任何已发布的RouterAddresses(SSU、SSU2或SSU),那么轮换前的最短停机时间可能只有两小时,即使IP地址发生变化,除非router进行"rekeys"。
如果 router “重新生成密钥"到不同的 Router Hash,它也应该生成新的 noise 密钥和 intro 密钥。
实现必须注意,更改静态公钥或 IV 将阻止来自已缓存旧 RouterInfo 的路由器的传入 SSU2 连接。RouterInfo 发布、隧道对等体选择(包括 OBGW 和 IB 最近跳)、零跳隧道选择、传输选择以及其他实现策略都必须考虑到这一点。
Intro 密钥轮换遵循与密钥轮换相同的规则。
注意:重新生成密钥前的最短停机时间可能会被修改以确保网络健康,并防止因 router 中等时长停机而触发重新播种。
Identity Hiding
否认性不是目标。见上述概述。
每个模式都被分配了描述对发起方静态公钥和响应方静态公钥提供的机密性的属性。基本假设是临时私钥是安全的,如果参与方收到来自另一方的不信任的静态公钥,则会中止握手。
本节仅考虑通过握手中静态公钥字段的身份泄露。当然,Noise参与者的身份可能会通过其他方式暴露,包括载荷字段、流量分析或诸如IP地址等元数据。
Alice:(8) 使用前向保密加密发送给已认证的一方。
Bob:(3) 不传输,但被动攻击者可以检查响应者私钥的候选值并确定候选值是否正确。
Bob 在 netDb 中发布他的静态公钥。Alice 可能不会这样做,但必须在发送给 Bob 的 RI 中包含该公钥。
Packet Guidelines
认证加密
握手消息(会话请求/已创建/已确认,重试)基本步骤,按顺序:
- 创建 16 或 32 字节头部
- 创建负载
- mixHash() 头部(Retry 除外)
- 使用 Noise 加密负载(Retry 除外,使用 ChaChaPoly 并以头部作为 AD)
- 加密头部,对于 Session Request/Created,还需加密临时密钥
数据阶段消息的基本步骤,按顺序:
- 创建 16 字节头部
- 创建载荷
- 使用 ChaChaPoly 加密载荷,将头部作为 AD
- 加密头部
Inbound Packet Handling
负载
所有入站消息的初始处理:
- 使用介绍密钥解密头部的前8个字节(目标连接ID)
- 通过目标连接ID查找连接
- 如果找到连接且处于数据阶段,则进入数据阶段部分
- 如果未找到连接,则进入握手部分
- 注意:Peer Test和Hole Punch消息也可能通过从测试或中继nonce创建的目标连接ID进行查找。
握手消息(Session Request/Created/Confirmed、Retry、Token Request)和其他会话外消息(Peer Test、Hole Punch)处理:
- 使用引入密钥解密头部的第8-15字节 (数据包类型、版本和网络ID)。如果这是一个 有效的Session Request、Token Request、Peer Test或Hole Punch,继续执行
- 如果不是有效消息,通过数据包源IP/端口查找待处理的出站连接,将数据包视为Session Created或Retry。 使用正确的密钥重新解密头部的前8字节, 以及头部的第8-15字节 (数据包类型、版本和网络ID)。如果这是一个 有效的Session Created或Retry,继续执行
- 如果不是有效消息,则失败,或作为可能的乱序数据阶段数据包排队
- 对于Session Request/Created、Retry、Token Request、Peer Test和Hole Punch,解密头部的第16-31字节
- 对于Session Request/Created,解密临时密钥
- 验证所有头部字段,如果无效则停止
- mixHash()头部
- 对于Session Request/Created/Confirmed,使用Noise解密载荷
- 对于Retry和数据阶段,使用ChaChaPoly解密载荷
- 处理头部和载荷
数据阶段消息处理:
- 使用正确的密钥解密头部的第8-15字节 (数据包类型、版本和网络ID)
- 使用ChaChaPoly解密载荷,将头部作为AD
- 处理头部和载荷
Details
在 SSU 1 中,入站数据包分类很困难,因为没有头部来指示会话编号。路由器必须首先将源 IP 和端口与现有的对等体状态进行匹配,如果未找到,则尝试使用不同密钥进行多次解密来找到适当的对等体状态或启动新的状态。如果现有会话的源 IP 或端口发生变化(可能由于 NAT 行为),路由器可能使用昂贵的启发式方法来尝试将数据包与现有会话匹配并恢复内容。
SSU 2 旨在最小化入站数据包分类工作,同时保持对 DPI 的抗性和其他路径上的威胁。连接 ID 号码包含在所有消息类型的头部中,并使用已知密钥和随机数通过 ChaCha20 进行加密(混淆)。此外,消息类型也包含在头部中(通过头部保护加密到已知密钥,然后用 ChaCha20 混淆),可用于额外的分类。在任何情况下都不需要试探性 DH 或其他非对称加密操作来分类数据包。
对于来自所有节点的几乎所有消息,用于连接ID加密的ChaCha20密钥是目标router在netDb中发布的引入密钥。
唯一的例外是Bob发送给Alice的第一条消息(Session Created或Retry),此时Bob还不知道Alice的introduction key。在这些情况下,Bob的introduction key被用作密钥。
该协议旨在最小化可能需要在多个回退步骤中进行额外加密操作或复杂启发式算法的数据包分类处理。此外,绝大多数接收到的数据包不需要通过源IP/端口进行(可能代价昂贵的)回退查找和第二次头部解密。只有Session Created和Retry(以及可能的其他待定类型)需要回退处理。如果端点在会话创建后更改IP或端口,连接ID仍然用于查找会话。永远不需要使用启发式算法来查找会话,例如通过寻找具有相同IP但不同端口的不同会话。
因此,接收器循环逻辑中推荐的处理步骤是:
- 使用本地介绍密钥通过 ChaCha20 解密前 8 个字节,以恢复目标连接 ID。如果连接 ID 与当前或待处理的入站会话匹配:
a) 使用适当的密钥,解密头部字节 8-15
to recover the version, net ID, and message type.
b) 如果消息类型是 Session Confirmed,则为长报头。
Verify the net ID and protocol version are valid.
Decrypt the bytes 15-31 of the header with ChaCha20
using the local intro key. Then MixHash() the
decrypted 32 byte header and decrypt the message with Noise.
c) 如果消息类型有效但不是 Session Confirmed,
it is a short header.
Verify the net ID and protocol version are valid.
decrypt the rest of the message with ChaCha20/Poly1305
using the session key, using the decrypted 16-byte header
as the AD.
d) (可选) 如果连接 ID 是待处理的入站会话
awaiting a Session Confirmed message,
but the net ID, protocol, or message type is not valid,
it could be a Data message received out-of-order before the
Session Confirmed, so the data phase header protection keys are not yet known,
and the header bytes 8-15 were incorrectly decrypted.
Queue the message, and attempt to decrypt it once the
Session Confirmed message is received.
e) 如果 b) 或 c) 失败,丢弃该消息。
- 如果连接 ID 与当前会话不匹配:检查字节 8-15 处的明文头部是否有效(无需执行任何头部保护操作)。验证网络 ID 和协议版本是否有效,以及消息类型是否为会话请求或其他允许的会话外消息类型(待定)。
a) 如果一切都有效且消息类型是 Session Request,
decrypt bytes 16-31 of the header and the 32-byte X value
with ChaCha20 using the local intro key.
- 如果头部字节 24-31 处的令牌被接受, 则对解密的 32 字节头部进行 MixHash(), 并使用 Noise 解密消息。 发送 Session Created 作为响应。
- 如果令牌未被接受,向源 IP/端口发送带有令牌的 Retry 消息。 不要尝试使用 Noise 解密消息以避免 DDoS 攻击。
b) 如果消息类型是其他某种有效的消息
out-of-session, presumably with a short header,
decrypt the rest of the message with ChaCha20/Poly1305
using the intro key, and using the decrypted 16-byte header
as the AD. Process the message.
c) 如果 a) 或 b) 失败,则转到步骤 3)
- 通过数据包的源IP/端口查找待处理的出站会话。
a) 如果找到,使用 Bob 的 introduction key 通过 ChaCha20 重新解密前 8 个字节
to recover the Destination Connection ID.
b) 如果连接ID与待处理会话匹配:
Using the correct key, decrypt bytes 8-15 of the header
to recover the version, net ID, and message type.
Verify the net ID and protocol version are valid, and
the message type is Session Created or Retry, or other message type
allowed out-of-session (TBD).
如果所有内容都有效且消息类型为 Session Created, 使用 Bob 的 intro key 通过 ChaCha20 解密头部的后续 16 字节和 32 字节的 Y 值。 然后对解密的 32 字节头部执行 MixHash(), 并使用 Noise 解密消息。 响应发送 Session Confirmed。
- 如果所有内容都有效且消息类型为 Retry, 使用 Bob 的 intro key 通过 ChaCha20 解密头部的 16-31 字节。 使用 ChaCha20/Poly1305 解密并验证消息,其中 TBD 作为密钥,TBD 作为 nonce,解密的 32 字节头部作为 AD。 响应重新发送带有接收到的 token 的 Session Request。
- 如果消息类型是其他在会话外有效的消息, 推测是短头部, 使用 intro key 通过 ChaCha20/Poly1305 解密消息的其余部分, 并使用解密的 16 字节头部作为 AD。处理该消息。
c) If a pending outbound session is not found, or the connection ID does not match the pending session, drop the message, unless the port is shared with SSU 1.
- 如果在同一端口上运行 SSU 1,尝试将消息作为 SSU 1 数据包处理。
Error Handling
一般来说,会话(无论处于握手阶段还是数据阶段)在收到包含意外消息类型的数据包后都不应被销毁。这可以防止数据包注入攻击。这些数据包通常也会在握手数据包重传后收到,此时头部解密密钥已不再有效。
在大多数情况下,只需丢弃数据包。实现可以(但不是必须)重传之前发送的数据包(握手消息或 ACK 0)作为响应。
作为Bob发送Session Created后,意外的数据包通常是由于Session Confirmed数据包丢失或乱序而无法解密的Data数据包。将这些数据包排队,并在接收到Session Confirmed数据包后尝试解密它们。
作为 Bob 接收到 Session Confirmed 后,意外数据包通常是重传的 Session Confirmed 数据包,因为 Session Confirmed 的 ACK 0 丢失了。意外数据包可能会被丢弃。实现可以(但不是必需的)发送包含 ACK 块的 Data 数据包作为响应。
Notes
对于 Session Created 和 Session Confirmed,实现必须仔细验证所有解密的头部字段(Connection ID、包序号、包类型、版本、id、frag 和 flags),然后才能对头部调用 mixHash() 并尝试使用 Noise AEAD 解密载荷。如果 Noise AEAD 解密失败,则不得进行进一步处理,因为 mixHash() 会破坏握手状态,除非实现存储并"回退"哈希状态。
Version Detection
在同一个入站端口上可能无法有效检测传入数据包是版本1还是版本2。上述步骤可能应该在SSU 1处理之前执行,以避免使用两种协议版本尝试试验性DH操作。
如有需要,待定。
Recommended Constants
- 出站握手重传超时:1.25秒,采用指数退避 (重传时间为1.25、3.75和8.75秒)
- 出站握手总超时:15秒
- 入站握手重传超时:1秒,采用指数退避 (重传时间为1、3和7秒)
- 入站握手总超时:12秒
- 发送重试后超时:9秒
- ACK延迟:max(10, min(rtt/6, 150)) ms
- 立即ACK延迟:min(rtt/16, 5) ms
- 最大ACK范围:256?
- 最大ACK深度:512?
- 填充分布:0-15字节,或更大
Variants, Fallbacks, and General Issues
待定
Packet Overhead Analysis
假设使用 IPv4,不包含额外填充,不包含 IP 和 UDP 头部大小。填充是仅适用于 SSU 1 的 mod-16 填充。
SSU 1
| Message | Header+MAC | Keys | Data | Padding | Total | Notes |
|---|---|---|---|---|---|---|
| Session Request | 40 | 256 | 5 | 3 | 304 | Incl. |
| Session Created | 37 | 256 | 79 | 1 | 336 | Incl. |
| Session Confirmed | 37 | 462 | 13 | 512 | Incl. | |
| Data (RI) | 37 | 1014 | 1051 | Incl. | ||
| Data (1 full msg) | 37 | 14 | 51 | Incl. | ||
| Total | 2254 | |||||
| SSU 2 |
| Message | Header+MACs | Keys | Data | Padding | Total | Notes |
|---|---|---|---|---|---|---|
| Session Request | 48 | 32 | 7 | 87 | DateTi | |
| Session Created | 48 | 32 | 16 | 96 | DateTi | |
| Session Confirmed | 48 | 32 | 1005 | 1085 | 1000 b | |
| Data (1 full msg) | 32 | 14 | 46 | |||
| Total | 1314 | |||||
| 待办事项:除非在 Session Request 和 Created 中强制执行最小数据包大小以支持 PMTU。 |