Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

安全模型

Subnetra 的传输安全在 v1 中是强制的,不可延后。本页描述威胁模型以及落实它的每一项 机制。逐字节的精确规则在规范 线协议 中;本页是其概念上 的伴读。

威胁模型

Subnetra 假设承载是敌对的:攻击者可以观察、丢弃、修改、注入、重放 UDP 数据报,并可主动 探测监听端口。设计目标是:

  • 每个内层包的 机密性与完整性
  • 隐蔽性——主动探测者无法把 Subnetra 端点与一台单纯丢包的主机区分开。
  • 捕获的密文 无法重放 进受保护网络。
  • 隔离性——攻破某个 Spoke 的密钥,不得伪造任何其他链路。

每链路预共享密钥

每个 peers[] 条目携带 自己的 32 字节 PSK(64 个十六进制字符)。不存在全网共享密钥; 仍带顶层 psk 的旧配置会被拒绝(InvalidPsk),在多个对端间复用同一把 PSK 也会被拒绝 (DuplicatePsk)。

从每把 PSK,按有序对派生一把 方向链路密钥

link_key(psk, from_id, to_id) =
    BLAKE2b-256(key = psk, msg = "subnetra-v1-link" || u32_be(from_id) || u32_be(to_id))

因此一个节点发往某对端的发送密钥,等于该对端用于接收该节点流量的接收密钥,而两个方向 使用不同的密钥。

为什么每链路密钥是强制的: 在共享 PSK 下,两个独立的每对端单调计数器可能为不同的 明文产生相同的 (key, nonce) 对,这会灾难性地破坏 ChaCha20-Poly1305。给每条方向链路 各自的密钥,使每条链路的 nonce 空间互不相交。

会话 epoch——无状态、无握手的会话

Subnetra 不在线路上建立会话。取而代之,每个守护进程生命周期在启动时采样一次 boot epoch(墙钟纳秒,u64),并派生一把全新的 每会话密钥

session_key(link_key, epoch) =
    BLAKE2b-256(key = link_key, msg = "subnetra-v1-session" || u64_be(epoch))

epoch 随每个数据报传输(8 字节)。接收方从看到的 epoch 无状态地 派生匹配密钥,并施加 一条 只进不退 的规则:

  • 更大(更晚)的 epoch 一旦通过认证,便取代旧会话并 重置防重放窗口
  • 更小(更旧)的 epoch 被丢弃(那会是对已退役会话的跨 epoch 重放)。

因为每次重启都产出新密钥,序号可以安全地从 1 重新开始,而绝不会复现历史上的 (key, nonce) 对——无需任何磁盘持久化。

失败即关闭的时钟: epoch 必须 ≥ 2024-01-01T00:00:00Z 的纳秒值且非零。时钟无法满足 此条件的节点会拒绝启动,而不是发出一个过低/会碰撞的 epoch。

可接受的残余限制: 如果某节点的墙钟在重启之间 倒退(无 RTC、尚未 NTP 同步),它 的新 epoch 可能小于对端记住的值,对端会拒绝新会话,直到其时钟越过旧值。这通过运维手段 (NTP/RTC)缓解,绝不通过协议内的 epoch 交换——按设计就没有握手。

Nonce 与防重放

96-bit AEAD nonce 由 64-bit 单调计数器 派生,每端每个数据报递增——绝不固定或复用。 接收方为每个会话维护一个 滑动窗口(位图)防重放检查:窗口之外的序号、或已见过的序号, 被丢弃;窗口内的乱序序号被接受。没有它,历史密文就能被重放进受保护的局域网。

隐蔽:静默 Drop,无魔数

Subnetra 是 无状态混淆:密文 不含固定魔数,且在任何认证或校验失败时,包被 静默丢弃——绝不回以 TCP Reset、ICMP 错误或任何可观测之物。对于向 UDP 端口发送垃圾 (或重放密文)的主动探测者而言,端点与黑洞无异,其 CPU 也没有异常尖刺。

这挫败的是主动探测。20 字节封装报头在 AEAD 之外,因此若以明文传输,被动的路径上观察者可凭 恒定的 version、重复的 epoch、低位单调的 seq 对协议做指纹识别。部署级的 obfuscate 开关(默认开启线协议 → 报头混淆)用每包 pad 对报头做 XOR 掩码,使整个数据报看起来像随机字节——零字节开销、全 mesh 一致——并把 spoke 的 NAT 保活去周期化, 使其节奏不构成指纹。它只隐藏协议指纹,隐藏包长或一般时序。设 obfuscate: false 可关闭 (改发可读的明文报头,便于抓包调试)。

内层源绑定(反伪造)

每个对端声明一个 allowed_src CIDR。解密后,接收方将 内层 IPv4 源地址 与该对端的 allowed_src 比对;内层源落在允许范围之外的包被丢弃(计为 spoof)。这防止一个已认证的 对端注入冒充其他节点地址空间的流量。

防反射中继保护

当 Hub 在 Spoke 之间中继时,它绝不把包 回送给来源对端。结合最长前缀策略路由,这避免了 反射环路。

NAT 保活(单向、永不确认)

role=spoke 默认启用内置 NAT 保活(keepalive_secs = 20):它每隔一段时间向其 Hub 发送 一个极小的 已认证 数据报,使 Spoke 的 NAT 孔保持打开、Hub 保持一条新鲜的回程路由。它是 单向、永不确认 的数据报,纯由静态配置门控——它 不是 握手,也不削弱无状态模型。设置 keepalive_secs 调节它,或设为 0 关闭(hub/manual 默认为 0)。

哪些秘密永不暴露

subnetra status(及 --json)刻意 绝不 序列化 PSK 或任何派生密钥。计数器、endpoint 与健康状态可观测;秘密不可。

密码学原语

原语选择参数
AEADChaCha20-Poly1305(IETF,96-bit nonce)密钥 32 B,nonce 12 B,tag 16 B
KDF / keyed hashBLAKE2b-256 原生 keyed 模式(非 HMAC)key = 父密钥,32 B 摘要

精确的密钥日程、nonce 构造、报头序列化与完整的接收方决策序列——全部由已知答案测试向量 钉死——见 线协议