如何在Node.js后端验证StoreKit2内购收据JWS Representation的签名合法性
StoreKit2 JWS 验签实现方案
实现原理
- StoreKit2返回的JWS头里的
x5c字段是有序证书链:第一个是签署当前JWS的叶子证书,第二个是中间CA证书,第三个是Apple根CA证书 - 完整验签流程分两步:1. 验证x5c证书链的合法性,确保证书链由Apple信任的根CA签发;2. 用证书链里的叶子证书公钥验证JWS本身的签名
具体实现步骤
1. 提前准备Apple官方G3根证书
先把Apple Root CA - G3的公钥PEM格式硬编码到你的项目配置里,避免请求外部资源带来的安全风险:
// 配置项:Apple G3根证书PEM格式,可从Apple官方公开文档获取固定值 const APPLE_ROOT_CA_G3 = `-----BEGIN CERTIFICATE----- MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN MTQwNDI5MTg0NjQ2WhcNNDQwNDI5MTg0NjQ2WjBnMRswGQYDVQQDDBJBcHBsZSBS b290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9y aXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49 AgEGBSuBBAAiA2IABNxnmCFcWbrhWj68mOulX5Kv65hBn8PnI4L9Pn8qZJxYj7kV // 此处省略剩余官方根证书内容,替换为你获取的完整Apple G3根证书即可 -----END CERTIFICATE-----`
2. 完整验签代码实现
依赖Node.js内置crypto模块和node-jose库实现:
const crypto = require('crypto'); const jose = require('node-jose'); async function verifyStoreKitJWS(jwsString) { // 第一步:解析JWS获取header和x5c证书链 const parsedJWS = await jose.JWS.createVerify().verify(jwsString, { allowEmbeddedKey: true }); // 将x5c中base64格式的证书转换为X509证书对象 const x5cCerts = parsedJWS.header.x5c.map(certRaw => { const certPem = `-----BEGIN CERTIFICATE-----\n${certRaw.match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`; return new crypto.X509Certificate(certPem); }); // 第二步:验证证书链合法性 const trustedRootCa = new crypto.X509Certificate(APPLE_ROOT_CA_G3); let isChainValid = true; // 从叶子证书到根证书依次校验签发关系 for (let i = 0; i < x5cCerts.length - 1; i++) { const currentCert = x5cCerts[i]; const issuerCert = x5cCerts[i + 1]; if (!currentCert.verify(issuerCert.publicKey)) { isChainValid = false; break; } } // 校验链尾根证书是否为信任的Apple G3根证书 if (x5cCerts.at(-1).fingerprint256 !== trustedRootCa.fingerprint256) { isChainValid = false; } if (!isChainValid) { throw new Error('证书链验证失败,非Apple官方签发'); } // 第三步:用验证通过的叶子证书公钥重新校验JWS签名,防止数据篡改 const leafPublicKeyJwk = x5cCerts[0].publicKey.export({ format: 'jwk' }); const verifyKey = await jose.JWK.asKey(leafPublicKeyJwk); const finalVerifyResult = await jose.JWS.createVerify(verifyKey).verify(jwsString); // 所有校验通过,返回payload数据 return JSON.parse(finalVerifyResult.payload.toString()); }
3. 注意事项
- 禁止直接使用
allowEmbeddedKey: true的解析结果作为可信数据,必须先完成证书链校验,避免恶意构造的JWS被误判为合法 - Apple根证书不要动态从外部拉取,避免DNS劫持等风险,直接硬编码到项目中即可
- 可根据业务需要额外校验证书的有效期、主题字段,进一步提升安全性
内容的提问来源于stack exchange,提问作者max
相关产品推荐
相关产品推荐

