首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在后端验证Apple StoreKit2在应用程序中的购买收据jwsRepresentation (节点理想情况下,但一切正常)

在后端验证Apple StoreKit2在应用程序中的购买收据jwsRepresentation (节点理想情况下,但一切正常)
EN

Stack Overflow用户
提问于 2021-10-04 15:47:57
回答 4查看 2.5K关注 0票数 7

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

很容易解码有效载荷,但我找不到苹果在任何地方签署这些JWS/JWT的公钥。在使用JWT时,您只需使用节点jsonwebtoken库,并传入签名者公钥或共享密钥,这些密钥可以是配置的,也可以是从JWK获取的。

我可以使用node-jose j.JWS.createVerify().verify(jwsString, {allowEmbeddedKey: true}).then(r => obj = r)轻松地解码JWS,这给了我一个类似于以下的对象:

代码语言:javascript
复制
 {
  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: ''
  }
}

而且它很容易JSON.parse的有效载荷,并获得我想要的数据。但是,如何使用x5c字段中的证书链来验证其真实性

谢谢!

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2022-08-25 06:50:51

从所有信息中拼凑起来是很有挑战性的,但是下面是如何在NodeJS中这样做的。请注意,最新的Node支持内置密码,这使得它更容易。这是我的代码和必要的注释。

代码语言:javascript
复制
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);
}

祝好运!

票数 6
EN

Stack Overflow用户

发布于 2021-11-05 18:27:53

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

苹果拥有他们的网站所需的证书。您已经下载了根证书(因为这是一个签名整个链的证书),但是您也可以获得中间证书。

下载后,将其转换为.pem

代码语言:javascript
复制
 $ openssl x509 -inform der -in apple_root.cer -out apple_root.pem

然后,您所需要做的就是对照JWS中的JWS验证它们(下面是PHP中的,但是您应该得到gist):

代码语言:javascript
复制
if (openssl_x509_verify($jws_root_cert, $downloaded_apple_root_cert) == 1){
    //valid
}

希望这对其他人都有帮助!

票数 7
EN

Stack Overflow用户

发布于 2022-06-15 23:41:38

您需要使用headerpayload来验证sign,就像在WWDC视频中说的那样:

https://developer.apple.com/videos/play/wwdc2022/10040/ https://developer.apple.com/videos/play/wwdc2021/10174/

但是如果你没有JWT的知识,那么做这件事比你想象的要复杂得多,因为没有来自Apple的文档可以这样做,他们只对你说“使用你最喜欢的密码库来验证数据”。

因此,在做了大量的研究之后,我终于找到了一个使用PHP 8.1Laravel的解决方案。

首先,您需要安装这个库https://github.com/firebase/php-jwt

代码语言:javascript
复制
composer require firebase/php-jwt

然后,您需要实现以下方法来验证来自事务的JWT

代码语言:javascript
复制
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

...

public function validateJwt($jwt)
{
    $components = explode('.', $jwt);

    if (count($components) !== 3) {
        throw new \Exception('JWS string must contain 3 dot separated component.');
    }

    $header = base64_decode($components[0]);
    $headerJson = json_decode($header,true);

    $this->validateAppleRootCA($headerJson);

    $jwsParsed = (array) $this->decodeCertificate($jwt, $headerJson, 0);

    for ($i = 1; $i < count($headerJson) - 1; $i++) {
        $this->decodeCertificate($jwt, $headerJson, $i);
    }

    // If the signature and the jws is invalid, it will thrown an exception

    // If the signature and the jws is valid, it will create the $decoded object

    // You can use the $decoded object as an array if you need:

    $transactionId = $jwsParsed['transactionId'];
}

private function validateAppleRootCA($headerJson)
{
    $lastIndex = array_key_last($headerJson['x5c']);
    $certificate = $this->getCertificate($headerJson, $lastIndex);

    //  As Oliver Zhang says in their NodeJS example, download the .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
    // In Laravel, this location is at storage/keys/AppleRootCA-G3.pem
    $appleRootCA = file_get_contents(storage_path('keys/AppleRootCA-G3.pem'));

    if ($certificate != $appleRootCA) {
        throw new \Exception('jws invalid');
    }
}

private function getCertificate($headerJson, $certificateIndex)
{
    $certificate = '-----BEGIN CERTIFICATE-----'.PHP_EOL;
    $certificate .= chunk_split($headerJson['x5c'][$certificateIndex],64,PHP_EOL);
    $certificate .= '-----END CERTIFICATE-----'.PHP_EOL;

    return $certificate;
}

private function decodeCertificate($jwt, $headerJson, $certificateIndex)
{
    $certificate = $this->getCertificate($headerJson, 0);

    $cert_object = openssl_x509_read($certificate);
    $pkey_object = openssl_pkey_get_public($cert_object);
    $pkey_array = openssl_pkey_get_details($pkey_object);
    $publicKey = $pkey_array['key'];
    $jwsParsed = null;

    try {
        $jwsDecoded = JWT::decode($jwt, new Key($publicKey, 'ES256'));
        $jwsParsed = (array) $jwsDecoded;
    } catch (SignatureInvalidException $e) {
        throw new \Exception('signature invalid');
    }

    return $jwsParsed;
}

要调用函数,需要从事务传递jwt

代码语言:javascript
复制
$jwt = 'eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUl...';
validateJwt($jwt);
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69438848

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档