安全模型与诊断边界
iroh-pairing 的安全模型建立在一个核心原则之上:手机介绍者(Introducer / Connector)只负责撮合会合与观察进度,真正的身份认证与授权决策完全由两台被配对设备(Endpoint A 与 Endpoint B)在端到端加密配对通道上独立完成。
完整的协议规范见仓库内的 protocol/invitation.md、protocol/transport.md 与 protocol/sas-profile.md。
1. 核心领域概念与信任分层
Section titled “1. 核心领域概念与信任分层”| 概念 | 信任级别 | 精确定义与安全边界 |
|---|---|---|
邀请(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 之间的配对流完全隔离:
- 手机 → 设备 RPC:仅定义
Introduce、Observe与交接前的CancelIntroduction三种请求。- 手机向端点 A 发送
Introduce时,只携带端点 A 自己的invite_id与token;端点 A 的token绝不发送给端点 B(反之亦然),且TransactionContext中已完全剥离双方的token与expires_at。 - 协议中不存在任何由手机触发 SAS 确认或读取验证后
payload的 RPC 消息。
- 手机向端点 A 发送
- 设备 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状态后才在这条设备间加密流上发送,永不经过手机转发。
- 每一端都直接从底层 QUIC/TLS 连接读取已认证的
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 HKDFinfo与双向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且双方互发 SASDone后才进入Verified;手机端只有在通过Observe观察到两个端点均报告ExchangeComplete时,才会显示“两个端点均已报告完成”。 - 显式回执与
Indeterminate:在Verified之后交换短消息时,接收端宿主必须显式调用accept_payload/accept_payload_with_digest发送Receipt,且双方互发ExchangeDone才算ExchangeOutcome::Complete。若在此期间发生网络断开或超时,无法确认对端是否已完成接收,库会显式返回ExchangeOutcome::Indeterminate(同时保留已验证的VerifiedPeer),绝不静默伪装为交换成功。
6. 脱敏 tracing 诊断与安全导出
Section titled “6. 脱敏 tracing 诊断与安全导出”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 安全提取诊断 JSONL
Section titled “从 pair-demo 安全提取诊断 JSONL”重要警告:
pair-demo的原始stderr同时包含供人类交互使用的终端二维码、备用邀请行和 SAS 提示,严禁直接将原始stderr文件分享到工单或公开渠道。
如需从 pair-demo 提取可安全分享的诊断记录,请使用 docs/diagnostics.md 中的白名单过滤脚本,仅保留固定诊断 target 与白名单字段:
pair-demo invite 2> pair-demo.stderrpython3 - <<'PY' < pair-demo.stderr > pair-demo-diagnostics.jsonlimport jsonimport 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