跳转到内容

三密钥模型

查看 Markdown

Connect 兑换需要由不同参与方持有的三个会话密钥。每一次经过 Connect 的身份认证往返都会在不同时刻签发三把彼此独立的密钥,分别由不同的当事方持有,防御不同类型的攻击。一次成功的 /redeem 必须同时出示这三把密钥。

本页讲的不是 Sudomimus 用来签令牌的长期密钥(那部分见 令牌与验证)。本页讲的是一次浏览器登录如何证明自己的合法性。

密钥 创建者 持有方 可见范围
exposureKey /establish 应用后端 → 浏览器 浏览器、URL、via.sudomimus.com
hiddenKey /establish 仅应用后端 后端
confirmationKey 用户完成认证后由 via.sudomimus.com 签发 应用后端(通过回调) 浏览器、URL

exposureKey 和 hiddenKey 是同一次 /establish 调用成对签发的。它们属于唯一的一个会话,也只属于那个会话。

每把密钥都带有角色专属的前缀,便于在请求边界直接拒绝错位的密钥:

  • exp_ + 32 位小写 hex
  • hid_ + 32 位小写 hex
  • cnf_ + 32 位小写 hex

如果仅凭一个不透明的会话引用就能换取令牌,那么任何得到该引用的攻击者都可以冒充用户完成兑换。兑换要求攻击者从各持有方取得全部三个密钥:

  • 没有 hiddenKey —— 即使攻击者窃取了用户正在访问的 URL,也无法兑换令牌。hiddenKey 永远不会离开应用服务器。
  • 没有 exposureKey —— 即使攻击者攻破了应用服务器,也无法兑换其他用户的会话,因为每个待处理会话的 exposureKey 只会发送给对应的浏览器。
  • 没有 confirmationKey —— 任何一方都无法兑换用户尚未完成的会话。只有用户在 via.sudomimus.com 上通过通行密钥或验证码挑战后,平台才会签发 confirmationKey。
flowchart TD
    Establish["POST /establish"] --> Exposure["exposureKey<br/>公开会话引用"]
    Establish --> Hidden["hiddenKey<br/>服务端密钥"]

    Exposure -->|重定向 URL| Browser["用户浏览器"]
    Hidden -->|由其保管| Backend["应用后端"]

    Browser -->|exposureKey| Via["via.sudomimus.com<br/>用户完成认证"]
    Via -->|认证成功| Confirmation["confirmationKey<br/>认证证明"]

    Exposure -. 随浏览器传递 .-> Callback["应用 callback"]
    Confirmation -->|callback URL| Callback

    Callback -->|exposureKey + confirmationKey| Redeem["POST /redeem"]
    Backend -->|hiddenKey| Redeem

    Redeem --> Tokens["accessToken + refreshToken"]

/redeem 成功之后,这三把密钥都会被消费掉,不能再次使用。即使应用重复发送同一组三元组,第二次也会失败。

如果你熟悉 OAuth 的 authorization-code 流程,大致的对应关系是:

Sudomimus OAuth 2.0
exposureKey state 参数(大致对应)
confirmationKey authorization code
hiddenKey 没有直接对应物

OAuth 使用长期存在的 client_secret 验证令牌交换请求。Sudomimus 则在每次 /establish 时生成新的 hiddenKey。某次会话的 hiddenKey 泄露只会影响这一次兑换,不会影响应用过去或未来的其他兑换。

这与「持有证明(proof-of-possession)令牌」的思路一致:短期、范围明确的密钥优于长期、被广泛共享的密钥。

如果需要标准的 OAuth/OIDC 语义,可以使用部署在 oidc.sudomimus.com 的标准 OIDC 提供方。三密钥模型用于底层 Connect 协议,通过 OIDC 接入的应用不会直接接触它。

除了上述三把会话密钥,每个应用还有两对长期 RSA-2048 密钥对:

  • Token-signing 密钥对 —— Sudomimus 用私钥半边签 access / refresh JWT;应用通过 Session GET /applications/{applicationAnchor}/jwks.json 拿到公钥半边验证签名。
  • Client-auth 密钥对 —— 应用用私钥半边(在应用创建和每次轮换时一次性下发)签每一次 /establish 请求;Sudomimus 用存储的公钥半边校验。

这两对都不属于「三密钥」模型。它们认证的是 Sudomimus 与应用之间的通道;三把会话密钥认证的是一次登录。完整细节见 令牌与验证。