服务器身份固定
每台 rVPN 服务器都由一个长期存在的 Ed25519 身份密钥标识。客户端在首次连接 某个配置文件时会固定该密钥,并在固定的密钥发生变化且无有效轮换证明时拒绝连接。 这与 SSH 的信任模型相同,并额外引入了一个链式签名的轮换仪式,让运营方可以在 不影响现有客户端的前提下轮换密钥。
所有固定值都采用同一种规范编码:
ik:1:<52 个 base32 字符>ik:— 命名空间,为未来密钥类型预留。1— 版本号,合法轮换时可以明确地升级到ik:2:…。- 主体为
sha256(identity_key),采用 RFC 4648 base32、小写、无填充。 52 个字符,ASCII 安全,适合放入二维码。
示例:
ik:1:d4rgmp5b7ta6qmxi2mccwkjq4qxopfxzr7qivbfgu4wjycmuxnla桌面版客户端曾经使用 32 个字符的十六进制指纹。它仍会在首次连接时读取旧值,
并在保存时以 ik:1: 形式重写 —— 无需修改任何配置。
移动端的固定行为
Section titled “移动端的固定行为”在 iOS、macOS 和 Android 上,固定值存放在每个配置文件的 Server Fingerprint
字段中。不再有单独的 known_hosts.json。
- 首次连接且
Server Fingerprint为空、Trust on First Use 打开时: 应用捕获服务器的固定值并写入该配置文件。 - 后续连接:只有当服务器的固定值与已存值一致时,隧道才会建立。
- 不一致:应用弹出 Server identity changed 对话框, 同时显示已固定和当次收到的两个固定值,并提供三个选项 —— 取消、 删除配置文件,或信任新身份。
若要在首次连接前显式固定身份,把运营方提供的固定值粘贴到配置文件编辑器的 Server Fingerprint 字段中。
桌面 CLI 的固定行为
Section titled “桌面 CLI 的固定行为”桌面 rvpn 客户端把固定值存放在 known_hosts.json(路径可配置)中,
并在其上叠加一个配置层面的指纹字段。完整字段说明请参考
客户端配置
的 [server_identity] 小节;要点:
fingerprint— 设为ik:1:…值可强制指定身份,无视known_hosts.json中的存值。trust_on_first_use— 默认为true。首次连接时捕获。strict_mode— 默认为false。设为true时会完全拒绝未知服务器; 必须在首次连接前设置fingerprint。strict— 默认为true。设为false时不一致仅记录警告而不中止连接。
客户端还会在保存 known_hosts.json 时把任何遗留的十六进制指纹迁移为规范的
ik:1: 格式,并保留原有的 first_seen 时间戳。
密钥轮换仪式
Section titled “密钥轮换仪式”运营方轮换服务器身份密钥时,prekey bundle 会新增两个字段,用于让客户端 校验连续性:
identity_key_version— 单调递增;出现新身份即+1。rotation_signature— 由旧身份对新公钥字节与新版本号进行的 Ed25519 签名。
已固定版本 N 的客户端遇到不一致时会:
- 看到某个 bundle 的身份与固定值不符,
- 校验
identity_key_version == N + 1, - 使用已固定的身份作为验证密钥,校验轮换签名,
- 两项通过则静默更新固定值为新值 —— 不弹对话框,连接照常进行。
若客户端遇到未签名的轮换或版本跳跃,则回落到上文的不一致对话框流程。
运营方的仪式
Section titled “运营方的仪式”全新的服务器发布 v1 bundle,不带轮换签名:
rvpn-server keygenrvpn-server prekey-bundle轮换时,生成新身份并用旧密钥对新 bundle 签名:
# 把旧身份暂时保留在可访问的位置mv server_identity.key old_identity.key
# 生成新身份rvpn-server keygen --output server_identity.key
# 发布由旧身份签名的 v2 bundlervpn-server prekey-bundle \ --identity server_identity.key \ --output prekey-bundle.json \ --rotate-from old_identity.key \ --from-version 1--rotate-from 和 --from-version 必须成对出现。服务器不会发布未签名的
轮换。发布新 bundle 后,已固定客户端会静默更新,新客户端在首次使用时固定
新密钥。
请归档 old_identity.key —— 你只需要保留到发布轮换 bundle 为止,
但一旦销毁,就再也无法从该版本签署下一次轮换。
配置文件的导出与导入
Section titled “配置文件的导出与导入”移动端和桌面端的配置文件导出都会携带 serverFingerprint 字段。当你导入的
.rvpn 配置文件已经带有固定值时,说明发送方通过带外方式为服务器身份做了
背书 —— 保存前请核对导入面板中显示的固定值与他们公布的值是否一致。
若导入的配置文件没有固定值,隧道会在首次成功连接时捕获一个,就和你在本地 新建配置文件时一样。
可防御
- 首次成功连接之后的被动 MITM。
- 恶意运营方接管你的服务器域名或 IP,并指向不同的后端。
- 使用来自其他服务器的偷取到的 prekey bundle 进行重放。
无法防御
- 首次连接时的主动 MITM。这是经典的 TOFU 局限。缓解方式:导入已经携带 固定值的配置文件,或在连接前通过带外渠道粘贴固定值。
- 已越狱 / root 系统上对本地配置文件存储的篡改。原厂 iOS 会沙盒化配置文件; 原厂 Android 则将其保持为应用私有。
如果你确实需要主动替换服务器身份 —— 比如泄露后的恢复 —— 那么弹出的不一致 对话框就是正确的路径。通过带外渠道公布新固定值,告知用户接受变更, 他们就会带着新的固定值回到新密钥上。