跳转到内容

安全模型与诊断边界

iroh-pairing 的安全模型建立在一个核心原则之上:手机介绍者(Introducer / Connector)只负责撮合会合与观察进度,真正的身份认证与授权决策完全由两台被配对设备(Endpoint A 与 Endpoint B)在端到端加密配对通道上独立完成。

完整的协议规范见仓库内的 protocol/invitation.md、protocol/transport.md 与 protocol/sas-profile.md。

概念 信任级别 精确定义与安全边界
邀请(Invite) 公开 + 短期介绍能力 被配对设备生成的一次性、限时(默认 5 分钟)、版本化 CBOR 封装,包含公开 EndpointId、地址提示、业务上下文(ApplicationContext)与 32 字节一次性 token。绝不包含设备长期私钥、SAS 临时私钥或业务机密 payload。
介绍者(Introducer / Connector) 仅会合引导与观察 扫描两份邀请、检查基础兼容性并向两端递交 TransactionContext 的手机端角色(connector-web / connector-android)。不转发业务流量,也不拥有本地确认权。
介绍交付完成(Introduced / Handoff) 会合已绑定(未验证) 两台设备均已校验本端 token 与快照、绑定同一 TransactionContext 并向手机返回 IntroductionAck。此后两端独立建连完成后续步骤,不再依赖手机在线。绝不等于“配对成功”或“身份已验证”。
候选对端(CandidatePeer) 未验证身份 通过介绍材料获知的对端声明信息,仅用于启动配对握手或执行宿主前置准入回调(HostValidator),尚未经过密码学 SAS 与人工确认,不可授予业务权限。
已验证对端(VerifiedPeer) 已认证身份与上下文 在专用配对通道上完成完整 Matrix SAS、通过双端本地人工确认及双向身份/上下文 MAC 校验后产出的不可伪造记录(私有字段、无公开构造、无 Deserialize)。
宿主授权(Host Authorization) 受控业务权限 宿主基于 VerifiedPeer 与本地策略,显式授予特定 EndpointId 在指定业务 ALPN(通过 PairingGate)与资源 scope 内的访问权(Once 或 Persistent,可撤销)。

2. 为什么手机端无法代确认 SAS 或窃取载荷?

Section titled “2. 为什么手机端无法代确认 SAS 或窃取载荷?”

2.1 物理通道隔离与协议最小权限

Section titled “2.1 物理通道隔离与协议最小权限”

在 protocol/transport.md 中,手机与设备之间的 RPC 跟设备 A 与设备 B 之间的配对流完全隔离:

  1. 手机 → 设备 RPC:仅定义 Introduce、Observe 与交接前的 CancelIntroduction 三种请求。
    • 手机向端点 A 发送 Introduce 时,只携带端点 A 自己的 invite_id 与 token;端点 A 的 token 绝不发送给端点 B(反之亦然),且 TransactionContext 中已完全剥离双方的 token 与 expires_at。
    • 协议中不存在任何由手机触发 SAS 确认或读取验证后 payload 的 RPC 消息。
  2. 设备 A ↔ 设备 B 端到端加密配对流:两端收齐介绍后,由 EndpointId 原始 32 字节字典序较小的一端(starter)主动拨号连接较大的一端(acceptor),在端到端加密的 /iroh-pairing/1 双向流(支持 UDP 直连或经 relay 转发)上完成 Pair / PairReady、Matrix SAS 握手以及验证后的 ExchangeOffer / Receipt / ExchangeDone 交换。
    • 每一端都直接从底层 QUIC/TLS 连接读取已认证的 Connection::remote_id(),并与 TransactionContext 中的对端 endpoint_id 严格比对。
    • 验证后的 bootstrap payload(上限 64 KiB)只在双端均进入 Verified 状态后才在这条设备间加密流上发送,永不经过手机转发。

2.2 退出观察(Detach)与取消配对(Cancel)严格区分

Section titled “2.2 退出观察(Detach)与取消配对(Cancel)严格区分”
  • 交接前取消(cancel_before_handoff):仅当某个端点尚处于 Introduced(尚未与对端建立 PairingConnected 配对流)时,手机才可请求 CancelIntroduction。一旦两端进入配对流,端点会以 phase_conflict 拒绝来自手机的取消请求,防止手机越权中断已建立的设备间配对。
  • 交接后退出观察(detach):手机收齐双端 IntroductionAck 后,界面只提供“退出观察”。调用 detach 或关闭手机页面仅停止手机侧的 Observe 轮询并释放手机自身的临时 Endpoint,两台设备继续独立完成 SAS 与数据交换。
  • 设备端取消隔离:设备本机调用 Session::cancel() 只清理当前配对事务的任务与流,绝不调用宿主 Router::shutdown 或 Endpoint::close。

3. Matrix SAS(iroh adaptation v1)密码学绑定

Section titled “3. Matrix SAS(iroh adaptation v1)密码学绑定”

iroh-pairing 使用 vodozemac 0.11.1 执行完整的 Matrix SAS 协议(规范见 protocol/sas-profile.md),不自行手写 Curve25519 ECDH、HKDF 或 MAC 原语,也不制造伪造的 @user:example.org Matrix 账号:

  • 固定算法套件(禁止降级):
    • 密钥协商:"curve25519-hkdf-sha256"
    • 承诺哈希:"sha256"(starter 先发送公钥与 Start 参数的 SHA-256 承诺,acceptor 回复 Accept 后双方再交换临时 Curve25519 公钥 Key)
    • 消息认证码:"hkdf-hmac-sha256.v2"
    • 短认证码格式:["decimal", "emoji"](派生 7 个 Matrix SAS 表情与 3 个四位十进制数)
  • 独立域分离前缀:SAS 派生与 MAC 计算分别使用 IROH_PAIRING_SAS_V1| 与 IROH_PAIRING_MAC_V1| 前缀及无歧义管道符 | 分隔,与标准 Matrix 客户端协议域完全隔离,防止跨协议混淆攻击。
  • 完整上下文绑定(TransactionContext):SAS HKDF info 与双向 Mac 同时绑定双方真实 EndpointId、两个邀请的 invite_id、16 字节随机 transaction_id 以及双方的完整 PeerDescriptor(含 app_name、device_name、地址提示、namespace、profile_id、profile_version、role、accepts_roles、purpose、scope、required_capabilities 与 metadata)。任何字段被篡改都会导致 SAS 不一致或 MAC 校验失败。
  • 禁止跨重试复用密钥:每次配对事务或失败重试都必须生成全新的 transaction_id 与全新的临时 Curve25519 密钥对,断连后绝不跨连接恢复未完成的 SAS 会话。

4. 自报名称(Self-Reported Names)为什么标注“尚未验证”?

Section titled “4. 自报名称(Self-Reported Names)为什么标注“尚未验证”?”

在邀请与 PeerDescriptor 中,app_name、device_name 与 profile_id 是由发起配对的端点自行声明的文本。

Matrix SAS 与 MAC 对 TransactionContext 的校验能够保证**“端点 A 与端点 B 看到的上下文声明完全一致,未被中间人篡改”,但不能证明该端点获得了某个第三方 CA 或软件发行商的背书**。因此:

  • 手机界面(Web 与 Android)在卡片上外露展示 app_name 与 profile_id 时,始终附带**“自报名称,尚未验证 / Self-reported, not verified”**提示,绝不渲染误导性的“官方认证发行者”徽章。
  • 操作者必须结合当前物理场景,在两个端点本机核对全部 7 个 SAS 表情一致后再确认。

5. 确定性完成(ExchangeComplete)与不确定结果(Indeterminate)

Section titled “5. 确定性完成(ExchangeComplete)与不确定结果(Indeterminate)”
  • 单端确认不等于完成:只有本端用户确认、收到对端有效 Mac 且双方互发 SAS Done 后才进入 Verified;手机端只有在通过 Observe 观察到两个端点均报告 ExchangeComplete 时,才会显示“两个端点均已报告完成”。
  • 显式回执与 Indeterminate:在 Verified 之后交换短消息时,接收端宿主必须显式调用 accept_payload / accept_payload_with_digest 发送 Receipt,且双方互发 ExchangeDone 才算 ExchangeOutcome::Complete。若在此期间发生网络断开或超时,无法确认对端是否已完成接收,库会显式返回 ExchangeOutcome::Indeterminate(同时保留已验证的 VerifiedPeer),绝不静默伪装为交换成功。

iroh-pairing(目标 iroh_pairing::diagnostics)与 connector-core(目标 connector_core::diagnostics)仅发出固定白名单字段的结构化 tracing 事件(含进程内匿名序号 correlation、阶段 phase/stage、耗时 stage_elapsed_ms/total_elapsed_ms、握手后单次采样的 path_type 以及稳定错误码 error_code)。

诊断事件永不记录以下内容:

  • 邀请原文、invite_id、token、二维码内容或 --connector-url;
  • SAS 表情、十进制数字、Curve25519 临时密钥或承诺值;
  • 业务 payload 字节、摘要或元数据;
  • EndpointId、IP/端口地址、relay URL、设备名、应用名或原始错误文本。

重要警告:pair-demo 的原始 stderr 同时包含供人类交互使用的终端二维码、备用邀请行和 SAS 提示,严禁直接将原始 stderr 文件分享到工单或公开渠道。

如需从 pair-demo 提取可安全分享的诊断记录,请使用 docs/diagnostics.md 中的白名单过滤脚本,仅保留固定诊断 target 与白名单字段:

终端窗口
pair-demo invite 2> pair-demo.stderr
python3 - <<'PY' < pair-demo.stderr > pair-demo-diagnostics.jsonl
import json
import sys
TARGETS = {
"iroh_pairing::diagnostics",
"connector_core::diagnostics",
"pair_demo::diagnostics",
}
FIELDS = {
"target",
"level",
"correlation",
"phase",
"stage",
"previous",
"stage_elapsed_ms",
"total_elapsed_ms",
"path_type",
"error_code",
"operation",
"connect_ms",
"rpc_ms",
"result",
}
for line in sys.stdin:
try:
value = json.loads(line)
except json.JSONDecodeError:
continue
if not isinstance(value, dict) or value.get("target") not in TARGETS:
continue
record = {key: value[key] for key in FIELDS if key in value}
if "target" in record:
print(json.dumps(record, ensure_ascii=False, sort_keys=True))
PY