在 JS 中手动验证 GCP JWT 签名

Verifying GCP JWT signature manually in JS

我已经能够在 jwt.io 的网站上验证 GCP ID 令牌 UI 没问题,但我很难在 JS 代码中复制它。

我使用了 josejsrsasign 库,但收效甚微。

一些我自己的代码来获得基础知识

function decodeJWT(jwtString: string) {
  const jwt = jwtString.match(
    /(?<header>[^.]+)\.(?<payload>[^.]+)\.(?<signature>[^.]+)/
  ).groups;

  // For simplicity trust that the urlBase64toStr function works
  // The parsed JWT is identical to what I see on jwt.io
  jwt.header = JSON.parse(urlBase64toStr(jwt.header));
  jwt.payload = JSON.parse(urlBase64toStr(jwt.payload));

  return jwt;
}

const jwt = decodeJWT('<....JWT string here......>')

const encoder = new TextEncoder();
const byteArrays = {
    signature: encoder.encode(jwt.signature),
    body: encoder.encode(
      JSON.stringify(jwt.header) + "." + JSON.stringify(jwt.payload)
    )
};

// Google's public certs at https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com
const cert = '-----BEGIN CERTIFICATE-----\n<........>' 

jose 验证得到 false

  const joseKey = await jose.importX509(cert, "RS256");
  console.log(
      await crypto.subtle.verify(
        joseKey.algorithm.name,
        joseKey,
        byteArrays.signature,
        byteArrays.body
      )
  ) 

// Note the following works

console.log(jose.jwtVerify(jwtRaw, joseKey))

使用 jsrsaassign 也会得到 false

  var c = new jsrsasign.X509();
  c.readCertPEM(cert);

  var jsRsaAssignKey = await crypto.subtle.importKey(
    "jwk",
    jsrsasign.KEYUTIL.getJWKFromKey(c.getPublicKey()),
    { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    true,
    ["verify"]
  ); // Gets RSAKey first, then transforms into a JWK, then imported to get CryptoKey
  console.log(
      await crypto.subtle.verify(
        jsRsaAssignKey.algorithm.name,
        jsRsaAssignKey,
        byteArrays.signature,
        byteArrays.body
      )
  )

我哪里错了?

注意:请不要推荐 NodeJS 库。我需要的环境 运行 脚本不支持 Node 核心模块。

crypto.subtle.verify(algo, key, signature, data)参数中,

  1. 提供给函数的 signature 应该是 URL-base64 解码的 版本的原始签名字符串的 TypedArray (Uint8Array)智威汤逊。它应该 而不是 是原始签名字符串的 TypedArray。
  2. 提供给函数的 data 应该是原始 JWT 字符串中 <header>.<payload> 提供的 字符串的 TypedArray。它应该 而不是 是解码、解析然后字符串化 header 和看起来像 {"typ": "JWT"}.{"iss": "https://issuer.com/"}
  3. 的有效负载

还必须注意,默认情况下 JS built-in TextEncoder 不会返回正确的 Uint8Array。不要使用它将字符串转换为 TypedArray,而是使用下面给出的 strToUint8Array 函数。

function strToUint8Array(value: string): Uint8Array {
  return Uint8Array.from(
    Array.from(value).map((letter) => letter.charCodeAt(0))
  );
}

感谢@John Hanley对签名解码的部分回答。

// Note the following works

console.log(await jose.jwtVerify('<....JWT string here......>', joseKey))

那么你不妨使用它,jose 是一个在 Node.js 之外工作的通用模块。

尽管如此,您的手动验证输入是错误的。 data 应该只是没有第二个点和签名的 jwt,编码为 base64url,表示为 ArrayBufferView。 signature应该是从base64url解码出来的JWT签名部分,表示为ArrayBufferView。