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 元组 (srcaddr, srcport, dstaddr, dstport),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,应该如何做才能不将其从活动连接中删除?(因为它仍然可以在稍后的任何时刻使用)

答: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 来代替正在删除的密钥,并且内核将原子地删除旧密钥,即使对等体仍在请求它。强制删除不保证,因为对等体可能还没有新密钥 - 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. 内核 MKT 数据库与用户空间数据库

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 (删除/显示/轮换)

MKT 的删除或添加

对于用户空间来说更难

对于内核来说更难

转储能力

getsockopt()

Netlink .dump() 回调

内核资源/内存的限制

相等

可扩展性

TCP_LISTEN 套接字上的争用

整个数据库上的争用

监视和警告

TCP_DIAG

相同的 Netlink 套接字

MKT 的匹配

半个问题:仅侦听套接字

困难

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。可以单独启用它们中的任何一个,并且可以按网络命名空间、四元组、系列、L3 索引和 TCP 报头标志对其进行过滤。如果段具有 TCP-AO 报头,则过滤器还可以包括 keyid、rnext 和 maclen。SNE 更新包括回滚的数字。

RFC 5925 非常宽松地规定了如何为 MKT 完成 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 号。

乍一看,与 RFC 5926 相比,2GB 窗口似乎更宽松。但这仅用于在回滚之前/之后选择正确的 SNE。它允许更多的 TCP 段重放,但仍然在验证的段上应用 tcp_sequence() 中的所有常规 TCP 检查。因此,它用略微宽松的接受重放/重传段换取算法的简单性和对于大型 TCP 窗口似乎更好的行为。