在后端验证 Apple StoreKit2 应用内购买收据 jwsRepresentation (理想情况下是节点,但任何方法都可以)

mxc*_*mxc 9 storekit in-app-purchase ios jwt

如何在 Node 后端验证来自 StoreKit2 的应用内购买 JWS 表示?

Its easy enough to decode the payload, but I can't find public keys that Apple uses to sign these JWS/JWTs anywhere. Any other time I've worked with JWTs, you simply used the node jsonwebtoken library and passed in the signers public key or shared secret key, either configured or fetched from a JWK.

I can easily decode the JWS using node-jose j.JWS.createVerify().verify(jwsString, {allowEmbeddedKey: true}).then(r => obj = r) which gives me an object like:

 {
  protected: [ 'alg', 'x5c' ],
  header: {
    alg: 'ES256',
    x5c: [
      'MIIEMDueU3...',
      'MII..., 
'MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0...'  
    ]
  },
  payload: <Buffer 7b 22 74 72 61 6e 73 61 63 74 69 6f 6e 49 64 22 3a 22 31 30 30 30 30 30 30 38 38 36 39 31 32 38 39 30 22 2c 22 6f 72 69 67 69 6e 61 6c 54 72 61 6e 73 ... 420 more bytes>,
  signature: <Buffer f8 85 65 79 a1 dc 74 dd 90 80 0a a4 08 85 30 e7 22 80 4c 20 66 09 0b 84 fc f4 e5 57 53 da d5 6f 13 c6 8f 56 e8 29 67 5c 95 a6 27 33 47 1e fe e9 6e 41 ... 14 more bytes>,
  key: JWKBaseKeyObject {
    keystore: JWKStore {},
    length: 256,
    kty: 'EC',
    kid: 'Prod ECC Mac App Store and iTunes Store Receipt Signing',
    use: '',
    alg: ''
  }
}

Run Code Online (Sandbox Code Playgroud)

And its easy to JSON.parse the payload and get the data I want. But, how can i verify that its authentic using the certificate chain in the x5c field

Thank you!

Oli*_*ang 9

将所有信息拼凑在一起非常具有挑战性,但以下是如何在 NodeJS 中做到这一点。请注意,最新的 Node 支持内置加密,这使得它变得更加容易。这是我的代码以及必要的注释。

const jwt = require('jsonwebtoken');
const fs = require('fs');
const {X509Certificate} = require('crypto');

async function decode(signedInfo) {

    // MARK: - Creating certs using Node's new build-in crypto
    function generateCertificate(cert) {
        // MARK: - A simple function just like the PHP's chunk_split, used in generating pem. 
        function chunk_split(body, chunklen, end) {
            chunklen = parseInt(chunklen, 10) || 76;
            end = end || '\n';
            if (chunklen < 1) {return false;}
            return body.match(new RegExp(".{0," + chunklen + "}", "g")).join(end);
        }
        return new X509Certificate(`-----BEGIN CERTIFICATE-----\n${chunk_split(cert,64,'\n')}-----END CERTIFICATE-----`);
    }

    // MARK: - Removing the begin/end lines and all new lines/returns from pem file for comparison
    function getPemContent(path) {
        return fs.readFileSync(path)
            .toString()
            .replace('-----BEGIN CERTIFICATE-----', '')
            .replace('-----END CERTIFICATE-----', '')
            .replace(/[\n\r]+/g, '');
    }



    // MARK: - The signed info are in three parts as specified by Apple
    const parts = signedInfo.split('.');
    if (parts.length !== 3) {
        console.log('The data structure is wrong! Check it! ');
        return null;
    }
    // MARK: - All the information needed for verification is in the header
    const header = JSON.parse(Buffer.from(parts[0], "base64").toString());

    // MARK: - The chained certificates
    const certificates = header.x5c.map(cert => generateCertificate(cert));
    const chainLength = certificates.length;

    // MARK: - Leaf certificate is the last one
    const leafCert = header.x5c[chainLength-1];
    // MARK: - Download .cer file at https://www.apple.com/certificateauthority/. Convert to pem file with this command line: openssl x509 -inform der -in AppleRootCA-G3.cer -out AppleRootCA-G3.pem
    const AppleRootCA = getPemContent('AppleRootCA-G3.pem');
    // MARK: - The leaf cert should be the same as the Apple root cert
    const isLeafCertValid = AppleRootCA === leafCert;
    if (!isLeafCertValid) {
        console.log('Leaf cert not valid! ');
        return null;
    }

    // MARK: If there are more than one certificates in the chain, we need to verify them one by one 
    if (chainLength > 1) {
        for (var i=0; i < chainLength - 1; i++) {
            const isCertValid = certificates[i].verify(certificates[i+1].publicKey);
            if (!isCertValid) {
                console.log(`Cert ${i} not valid! `);
                return null;
            }
        }
    }

    return jwt.decode(signedInfo);
}
Run Code Online (Sandbox Code Playgroud)

祝你好运!


Rai*_*idi 8

终于明白了这一点。事实证明,我们需要一个“硬编码”证书来检查。

Apple 在其网站上提供了所需的证书。您已经下载了根证书(因为这是签署整个链的证书),但您也可以获得中间证书。

下载后,将其转换为.pem

 $ openssl x509 -inform der -in apple_root.cer -out apple_root.pem
Run Code Online (Sandbox Code Playgroud)

那么您需要做的就是根据 JWS 中的内容验证它们(以下内容位于 中PHP,但您应该了解要点):

if (openssl_x509_verify($jws_root_cert, $downloaded_apple_root_cert) == 1){
    //valid
}
Run Code Online (Sandbox Code Playgroud)

希望这对其他人有帮助!