TCP 认证选项的 Linux 实现 (RFC5925)

TCP 认证选项 (TCP-AO) 提供了一个 TCP 扩展,旨在验证受信任对等方之间的报文段。它添加了一个新的 TCP 头部选项,其中包含消息认证码 (MAC)。MAC 由 TCP 报文段的内容生成,使用一种与两个对等方均知的密码的哈希函数。TCP-AO 的目的是弃用 TCP-MD5,提供更好的安全性、密钥轮换和对各种哈希算法的支持。

1. 介绍

TCP-AO 和 TCP-MD5 的简短有限比较

TCP-MD5

TCP-AO

支持的哈希算法

MD5(加密弱点)

必须支持 HMAC-SHA1(选择前缀攻击)和 CMAC-AES-128(仅侧信道攻击)。可以支持任何哈希算法。

MAC 长度(字节)

16

通常 12-16。允许适合 TCP 头部的其他变体。

每个 TCP 连接的密钥数量

1

许多

更改活动密钥的可能性

不切实际(两个对等方必须在 MSL 期间更改)

协议支持

抵御 ICMP “硬错误” 的保护

是:在已建立的连接上默认忽略它们

抵御流量交叉攻击的保护

是:伪头部包含 TCP 端口。

抵御重放 TCP 报文段的保护

序列号扩展 (SNE) 和初始序列号 (ISN)

支持无连接重置

否。需要 ISN+SNE 才能正确签署 RST。

标准

RFC 2385

RFC 5925, RFC 5926

1.1 常见问题 (FAQ) 及 RFC 5925 引用

问:对于相同的 4 元组(源地址、源端口、目的地址、目的端口),SendID 或 RecvID 是否可以是唯一的?

答:否 [3.1]

>> The IDs of MKTs MUST NOT overlap where their TCP connection
identifiers overlap.

问:活动连接的主密钥元组 (MKT) 可以删除吗?

答:否,除非它被复制到传输控制块 (TCB) [3.1]

It is presumed that an MKT affecting a particular connection cannot
be destroyed during an active connection -- or, equivalently, that
its parameters are copied to an area local to the connection (i.e.,
instantiated) and so changes would affect only new connections.

问:如果需要删除一个旧的 MKT,应该如何操作才能不删除活动连接的 MKT?(因为它可能在任何时候被使用)

答:RFC 5925 未指定,这似乎是密钥管理的问题,需确保在尝试删除之前没有人使用此类 MKT。

问:旧的 MKT 可以永远存在并被另一个对等方使用吗?

答:可以,何时删除旧密钥是密钥管理的任务 [6.1]

Deciding when to start using a key is a performance issue. Deciding
when to remove an MKT is a security issue. Invalid MKTs are expected
to be removed. TCP-AO provides no mechanism to coordinate their removal,
as we consider this a key management operation.

另请参见 [6.1]

The only way to avoid reuse of previously used MKTs is to remove the MKT
when it is no longer considered permitted.

Linux TCP-AO 将尽力阻止您删除正在使用的密钥,认为这是密钥管理失败。但由于保留过时的密钥可能会成为安全问题,并且对等方可能会通过始终将其设置为 RNextKeyID 而无意中阻止旧密钥的删除——因此提供了强制密钥删除机制,用户空间必须提供要使用的 KeyID 来替代正在删除的 KeyID,内核将原子地删除旧密钥,即使对等方仍在请求它。强制删除不作任何保证,因为对等方可能尚未拥有新密钥——TCP 连接可能会因此中断。或者,可以选择关闭套接字。

问:当新连接收到没有已知 MKT 的 RecvID 的数据包时会发生什么?

答:RFC 5925 规定默认情况下会接受并记录警告,但行为可由用户配置 [7.5.1.a]

If the segment is a SYN, then this is the first segment of a new
connection. Find the matching MKT for this segment, using the segment's
socket pair and its TCP-AO KeyID, matched against the MKT's TCP connection
identifier and the MKT's RecvID.

   i. If there is no matching MKT, remove TCP-AO from the segment.
      Proceed with further TCP handling of the segment.
      NOTE: this presumes that connections that do not match any MKT
      should be silently accepted, as noted in Section 7.3.

[7.3]:

>> A TCP-AO implementation MUST allow for configuration of the behavior
of segments with TCP-AO but that do not match an MKT. The initial default
of this configuration SHOULD be to silently accept such connections.
If this is not the desired case, an MKT can be included to match such
connections, or the connection can indicate that TCP-AO is required.
Alternately, the configuration can be changed to discard segments with
the AO option not matching an MKT.

[10.2.b]

Connections not matching any MKT do not require TCP-AO. Further, incoming
segments with TCP-AO are not discarded solely because they include
the option, provided they do not match any MKT.

请注意,Linux TCP-AO 实现在这方面有所不同。目前,具有未知密钥签名的 TCP-AO 报文段会被丢弃并记录警告。

问:RFC 是否以任何方式暗示集中式内核密钥管理?(即所有连接上的密钥必须同时轮换吗?)

答:未指定。MKT 可以在用户空间中管理,与密钥更改相关的唯一部分是 [7.3]

>> All TCP segments MUST be checked against the set of MKTs for matching
TCP connection identifiers.

问:当对等方请求的 RNextKeyID 未知时会发生什么?连接应该重置吗?

答:不应该,无需执行任何操作 [7.5.2.e]

ii. If they differ, determine whether the RNextKeyID MKT is ready.

    1. If the MKT corresponding to the segment’s socket pair and RNextKeyID
    is not available, no action is required (RNextKeyID of a received
    segment needs to match the MKT’s SendID).

问:current_key 是如何设置的,何时更改?是用户触发的更改,还是远程对等方的请求触发的?是用户显式设置的,还是通过匹配规则设置的?

答:current_key 由 RNextKeyID 设置 [6.1]

Rnext_key is changed only by manual user intervention or MKT management
protocol operation. It is not manipulated by TCP-AO. Current_key is updated
by TCP-AO when processing received TCP segments as discussed in the segment
processing description in Section 7.5. Note that the algorithm allows
the current_key to change to a new MKT, then change back to a previously
used MKT (known as "backing up"). This can occur during an MKT change when
segments are received out of order, and is considered a feature of TCP-AO,
because reordering does not result in drops.

[7.5.2.e.ii]

2. If the matching MKT corresponding to the segment’s socket pair and
RNextKeyID is available:

   a. Set current_key to the RNextKeyID MKT.

问:如果两个对等方都有多个 MKT 匹配连接的套接字对(具有不同的 KeyID),发送方/接收方应如何选择要使用的 KeyID?

答:某种机制应该选择“所需”的 MKT [3.3]

Multiple MKTs may match a single outgoing segment, e.g., when MKTs
are being changed. Those MKTs cannot have conflicting IDs (as noted
elsewhere), and some mechanism must determine which MKT to use for each
given outgoing segment.

>> An outgoing TCP segment MUST match at most one desired MKT, indicated
by the segment’s socket pair. The segment MAY match multiple MKTs, provided
that exactly one MKT is indicated as desired. Other information in
the segment MAY be used to determine the desired MKT when multiple MKTs
match; such information MUST NOT include values in any TCP option fields.

问:TCP-MD5 连接可以迁移到 TCP-AO 吗(反之亦然)?

答:否 [1]

TCP MD5-protected connections cannot be migrated to TCP-AO because TCP MD5
does not support any changes to a connection’s security algorithm
once established.

问:如果连接上所有 MKT 都被删除,它还能成为非 TCP-AO 签名的连接吗?

答:[7.5.2] 没有 [7.5.1.i] 中 SYN 数据包处理的相同选择,该选择允许接受没有签名的报文段(这将是不安全的)。虽然未直接禁止切换到非 TCP-AO 连接,但这似乎是 RFC 的意思。此外,TCP-AO 连接要求始终有一个 current_key [3.3]

TCP-AO requires that every protected TCP segment match exactly one MKT.

[3.3]:

>> An incoming TCP segment including TCP-AO MUST match exactly one MKT,
indicated solely by the segment’s socket pair and its TCP-AO KeyID.

[4.4]:

One or more MKTs. These are the MKTs that match this connection’s
socket pair.

问:非 TCP-AO 连接可以成为支持 TCP-AO 的连接吗?

答:否:对于已经建立的非 TCP-AO 连接,不可能切换到使用 TCP-AO,因为流量密钥生成需要初始序列号。换句话说,开始使用 TCP-AO 需要重新建立 TCP 连接。

2. 内核中 MKTs 数据库与用户空间数据库的比较

Linux TCP-AO 支持使用 setsockopt()s 实现,类似于 TCP-MD5。这意味着想要使用 TCP-AO 的用户空间应用程序在添加、删除或轮换 MKT 时,应该在 TCP 套接字上执行 setsockopt()。这种方法将密钥管理责任以及对边缘情况的决策(例如,如果对等方不遵守 RNextKeyID,该怎么办)转移到用户空间;将更多代码移到用户空间,特别是负责策略决策的代码。此外,它灵活且可扩展性好(比内核内数据库所需的锁定更少)。还需要记住,主要目标用户是 BGP 进程,而不是任何随机应用程序,这意味着与 IPsec 隧道相比,不需要真正的透明性,并且现代 BGP 守护程序已经支持 TCP-MD5 的 setsockopt()s

所考虑方法的优缺点

setsockopt()

内核内数据库 (DB)

可扩展性

setsockopt() 命令应是可扩展的系统调用

Netlink 消息简单且可扩展

所需的用户空间更改

BGP 或任何需要 TCP-AO 的应用程序需要执行 setsockopt()s 并进行密钥管理

可以像隧道一样透明,提供类似 ip tcpao add key(删除/显示/轮换)的功能

MKTs 删除或添加

用户空间更难

内核更难

可转储性

getsockopt()

Netlink .dump() 回调

内核资源/内存限制

相等

可伸缩性

TCP_LISTEN 套接字上的争用

整个数据库上的争用

监控和警告

TCP_DIAG

相同的 Netlink 套接字

MKTs 匹配

一半问题:只有监听套接字

3. 用户应用程序编程接口 (uAPI)

Linux 提供了一组 setsockopt()sgetsockopt()s,允许用户空间按套接字管理 TCP-AO。为了添加/删除 MKT,必须使用 TCP_AO_ADD_KEYTCP_AO_DEL_KEY TCP 套接字选项。不允许在已建立的非 TCP-AO 连接上添加密钥,也不允许从 TCP-AO 连接中删除最后一个密钥。

setsockopt(TCP_AO_DEL_KEY) 命令可以指定 tcp_ao_del::current_key + tcp_ao_del::set_current 和/或 tcp_ao_del::rnext + tcp_ao_del::set_rnext,这将使删除变为“强制”:它为用户空间提供了一种删除正在使用的密钥并原子地设置另一个密钥的方法。这并非用于正常使用,仅当对等方忽略 RNextKeyID 并持续请求/使用旧密钥时才应使用。它提供了一种强制删除不受信任密钥的方式,但这可能会中断 TCP-AO 连接。

常规/正常密钥轮换可以使用 setsockopt(TCP_AO_INFO) 执行。它还提供了一个 uAPI 来更改每个套接字的 TCP-AO 设置,例如忽略 ICMP,以及清除每个套接字的 TCP-AO 数据包计数器。相应的 getsockopt(TCP_AO_INFO) 可用于获取这些每个套接字的 TCP-AO 设置。

另一个有用的命令是 getsockopt(TCP_AO_GET_KEYS)。可以使用它列出 TCP 套接字上的所有 MKT,或者使用过滤器获取特定对等方和/或 sndid/rcvid、VRF L3 接口的密钥,或获取 current_key/rnext_key。

要修复 TCP-AO 连接,可以使用 setsockopt(TCP_AO_REPAIR),前提是用户之前使用 getsockopt(TCP_AO_REPAIR) 检查点/转储了套接字。

对于数千个 TCP-AO 密钥的扩展 TCP_LISTEN 套接字,这里有一个提示:在 getsockopt(TCP_AO_GET_KEYS) 中使用过滤器,并使用 setsockopt(TCP_AO_DEL_KEY) 进行异步删除。

Linux TCP-AO 还提供了一堆报文段计数器,有助于故障排除/调试问题。每个 MKT 都有好/坏计数器,反映有多少数据包通过/未通过验证。每个 TCP-AO 套接字都有以下计数器: - 用于良好报文段(正确签名) - 用于不良报文段(TCP-AO 验证失败) - 用于未知密钥的报文段 - 用于期望 AO 签名但未找到签名的报文段 - 用于被忽略的 ICMP 数量

TCP-AO 的每个套接字计数器也与每个网络命名空间的计数器重复,通过 SNMP 公开。这些是 TCPAOGoodTCPAOBadTCPAOKeyNotFoundTCPAORequiredTCPAODroppedIcmps

出于监控目的,有以下 TCP-AO 跟踪事件:tcp_hash_bad_headertcp_hash_ao_requiredtcp_ao_handshake_failuretcp_ao_wrong_maclentcp_ao_wrong_maclentcp_ao_key_not_foundtcp_ao_rnext_requesttcp_ao_synack_no_keytcp_ao_snd_sne_updatetcp_ao_rcv_sne_update。可以单独启用其中任何一个,并且可以按网络命名空间、4 元组、家族、L3 索引和 TCP 头部标志进行过滤。如果报文段有 TCP-AO 头部,过滤器还可以包含 keyid、rnext 和 maclen。SNE 更新包括翻转后的号码。

RFC 5925 非常宽松地规定了 MKTs 如何进行 TCP 端口匹配。

TCP connection identifier. A TCP socket pair, i.e., a local IP
address, a remote IP address, a TCP local port, and a TCP remote port.
Values can be partially specified using ranges (e.g., 2-30), masks
(e.g., 0xF0), wildcards (e.g., "*"), or any other suitable indication.

目前 Linux TCP-AO 实现不提供任何 TCP 端口匹配。可能,端口范围对于 uAPI 来说最灵活,但到目前为止尚未实现。

4. setsockopt()accept() 竞态

与只有一个密钥的已建立 TCP-MD5 连接不同,TCP-AO 连接可以有多个密钥,这意味着监听套接字上接受的连接也可能有任意数量的密钥。由于在第一个正确签名的 SYN 上复制所有这些密钥会使请求套接字变大,这是不可取的。目前,实现不会将密钥复制到请求套接字,而是从“父”监听套接字中查找它们。

结果是,当用户空间删除 TCP-AO 密钥时,这可能会破坏请求套接字上尚未建立的连接,并且不会从已建立但尚未 accept() 的套接字中删除密钥,使其挂在接受队列中。

反之亦然:如果用户空间在监听套接字上为对等方添加新密钥,则接受队列中已建立的套接字将不会拥有新密钥。

目前,解决这两个竞态问题:setsockopt(TCP_AO_ADD_KEY)accept() 以及 setsockopt(TCP_AO_DEL_KEY)accept() 的方法是委托给用户空间。这意味着用户空间应检查由 accept() 返回的套接字上的 MKT,以验证监听套接字上发生的任何密钥轮换是否反映在新建立的连接上。

这类似于内核方面对 TCP-MD5 的“不作为”方法,并且可能稍后通过引入新标志到 tcp_ao_addtcp_ao_del 来改变。

请注意,这种竞态很少发生,因为它需要在新 TCP 连接的三次握手期间发生 TCP-AO 密钥轮换。

5. 与 TCP-MD5 的交互

TCP 连接不能在 TCP-AO 和 TCP-MD5 选项之间迁移。已建立的、具有 AO 或 MD5 密钥的套接字被限制添加其他选项的密钥。

对于监听套接字,情况有所不同:BGP 服务器可能希望同时接收 TCP-AO 和(已弃用)TCP-MD5 客户端。因此,两种类型的密钥都可以添加到 TCP_CLOSED 或 TCP_LISTEN 套接字。不允许为同一对等方添加不同类型的密钥。

6. SNE Linux 实现

RFC 5925 [6.2] 描述了如何使用 SNE 扩展 TCP 序列号的算法。简而言之:TCP 必须跟踪以前的序列号,并在当前 SEQ 号翻转时设置 sne_flag。当当前和以前的 SEQ 号都超过 0x7fff(即 32KB)时,该标志被清除。

在 sne_flag 设置期间,算法会将每个数据包的 SEQ 号与 0x7fff 进行比较,如果高于 32KB,则假定该数据包在递增之前应使用 SNE 进行验证。结果,存在一个 [0; 32KB] 窗口,当数据包带有 (SNE - 1) 时可以接受。

Linux 实现对此做了一些简化:由于网络堆栈已经跟踪了所需 ACK 的第一个 SEQ 字节 (snd_una) 和所需下一个 SEQ 字节 (rcv_nxt)——这些信息足以粗略估计发送方和接收方在 4GB SEQ 号空间中的位置。当它们翻转为零时,相应的 SNE 会递增。

每个 TCP-AO 段都会调用 tcp_ao_compute_sne()。它将段中的 SEQ 号与 snd_una 或 rcv_nxt 进行比较,并将结果拟合到它们周围的 2GB 窗口中,检测 SEQ 号翻转。这大大简化了代码,并且只需要在每个 TCP-AO 套接字上存储 SNE 号码。

2GB 窗口乍一看似乎比 RFC 5926 允许的范围宽松得多。但这仅用于在翻转之前/之后选择正确的 SNE。它允许更多的 TCP 段重放,但所有常规 TCP 检查在 tcp_sequence() 中仍然应用于已验证的段。因此,它在算法的简单性和对大 TCP 窗口来说似乎更好的行为之间进行权衡,允许稍微更宽松地接受重放/重传的段。