在 JS 中手动验证 GCP JWT 签名
Verifying GCP JWT signature manually in JS
我已经能够在 jwt.io 的网站上验证 GCP ID 令牌 UI 没问题,但我很难在 JS 代码中复制它。
我使用了 jose
和 jsrsasign
库,但收效甚微。
一些我自己的代码来获得基础知识
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)
参数中,
- 提供给函数的
signature
应该是 URL-base64 解码的 版本的原始签名字符串的 TypedArray (Uint8Array)智威汤逊。它应该 而不是 是原始签名字符串的 TypedArray。
- 提供给函数的
data
应该是原始 JWT 字符串中 <header>.<payload>
提供的 字符串的 TypedArray。它应该 而不是 是解码、解析然后字符串化 header 和看起来像 {"typ": "JWT"}.{"iss": "https://issuer.com/"}
的有效负载
还必须注意,默认情况下 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。
我已经能够在 jwt.io 的网站上验证 GCP ID 令牌 UI 没问题,但我很难在 JS 代码中复制它。
我使用了 jose
和 jsrsasign
库,但收效甚微。
一些我自己的代码来获得基础知识
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)
参数中,
- 提供给函数的
signature
应该是 URL-base64 解码的 版本的原始签名字符串的 TypedArray (Uint8Array)智威汤逊。它应该 而不是 是原始签名字符串的 TypedArray。 - 提供给函数的
data
应该是原始 JWT 字符串中<header>.<payload>
提供的 字符串的 TypedArray。它应该 而不是 是解码、解析然后字符串化 header 和看起来像{"typ": "JWT"}.{"iss": "https://issuer.com/"}
的有效负载
还必须注意,默认情况下 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。