如何使用Jose库验证Apple签名的JWS(x5c证书链)
正确的Apple Store Webhook签名验证流程(基于Jose库)
Apple Store Webhook(Notification API v2)的签名验证核心是确认JWS的合法性,同时验证附带的证书链完整可信。以下是针对你的场景的正确实现方案:
现有代码的问题
你当前的实现存在两个关键错误:
- 证书不是JWT格式,不能用
jwtVerify来验证证书间的签名关系 - 缺少对JWS本身的签名验证,仅验证证书链无法确认请求内容未被篡改
完整验证代码
import { jwtVerify, importX509, verifyCertificateChain, decodeJwt } from 'jose'; async function verifyAppleWebhook(signedPayload, env) { try { // 1. 解码JWS获取header中的证书链 const decodedJwt = decodeJwt(signedPayload); const x5cChain = decodedJwt.header.x5c; if (!x5cChain || x5cChain.length < 2) { throw new Error('Invalid certificate chain in JWS header'); } // 2. 导入所有证书:叶证书(签名JWS的证书)、中间证书、本地根证书 const leafCertificate = await importX509(x5cChain[0], 'ES256'); const intermediateCertificate = await importX509(x5cChain[1], 'ES256'); const rootCertificate = await importX509(env.APPLE_ROOT_CERTIFICATION, 'ES256'); // 3. 验证证书链:叶证书由中间证书签名,中间证书由根证书签名 await verifyCertificateChain(leafCertificate, [intermediateCertificate, rootCertificate]); // 4. 验证JWS本身的签名:确认请求内容是叶证书签署的 const { payload } = await jwtVerify(signedPayload, leafCertificate, { issuer: 'https://appleid.apple.com', // Apple固定签发者 audience: '你的应用Bundle ID' // 替换为你的App Bundle ID }); // 5. 可选:验证叶证书的主题和有效期,提升安全性 const leafCertInfo = leafCertificate.toJSON(); const now = Math.floor(Date.now() / 1000); if (!leafCertInfo.subject.includes('Apple Push Notification service') || leafCertInfo.notBefore > now || leafCertInfo.notAfter < now) { throw new Error('Invalid leaf certificate'); } return payload; // 验证通过,返回解析后的通知内容 } catch (err) { console.error('Webhook verification failed:', err); return new Response('Failed to verify Apple webhook signature.', { status: 401 }); } }
关键注意事项
- 算法固定:Apple Notification API v2仅使用
ES256算法,不要更换其他算法 - 证书导入方式:必须用
importX509导入证书,x5c数组中的内容是完整X.509证书,不是SPKI格式公钥 - JWT校验参数:
issuer固定为https://appleid.apple.com,audience必须严格匹配你的应用Bundle ID,防止伪造请求 - 链验证工具:Jose的
verifyCertificateChain会自动完成证书链的层级签名验证,无需手动逐个校验
内容的提问来源于stack exchange,提问作者PastaLover
相关产品推荐
相关产品推荐

