mcpField
D
dpop-adoption

dpop-adoption

github.com/google安全21.6k2.7k 次安装今天更新
代码仓库

关于 dpop-adoption

面向实现 Google OAuth 2.0 DPoP 的开发者,提供密钥、证明 JWT、Nonce 重试及 SPA 后端架构的安全规则。重点规定 WebCrypto 签名格式和令牌端点流程。

主要功能

  1. 01P-256 密钥生成与 JWK 导出
  2. 02DPoP Proof JWT 声明规则
  3. 03WebCrypto 签名格式约束
  4. 04Nonce 挑战单次重试流程
  5. 05SPA 的 BFF 架构建议

适用场景

  1. 01为 OAuth 令牌刷新添加 DPoP 绑定
  2. 02实现授权码交换的 DPoP 证明
  3. 03排查 WebCrypto 签名格式错误

MCP 相关

提及可通过 Google Developer Knowledge MCP Server 查询 Google 开发者文档,未说明本技能自身提供 MCP 接口。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/google/skills --skill dpop-adoption

安装后在 Claude Code 中输入/dpop-adoption确认已加载。

详细介绍

DPoP 采用与身份安全架构

演示式持有证明(DPoP,RFC 9449)通过以密码学方式将 OAuth 2.0 刷新 令牌绑定到仅由客户端持有的私钥,保护其免遭拦截和重放攻击。在 Google 的 OAuth 2.0 平台中, DPoP 会在令牌端点绑定刷新令牌,而为 Google API 签发的访问令牌则是标准 Bearer 令牌(token_type: "Bearer")。

1. 核心密码学与架构不变量

实现 DPoP 辅助工具或升级 HTTP 客户端时,必须遵守以下严格的安全不变量:

A. 通用 WebCrypto 与运行时兼容性
  • 在现代 ES6 JavaScript 中(Node 18+ 和浏览器使用 "type": "module"), 验证环境上下文后,始终直接访问 globalThis.crypto。
  • 切勿通过 require('node:crypto') 导入旧版 CommonJS 模块,也不要 引用浏览器作用域内的 window.crypto,因为这会导致模块在混合运行时中 初始化时崩溃。
B. 硬件支持的不可导出密钥持久化
  • 在 SECP256R1(P-256)曲线上生成椭圆曲线密钥对:{ name: 'ECDSA', namedCurve: 'P-256' }。
  • 关键安全防护措施:私钥必须配置为不可导出(extractable: false)。 这可确保私钥永远不会离开硬件密码学边界(Secure Enclave、Android KeyStore 或 JS 沙箱内存),从而防范 XSS 和依赖项窃取令牌攻击。
  • 公钥必须保持可导出(extractable: true),以便输出 JSON Web Key(JWK)。
C. 公共 JWK 格式标准
  • 导出公钥并将其附加到 DPoP Proof JWT 标头时,必须构造仅包含以下字段的 精简 JWK 字典:
    • "kty": "EC"
    • "crv": "P-256"
    • "x":Base64URL 编码的 x 坐标,不带末尾等号填充符 (=)。
    • "y":Base64URL 编码的 y 坐标,不带末尾等号填充符 (=)。
  • 切勿暴露私钥参数("d")或多余的元数据。
D. IEEE P1363 与 ASN.1 DER 签名格式的区分
  • 根据 IEEE P1363 和 RFC 7518,DPoP Proof JWT 要求使用原始拼接坐标签名($R \parallel S$,对于 P-256,长度恰好为 64 字节)。
  • WebCrypto 原生规则:在标准 WebCrypto(crypto.subtle.sign)中, ECDSA 签名原生输出格式已经是原始 IEEE P1363 格式(拼接的 32 字节 r 和 s 缓冲区,总长 64 字节)。不要 尝试转换 crypto.subtle.sign 的输出格式,因为将 64 字节原始缓冲区解析为 ASN.1 DER 会导致立即发生运行时异常 (Invalid DER sequence)。直接对原始 ArrayBuffer 进行 Base64URL 编码。
  • **旧版 API 回退方案:**仅在使用旧版 Java/Android (java.security.Signature)或 Node CommonJS(crypto.createSign)实现时, 才需将 ASN.1 DER 输出转换为原始 64 字节 IEEE P1363 格式,然后再进行 Base64URL 编码。
E. SPA 与前端后端(BFF)架构
  • **无密钥 SPA 的局限性:**纯客户端单页应用(SPA)若没有后端,就无法直接通过 DPoP 调用 Google API,因为服务器端点要求提供 client_secret,且浏览器对 DPoP-Nonce 响应标头存在 CORS 限制。
  • **BFF 模式:**若要使用 DPoP 保护 SPA,应通过前端后端(BFF)服务器端客户端 路由授权和令牌刷新请求。BFF 设置 access_type=offline,在服务器端使用 DPoP 绑定刷新令牌,并与前端维持安全的会话 Cookie。

2. 实现规则与强制公共 API

创建新模块时,模块 MUST 显式导出以下所有函数,以便与 CI/CD 验证工具和自动探测器顺利集成。检查或重构现有代码库时,确保包含等效的加密逻辑和 RFC 9449 逻辑。所有情况下均须严格遵循声明派生逻辑:

A. DPoP Proof JWT 声明派生规则(createDPoPProof)

在 createDPoPProof 中生成 DPoP Proof JWT 时:

1. JOSE 标头(typ、alg、jwk):

// Header
{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": await exportPublicJWK(publicKey)
}

2. 负载声明:

  • "htm":大写的 HTTP 方法(令牌请求使用 "POST")。
  • "htu":使用 sanitizeHTU(htu) 去除查询参数和哈希片段后的目标 URI。令牌请求的目标 URI 为 https://oauth2.googleapis.com/token。
  • "iat":当前的整数纪元时间戳,单位为秒 (Math.floor(Date.now() / 1000))。
  • "jti"(关键不变量):
    1. 如果向 createDPoPProof 提供了显式的 jti 参数,则优先使用该确切字符串。
    2. 否则,如果提供了 authCode 参数(在初始代码交换期间),则设置 jti = await calculateAuthCodeJti(authCode),其中 calculateAuthCodeJti 计算 base64url(sha256(authCode)),以确保 DPoP 证明在加密学上与授权码绑定。
    3. 只有在既未提供 jti 也未提供 authCode 时,才通过 generateRandomString() 生成新的加密安全随机字符串(例如,将 crypto.getRandomValues(new Uint8Array(24)) 进行 base64url 编码)。
  • "ath"(可选):如果为 RFC 9449 资源请求提供了 accessToken 参数,则通过 calculateATH(accessToken) 计算 base64url(sha256(accessToken)) 并将其注入负载(RFC 9449 第 6.1 节)。
  • "nonce"(可选):如果提供了 nonce 参数,则将其直接注入负载。
B. 显式导出签名
// 1. Key generation & JWK export
export async function generateDPoPKeyPair() // -> { publicKey, privateKey } (private key extractable=false)
export async function exportPublicJWK(publicKey) // -> { kty: 'EC', crv: 'P-256', x, y }

// 2. Proof generation & validation
export async function createDPoPProof({ privateKey, publicKey, htm, htu, nonce, accessToken, authCode, jti }) // -> signed JWT string
export async function verifyDPoPProof(dpopProofJwt) // -> { isValid: boolean, header, payload, error }
export function sanitizeHTU(htu) // -> URL stripped of query and hash: const u = new URL(htu); return `${u.origin}${u.pathname}`;

// 3. Cryptographic & encoding utilities
export function base64UrlEncode(buffer) // -> Uint8Array/ArrayBuffer to base64url string without '=' padding
export function base64UrlDecode(str) // -> base64url string to Uint8Array/Buffer
export function stringToBase64Url(str) // -> UTF-8 string to base64url
export function base64UrlToString(str) // -> base64url to UTF-8 string
export function generateRandomString(byteLength = 32) // -> cryptographic random base64url string
export async function calculateATH(accessToken) // -> base64url(sha256(accessToken)) per RFC 9449 Sec 6.1
export async function calculateAuthCodeJti(code) // -> base64url(sha256(code))
export async function generatePKCE() // -> { codeVerifier (>=43 chars), codeChallenge, codeChallengeMethod: 'S256' }

3. 令牌端点与资源请求工作流

与 Google 的 OAuth 2.0 平台集成时:

  1. 令牌端点请求(oauth2.googleapis.com/token):
    • 发送代码交换(grant_type=authorization_code)和令牌刷新(grant_type=refresh_token)的 POST 请求时,在 DPoP HTTP 标头中附加 DPoP Proof JWT: `DPoP: ${proofJwt}`。
  2. 资源 API 请求:
    • Google 的令牌端点返回 "token_type": "Bearer"。向下游 Google API(例如 Calendar、Drive、Gmail)发送请求时,使用标准的 `Authorization: Bearer ${accessToken}` 标头,不附加 DPoP 标头。
  3. 单次重试的 nonce 挑战循环与工作流隔离:
    • 如果 Google 的令牌端点返回 HTTP 400 Bad Request,且错误为 error: "use_dpop_nonce",响应标头中还包含 "DPoP-Nonce":
      • 工作流隔离: Google 的授权服务器会在授权码交换与令牌刷新之间强制执行工作流隔离,并返回 HTTP 400 use_dpop_nonce 挑战,以建立新的 nonce 命名空间。这是符合 RFC 标准的协议行为,并非服务器故障。
      • 将新的 nonce 缓存到客户端状态(this.dpopNonce)。
      • 立即合成新的 DPoP Proof JWT,加入更新后的 nonce 声明和新的 jti。
      • 将失败的令牌请求恰好重放一次。如果重试请求失败,立即终止并返回错误,以防止无限递归。

4. 简明 Agent 出站协议

在此技能下收到合成或输出代码交付物的提示时,应优先返回简洁、可直接导入的代码块,不要添加多余的对话式前言或重复的填充内容。对于概念性或架构类询问,提供直接的标准答复。

5. 参考资料与支持文档

开发者文档(Google for Developers)
开发者知识 MCP 服务
  • 配备 Model Context Protocol(MCP)的 Agent 可以通过 Google Developer Knowledge MCP 服务 (npx -y @google/mcp-developer-knowledge-server),使用 developer_knowledge:search_documents 和 developer_knowledge:get_documents 查询实时 Google 开发者文档。
标准与 RFC 规范

评论

支持 Markdown