apple / app-store-server-library-node

MIT License
159 stars 31 forks source link

"PEM routines::no start line" With "SignedDataVerifier" in Production environment #177

Closed guiann closed 1 month ago

guiann commented 1 month ago

Hello everyone!

I'm validating subcription from IAP Receipt using this library. Everything is fine during dev in Apple's "Sandbox" mode.

Once I upload my app and run it from a testFlight build I've go this error:

Error: error:0480006C:PEM routines::no start line at new X509Certificate (node:internal/crypto/x509:119:21) at /opt/app/node_modules/@apple/app-store-server-library/jws_verification.ts:55:65 at Array.map () at new SignedDataVerifier (/opt/app/node_modules/@apple/app-store-server-library/jws_verification.ts:55:53)

Here is how I initiate the call:


    const issuerId = '6f1337a3-1234-4929-9ccd-774e8c02fa18';
    const keyId = 'FG7N3NY68Y';
    const bundleId = 'com.appname.app';
    const teamId = '6JZPD4JMSJ';

    // appAppleId is required when the environment is Production
    const appAppleId = teamId+"."+bundleId;
    const appleEnv = Environment.PRODUCTION;

    const filePath = path.join(ROOT_FOLDER, `/keys/AppleApi_AuthKey_${keyId}.p8`);
    const privateKey = fs.readFileSync(filePath, 'utf8');

    const client = new AppStoreServerAPIClient(privateKey, keyId, issuerId, bundleId, appleEnv);

    const appleRootCAs = [
      fs.readFileSync(path.join(ROOT_FOLDER, '/keys/AppleRootCer/AppleIncRootCertificate.cer')),
      fs.readFileSync(path.join(ROOT_FOLDER, '/keys/AppleRootCer/AppleComputerRootCertificate.cer')),
      fs.readFileSync(path.join(ROOT_FOLDER, '/keys/AppleRootCer/AppleRootCA-G2.cer')),
      fs.readFileSync(path.join(ROOT_FOLDER, '/keys/AppleRootCer/AppleRootCA-G3.cer')),
    ];
    const enableOnlineChecks = true;

    console.log('privateKey = ', privateKey);

    const verifier = new SignedDataVerifier(appleRootCAs, enableOnlineChecks, appleEnv, bundleId, appAppleId);

I'm first trying to run in Production env then in sandbox as requested in apple's documentation. The files are correctly read. Login private key shows:

privateKey = -----BEGIN PRIVATE KEY----- MIGTAbEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4n35kqKOEJA5ITBZ lf5QfWlaj1re4R60jFflAmzfJQKgCgYIKoZIzj0DABehRANCAATvNxWUh/WGfw+q OW1kMsTRNyBN7IwS861VjFvtQKN7r8wlvG1jTw+vTpsv/84xZw/H+IDmFLnOkAbS yRfk1PcT -----END PRIVATE KEY-----

more log values : enableOnlineChecks : true environment : Production bundleId : com.appname.app appAppleId : 6JZPD4JMSJ.com.appname.app <= is this appId format correct ?

(All keys and appname/ team id have been change for obvious security reason)

Can you see anything I do wrong ? Is Apple root Certificate loading ok ? should we "enableOnlineChecks" ? Tank you for your help.

alexanderjordanbaker commented 1 month ago

Once I upload my app and run it from a testFlight build I've go this error:

To confirm, the library is not running on the app, you are just decoding a signed transaction received from the app, correct?

I'm first trying to run in Production env then in sandbox as requested in apple's documentation.

I don't believe our current documentation recommends this. We always recommend sending the expected environment along with the signed transaction, and then just using that in your verification directly so you don't have to try one and then the other.

appAppleId : 6JZPD4JMSJ.com.appname.app

No it should be a numerical value.

Is Apple root Certificate loading ok ?

This error appears to be when parsing the appleRootCAs files, please check the array of appleRootCAs contains valid values.

guiann commented 1 month ago

"you are just decoding a signed transaction received from the app, correct?"

=> Yep this is correct.

"I'm first trying to run in Production env then in sandbox as requested in apple's documentation. I don't believe our current documentation recommends this"

=> I'm pretty sure I've read this somewhere and the apple app verification team sent this to me last week: "The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead."

I'll check my root certificates and see if I can fix this .

Thank you for the answer.

alexanderjordanbaker commented 1 month ago

The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead.

These are not receipts, they are signed transactions, that advice applies when using the deprecated receipts from device and the deprecated verifyReceipt endpoint, it should no long be needed.

guiann commented 1 month ago

Thank you for this precision, it is very confusing to find so much information and docs for the deprecated method.

"Validate Receipt" meant to apply subscription from a previous purchase using this process (trunated code for simplicity)

  const receiptUtil = new ReceiptUtility();
  const transactionId = receiptUtil.extractTransactionIdFromAppReceipt(appReceipt);

  const client = new AppStoreServerAPIClient(privateKey, keyId, issuerId, bundleId, appleEnv);
  const response = client.getTransactionHistory(transactionId, revisionToken, transactionHistoryRequest);
  transactions.concat(response.signedTransactions);

  verifier = new SignedDataVerifier(appleRootCAs, enableOnlineChecks, appleEnv, bundleId, appAppleId);
  for each (transactions) verifier.verifyAndDecodeTransaction(transaction);

This whole process is needed while "validating receipts" right ?

When should we test with Sandbox then ? (when running in testFlight mode before release, this is with the server production env, but still in Apple Sandbox mode right? we cannot know when sandbox is needed or not)

alexanderjordanbaker commented 1 month ago

@guiann Generally we recommend using Storekit 2 on the client side and directly validating these with the SignedDataVerifier. However, if you need to support clients using deprecated Storekit, then yes, this appears to be the truncated process to extract data from a legacy receipt and get transaction data for the user from the App Store Server API