跳转到内容

NS · 应用通道 PAKE 精确 profile

状态:协议定义,未生产实现。 本页细化应用通道的 PAKE,不改变 confirm、relay /rendezvous/v1 API 或已有 WireGuard PSK。规范向量和独立验证工具不能替代生产实现审查、秘密清除及三平台验收。

suite = 1 固定为 CPace draft-irtf-cfrg-cpace-21 的 §8.2 X25519 + SHA-512、initiator/responder 模式;不是 parallel 模式,也不进行算法协商。该规范仍为 Internet-Draft;升级修订需要新 profile 决定,不能随依赖升级改变字节结果。只接受 suite 1;其它值拒绝,不回退或协商。

  • CPace 的 A 始终为 o,B 为 p。relay 中 o 是信箱创建方,p 是被接纳者;隧道内 o 是被连接方,p 是 TCP 连接方,不能按 socket 发起顺序调换 CPace 角色。
  • 每个 attempt 按 CPace draft-21 §8.2 使用 CSPRNG 独立生成 32 bytes 原始秘密输入,由 RFC7748 X25519 解码执行低 3 位清零、最高位清零和次高位置 1;不额外限定原始输入的整数范围,不做拒绝采样或内部重抽,不复用秘密输入。DSI 为 ASCII CPace255,生成元按草案 generator_string、SHA-512 前 32 bytes 和 Elligator2 映射产生;ISK 使用完整 SHA-512 输出。
  • Share 为 32 bytes 的 X25519 u 坐标编码。长度不符拒绝;接收值按 RFC7748 忽略最高位并模 p 解码,不统一拒绝非规范 u 编码。共享输出为全零(包括曲线或 twist 的低阶输入)必须中止,不派生 ISK。生成本地 Share 失败也中止本 attempt、不发送 Share,不以重抽掩盖失败。transcript 和 th 使用实际发送/收到的原始 Share 字节,不能先正规化编码再计算绑定;重复帧比较也使用原始字节。
  • PRS 为 1..1024 bytes 的精确秘密输入,不自动 trim、改大小写、正规化 Unicode 或拼入路由短码。生成方使用 CSPRNG;口令熵与生成方案由应用 profile 按自身安全需求规定,通道不统一规定 33 bits 下限。文件应用约 33 bits 的三词口令是其应用设计,不扩展为通用约束;通道不解析票据或词表。
  • 双方每 attempt 各生成独立 32 bytes nonce;重试包括同 actor 再加入也必须刷新。suite、整数和下列数组均用规范 CBOR,长度最短、禁止不定长/额外字段/尾随数据;控制消息最多 8192 bytes,字符串为严格 UTF-8,kind 仍为 [a-z0-9-]{1,32}。

本页的 PRS 长度、nonce 长度、控制消息上限及握手期限是 NS profile 的输入与资源约束;CPace 算法步骤以固定草案为准。

信箱 open.intro = CBOR([1, kind, suite=1])。join.intro 为空 bytes;first 接纳不会保存加入者 intro,必要信息全部通过接纳后的 data 发送。两端都核对 kind 与本地 profile,未知 suite/version 立即失败。

控制消息沿用 Message = [ver=1, type u8, body]。原有 1 ACCEPT、2 COMPLETE 保留给 confirm;3 HELLO 保持其用途;4 APP 仍只在鉴权完成后可用。新增 5..8 不改变 relay 的不透明 data 契约:

type名称body
5PARAMS(o→p)[suite=1, mode, kind, authority, base_id bytes16, app_port u16, deadline u64, nonce_o bytes32, actor_o bytes, actor_p bytes, app_context bytes]
6NONCE(p→o)[nonce_p bytes32]
7SHARE[role u8, Y bytes32],role 0=o,1=p
3HELLO[ciphertext bytes],角色由当前步骤确定
8FINISH[role u8, tag bytes32]

mode=0 为 relay PAKE,mode=1 为已有 peer 隧道内 PAKE;其它值拒绝。mode 0 的 authority 是本次连接的规范 RelayId,base_id 为实际 mailbox_id,actors 为 o 声明的当前信箱 o/p 会合临时公钥(均 bytes32),不是 Node 身份。mode 1 的 authority 为 "",base_id 为双方已接受的 OPEN.channel_id,actors 均为空 bytes。不得用自报 authority/actor 覆盖当前连接已知事实。

mode 0 保持现有 relay API:Found 不返回 owner actor;MailboxView.peer 对两端都指被接纳的 p,并非对端 actor。o 从本地信箱所有者凭据取得 actor_o,从当前接纳结果取得 actor_p;p 必须核对 actor_p 等于自己的会合 actor,并核对本地已知的 RelayId、mailbox_id 和 expires。p 无法从这些 API 独立核实 actor_o,它只是 PARAMS 中的对端声明,经 C、HELLO 与 FINISH 绑定;不得称其为已验证的信箱所有者身份、actor 私钥持有证明或授权证据。应用身份与权限仍分别来自经验证的 NodeRecord/HELLO、实际承载 peer 及本地来源/grant;PAKE 成功不提升 actor_o 的信任等级。

app_port 必须非零且等于本地批准的 profile 端口。deadline 是本次会合/口令邀请的绝对到期 Unix 秒;relay 下等于实际信箱 expires,隧道内由邀请方固定且受本地邀请期限约束。接收时要求 now < deadline、剩余时间不超过本地 profile TTL 和 900 s;进度不延长握手期限。PARAMS 的 mode、kind、base_id、端口及已知 authority/actors 与本地上下文不符即失败。

app_context 为通用、不透明的 bytes(0..1024)。通道不解析业务字段;编码及是否必需由应用 profile 规定。两端必须逐字节核对各自本地预期值,不采用对端发送值替代本地要求;无附加绑定时为空,必需时不可省略。它进入公开 transcript,不应包含口令或其它秘密,也不承担来源选择、身份或权限证明。

mode 1 在双向 peer OPEN 后、PARAMS 前,由 p 发送控制帧 LOCATE = CBOR([1, invitation_locator bytes])(class=0/type=2)。invitation_locator 精确等于该邀请的 app_context,长度为 0..1024;不引入第二套上下文编码,也不改变下面的 C 或 CPace suite。p 从本地票据/profile 计算它;o 只按已登记的精确字节匹配邀请,并用该邀请保存的 app_context 构造 PARAMS。p 再逐字节校验 PARAMS.app_context 与本地值一致,最终经 C/th、HELLO 和 FINISH 绑定;定位成功本身不表示认证成功。

定位前必须按本地监听上下文、grant 和已认证 peer 唯一确定已授权的来源上下文。LOCATE 只能在此上下文内筛选 kind、端口及允许该 peer 的邀请,不能用来选择另一来源、扩大 grant 或解决跨来源歧义。同一匹配范围内的并发邀请必须有不同的 app_context;登记有重叠 peer 范围且上下文相同的活动邀请时拒绝。空值也遵循此唯一性规则,不能表示“任选一个”。未知、歧义、过期、已消费或已撤销的定位值一律关闭本流,不试遍口令或按顺序挑选,也不发送 PARAMS 或应用数据。

选中邀请时原子取得一个 attempt 名额;同一邀请最多一个进行中的 attempt,忙时拒绝新流,其它邀请可并行。失败预算归邀请所有,不能因新 channel_id、连接重建或 attempt 释放而重置。取得名额后的失败(包括对端断开和超时)只计一次;前两次释放该 attempt 并保留未到期、未撤销邀请,第三次失败或一次成功均消费邀请。未定位/忙的流不产生密码尝试,但仍受本地连接与资源上限约束。

以下数组的顺序固定;C 是整个数组的 CBOR bytes,不是 JSON,也不是字段文本拼接:

C = CBOR([1, 1, mode, kind, authority, base_id, app_port, deadline,
nonce_o, nonce_p, actor_o, actor_p, app_context])
sid = SHA-256("ns/channel-cpace-sid/v1" || C) // bytes32
CI = "ns/channel-cpace/v1" || C
ADa = CBOR([1, 0])
ADb = CBOR([1, 1])

前两个常量分别是 profile version 和 suite。先交换两个 nonce,再计算生成元;不存在用尚未分配的 mailbox_id 预计算 Share 的步骤。CPace 的 generator_string、LEB128 长度编码、映射、transcript_ir、ISK 完全遵循固定规范。Y_o、Y_p 是两个公开 Share 的原始 bytes32:

K_pake = ISK // 完整 SHA-512 输出 bytes64
th = SHA-256(CBOR([C, Y_o, Y_p])) // C 作为 bytes 元素
prk = HKDF-Extract-SHA256(salt=空, ikm=K_pake)
K_ch = HKDF-Expand-SHA256(prk, "ns/channel-key/v1" || UTF8(kind) || th, 32)

CPace 输入不公开 Node 身份。身份由下列加密 HELLO、签名和双方完成确认绑定,不能仅验证 PAKE 后就交付数据。

Hello 结构、NodeRecord 和签名上下文沿用应用通道 §4;record 是内嵌的规范 NodeRecord 数组。每方向的 k_hello_o/p 按 §4.1 从 K_ch Expand 32 bytes:

Hello = [1, record, endpoint text|null, not_after u64, sig]
sig = Ed25519_I("ns/channel-hello/v1" || UTF8(kind) || th || CBOR(前四字段))
AAD_dir = CBOR([1, 3, role, th])
ciphertext = ChaCha20-Poly1305-IETF(k_hello_dir, nonce=12个零字节,
plaintext=CBOR(Hello), AAD=AAD_dir)

要求在握手校验时 now < not_after <= deadline。验证 NodeRecord 规范编码、签名、代际/冲突、移除状态和地址冲突;不把 record.expires 擅自改成密钥有效期。隧道内还要求 Hello 身份和 K 与实际承载该流的已认证 peer 安装一致,不接受另一个有效签名身份。endpoint 沿用现有格式校验;不作为身份或授权证据。

对于内置 transfer,上述期限约束邀请与握手;只有在期限内完成认证及接纳后,才按文件阶段寿命运行,不因旧邀请消费/到期重开握手或撤销已接纳文件流。这不放宽握手校验,也不修改第三方隧道 PAKE IPC 将邀请期限继续施加于数据阶段的现有约定。

每方向 key 只加密一次 Hello。重复发送只能重发缓存的同一密文字节;收到完全相同的当前步骤重传可幂等处理,任何不同字节的重复、角色错位、越序或过期消息终止 attempt。重试必须创建新 nonce、标量、C、th 和所有方向密钥,不能只更换 AEAD nonce。

双方验证 Hello 后构造:

F = CBOR([1, th, SHA-256(CBOR(Hello_o)), SHA-256(CBOR(Hello_p)), I_o, I_p])
k_finish_o = HKDF-Expand-SHA256(K_ch, "ns/channel-finish-o/v1", 32)
k_finish_p = HKDF-Expand-SHA256(K_ch, "ns/channel-finish-p/v1", 32)
tag_dir = HMAC-SHA256(k_finish_dir, F)

FINISH 不使用 HELLO key;tag 比较恒定时间。接收对端 FINISH 且本地 FINISH 已送入有界发送队列,才可进入本地安装/应用接纳。信箱关闭不等于鉴权成功,也不保证两端安装的分布式原子性;安装失败/中断按本地事务回收。

relay 后续隧道 OPEN 仍采用应用通道 §7 的 channel_id=mailbox_id 与身份双向 MAC,K_confirm 从本次 K_ch Expand。隧道内 PAKE 的初始 OPEN 是 peer/null-MAC,用于建立流和 channel_id;PAKE 完成后不重新安装 peer、不更换 PSK,双方 FINISH 是放行应用数据的额外前置。两条路径均检查 kind、已选本地来源和会话所有权。

o: open(PakeDesc) p: lookup,校验描述;join(intro=空)
o → p: PARAMS
p → o: NONCE
o → p: SHARE(role=0)
p → o: SHARE(role=1)
p → o: HELLO // 创建方先验证加入方
o 校验失败:reject,清除 attempt;未先发送自己的 HELLO
o 校验成功:
o → p: HELLO
p → o: FINISH(role=1)
o → p: FINISH(role=0)
两端各自满足本地完成条件后安装/接纳;relay 消息排空并关闭信箱

隧道内略去信箱步骤,在 peer OPEN 双向通过后先由 p→o 发送 LOCATE、o 成功定位并取得 attempt 名额,再从 PARAMS 开始采用相同顺序;o/p 按 §1 定义。握手截止为 min(deadline, 本地 attempt 开始+60 s);隧道内这 60 s 从本地首次发送/接受初始 OPEN 起计,等待 LOCATE、定位或重传均不重置。尚未取得邀请 deadline 时也不能超过该 60 s 上限。超时、非法点、编码、身份或密码失败均不能安装或交付应用字节。

relay 下 o 对每次已接纳但失败的 attempt 执行 reject,包括超时/坏点;本地维护同一邀请的累计失败数,最多三次,不能只信任 relay 的计数或在断线后重置。前两次保留原短码并刷新所有 attempt 材料,第三次关闭邀请。p 在等待 o 的 HELLO 时遇到 reject 即清除本地状态,不抢先 close 以破坏正常错密码重试;p 收到无效的 o HELLO 则清除并关闭。恶意对端主动 close/失联仍可终止信箱,三次是上限而不是可用性保证。

隧道内失败只关闭本会话流;同一口令邀请也累计最多三次接纳失败,连接重建不重置计数,成功消费邀请,不能靠生成新 channel_id 无限猜测。daemon 重启不恢复这些临时邀请。

清除顺序:关闭新接纳/发送 → 撤销本次授权和 flow 并等待退休结果 → 释放本次临时 peer/租约(如存在)→ 丢弃秘密和缓存。清理失败显式报告,不能提前报告取消完成。清除 CPace 标量、PRS 的 attempt 副本、共享点、ISK、K_ch、HELLO/FINISH/K_confirm 密钥;已选来源的既有 peer/PSK 不受影响。允许重试时仅清除失败 attempt,邀请继续保存原 PRS、绝对期限和累计预算;成功消费、最终失败、到期、取消或来源/grant 撤销时才清除邀请材料并停止后续接纳。本次新安装的临时 peer 的会话 PSK 由安装方在移除时清除。记录日志只含不敏感状态/错误码,不含口令、密钥、票据或 Hello 明文。

采用 u32 big-endian length || u8 class || u8 type || payload。length 包含 class、type 与 payload、不包含 length 自身,范围 2..1048576;超限、截断、未知 class 拒绝。class 与 type 各占 1 byte,控制与应用各自独立编号:

classtypepayload
0(通道控制)0(OPEN)应用通道 §7 的 OPEN 规范 CBOR
0(通道控制)1(PAKE)本页完整 Message 的规范 CBOR,最多 8192 bytes
0(通道控制)2(LOCATE)[1, invitation_locator bytes] 的规范 CBOR,仅用于 mode 1;locator 最多 1024 bytes
1(应用)0..255应用 profile 定义的类型与载荷;通道不解释业务字段

其它控制 type 拒绝。第一帧必须为 class=0/type=0 的 OPEN;PAKE 仅在本地要求的握手步骤接收 class=0/type=1,不能根据对端首帧改变本地 auth 要求。LOCATE 仅在本地要求 mode 1 的双向 OPEN 后由 o 接收一次;重复、反向或越序 LOCATE 均关闭本流。relay 引入后的隧道及无需 PAKE 的 peer 通道拒绝 LOCATE,缺少 LOCATE 的 mode 1 不能回退到单邀请猜选或免 PAKE。应用 class 的 type 0/1/2/255 都不会被误解释为通道控制;具体应用对未知业务类型的处理由其 profile 规定。文件应用仍使用自己的 type 1 起编号,不占用控制类型。

只有双向 OPEN 完成且(若需要)本页 FINISH 完成,才向应用交付 class=1;提前应用帧立即关闭本会话,不缓存到鉴权后再交付。认证失败不影响其它 flow、peer 或 membership。confirm/relay 消息契约保持不变。

密码验证须以 RFC7748 和 CPace 附录 B.1 官方向量校准独立模型,覆盖两种模式的 C/sid/CI、生成元、Share、共享点、ISK、K_ch、签名 HELLO、密文、FINISH 与 OPEN MAC。仅双方使用同一候选库互测不足以提供独立证据。固定 seed、nonce、原始秘密输入仅供测试,禁止进入产品随机路径。

密码向量不覆盖 LOCATE、relay actor 字段来源、并发邀请选择或撤权/退休状态机。协议定义阶段验证 LOCATE 的规范编码、定位及状态转换规则;编码负例和顺序模型不能作为新控制帧已生产实现、真实并发安全或秘密清零的证据。relay API、confirm 与既有 OPEN 数组均保持不变。

以下随对应生产实现补齐,作为相关路径的验收条件,不作为开始实现的前置:两个同 peer/kind/端口邀请的独立选择;错误/跨来源/重复/过期定位拒绝;同邀请并发与三次累计失败;p 不将 actor_o 声明用于授权;失败、撤销和成功消费后的实际授权关闭及 flow 退休。模型验证不替代真实来源/grant、IPC、原生平台与数据流证明,缺口只阻断对应路径交付。

协议定义与候选可行性通过后,生产接入仍须匹配正式向量,完成固定源码与绑定一致性、维护/许可证/MSRV/供应链、密码调用/侧信道风险与秘密生命周期代码审查,以及 Linux/macOS/Windows 原生验收。原生平台验收约束对应平台与最终交付,不要求在编写生产接入前先有完整产品。候选通过向量不等于生产准入。