验证苹果支付支付令牌签名

Validating apple pay payment token signature

由于某些原因,我不得不在没有支付平台的情况下处理apple pay payment token。根据official document,我需要先“验证签名”。并且签名是 base64 编码的分离 PKCS #7 签名。我想用 node.js 或 openssl.

验证它

由于非常方便的加密工具 node-forge 尚不支持“ECDSA with sha256”(link),而且我找不到其他替代品。我转向了openssl。通过一些调查,签名似乎以“CMS signed data”格式打包。所以我发现 openssl 手册中的这个命令应该能够完成这项工作:

openssl cms -verify -inform DER -in signature.der -content content.txt

Apple 文档说“确保签名是 ephemeralPublicKey、数据、transactionId 和 applicationData 键的串联值的有效 ECDSA 签名”。因此,我通过从我的测试令牌中连接这些字段来生成我的测试内容。但结果是:

Verification failure
C0:25:34:08:01:00:00:00:error:CMS routines:CMS_SignerInfo_verify_content:verification failure:crypto/cms/cms_sd.c:901:
C0:25:34:08:01:00:00:00:error:CMS routines:CMS_verify:content verify error:crypto/cms/cms_smime.c:399:

cms_sd.c:901 是验证 igest 的签名者所以我认为这个命令是我需要的。那我做错了什么?

更新:我发现PKI.js可以完成这项工作。详细答案如下。

下面来自 PKI.js example 的代码片段展示了如何解析现有的 CMSSignedData。

const testData = "<TOKEN_SIGNATURE>";

const cmsSignedBuffer = stringToArrayBuffer(fromBase64(testData));
const asn1 = asn1js.fromBER(cmsSignedBuffer);
const cmsContentSimpl = new ContentInfo({ schema: asn1.result });
const cmsSignedSimpl = new SignedData({ schema: cmsContentSimpl.content });

在此之后,我们可以提取证书和签名者信息以进行进一步处理。

检查 OID:

cmsSignedSimpl.certificates[0].extensions.find(x => x.extnID === '1.2.840.113635.100.6.29') // leaf certificate
cmsSignedSimpl.certificates[1].extensions.find(x => x.extnID === '1.2.840.113635.100.6.2.14') // intermediate certificate

验证签名和信任链:

cmsSignedSimpl.verify({
    signer: 0,
    trustedCerts: [AppleRootCA_G3],
    data: signedData,
    checkChain: true, // check x509 chain of trust
    extendedMode: true, // enable to show signature validation result
  });

查看演唱时间:

const attrs = cmsSignedSimpl.signerInfos[0].signedAttrs.attributes;
const signingTimeAttr = attrs.find(x => x.type === '1.2.840.113549.1.9.5');
const signingTime = new Date(signingTimeAttr.value[0].toDate());