4

Apple offers two documents about receipt validation, with apparently contradictory statements.

In "Verifying Store Receipts":

Note: On iOS, the contents and format of the store receipt is private and subject to change. Your application should not attempt to parse the receipt data directly.

Yet, in "In-App Purchase Receipt Validation on iOS" sample code is provided in which the store receipt is parsed and verified, as part of a "mitigation strategy" for a security vulnerability:

// Check the validity of the receipt.  If it checks out then also ensure the transaction is something
// we haven't seen before and then decode and save the purchaseInfo from the receipt for later receipt validation.
- (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)transaction
{
    if (!(transaction && transaction.transactionReceipt && [transaction.transactionReceipt length] > 0))
    {
        // Transaction is not valid.
        return NO;
    }

    // Pull the purchase-info out of the transaction receipt, decode it, and save it for later so
    // it can be cross checked with the verifyReceipt.
    NSDictionary *receiptDict       = [self dictionaryFromPlistData:transaction.transactionReceipt];
    NSString *transactionPurchaseInfo = [receiptDict objectForKey:@"purchase-info"];
    NSString *decodedPurchaseInfo   = [self decodeBase64:transactionPurchaseInfo length:nil];
    NSDictionary *purchaseInfoDict  = [self dictionaryFromPlistData:[decodedPurchaseInfo dataUsingEncoding:NSUTF8StringEncoding]];

    NSString *transactionId         = [purchaseInfoDict objectForKey:@"transaction-id"];
    NSString *purchaseDateString    = [purchaseInfoDict objectForKey:@"purchase-date"];
    NSString *signature             = [receiptDict objectForKey:@"signature"];

    // Convert the string into a date
    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    [dateFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];

    NSDate *purchaseDate = [dateFormat dateFromString:[purchaseDateString stringByReplacingOccurrencesOfString:@"Etc/" withString:@""]];


    if (![self isTransactionIdUnique:transactionId])
    {
        // We've seen this transaction before.
        // Had [transactionsReceiptStorageDictionary objectForKey:transactionId]
        // Got purchaseInfoDict
        return NO;
    }

    // Check the authenticity of the receipt response/signature etc.

    BOOL result = checkReceiptSecurity(transactionPurchaseInfo, signature,
                                       (__bridge CFDateRef)(purchaseDate));

    if (!result)
    {
        return NO;
    }

    // Ensure the transaction itself is legit
    if (![self doTransactionDetailsMatchPurchaseInfo:transaction withPurchaseInfo:purchaseInfoDict])
    {
        return NO;
    }

    // Make a note of the fact that we've seen the transaction id already
    [self saveTransactionId:transactionId];

    // Save the transaction receipt's purchaseInfo in the transactionsReceiptStorageDictionary.
    [transactionsReceiptStorageDictionary setObject:purchaseInfoDict forKey:transactionId];

    return YES;
}

If I understand correctly, if I verify the receipt my app could stop working when Apple decides to change the format of the receipt.

And if I don't verify the receipt, I'm not following Apple's "mitigation strategy" and my app is vulnerable to attacks.

Damned if I do, damned if I don't. Is there something I'm missing?

hpique
  • 119,096
  • 131
  • 338
  • 476

2 Answers2

3

They heavily recommend to use your own server as an intermediary for validation, as this will allow a clear and secure passage to the App Store for all versions of iOS. That's really the best route to not be damned either way.

If you must perform validation directly from the device to the App Store, then you utilise their mitigation strategy only when the application is run on 5.1.x and below. For iOS6 and above, use the recommended means provided.

Whilst it was always the case that you shouldn't parse the receipt directly, the discovered vulnerability put Apple between a rock and a hard place in how to address this, and decided that application developers implement a check. It means when a user updates the application, the receipts are now secured again (irrespective of iOS version), thereby providing better fix coverage. As a side effect, it means you have to break from how you should normally do it (but Apple have give you permission to do so).

I agree the documentation isn't wholly clear on this, and could be clarified a bit more (you should give them feedback from the documentation pages at the bottom). Apple have published a mitigation strategy, and they do state it should be used on iOS 5.1.x and below to address the vulnerability. The onus is on them if they change the format/contents of IAP receipts.

WDUK
  • 18,870
  • 3
  • 64
  • 72
  • Thanks WDUK. I had already sent them feedback before asking the question. ;) For cases in which a server is not available, I think the best approach is to execute the code they provide only for devices running iOS 5.1 or lower. That way if they change the receipts, the damage is limited. – hpique Jul 31 '13 at 15:59
  • Yeah, that's basically the just of my answer. Apple have allowed this previously unrecommended practise, and it's really the only thing you can do if you don't have your own server (which Apple seem to understand happens). It's up to Apple, not the developer, to not break iOS 5.1.x and below compatibility due to use of the mitigation method. How they'll do this I don't know, maybe they'll only change the format of receipts in a time when iOS 5 is obsolete... – WDUK Jul 31 '13 at 16:51
0

Apple is also currently recommending on-device receipt validation. See Validating receipts locally and WWDC 2013 talk Using receipts to protect your digital sales.

Chris Prince
  • 7,288
  • 2
  • 48
  • 66
  • 5
    sorry but understanding how to validate receipts using these links you have provided created by Apple, the king of vague, is like understanding brain surgery by watching two monkeys playing cricket. – Duck Nov 13 '13 at 18:21
  • Yes, in app purchases are a bit of a pain. I've been working on this for a month or so and still working. What information would help? – Chris Prince Nov 14 '13 at 20:13
  • 1
    all. My knowledge about SSL, certificates, etc. is zero. I need to see an example, top to bottom on how to do it. Obviously I know I would have to change some methods to prevent automated crackers, but if I can see the whole thing working, I would be happy. – Duck Nov 14 '13 at 20:34
  • 1
    [I have opened a bounty on this](http://stackoverflow.com/questions/19943183/a-complete-solution-to-validate-an-iapp-and-bundle-receipt-locally-on-ios-7) – Duck Nov 15 '13 at 01:18
  • In the last couple of days I got my iOS7 device-local receipt validation working, seemingly. Just in the initial testing phase. About 500 lines of code. In general, I'd be willing to share it, except I do have concerns that these security-related details from my app will make it into other people's apps and make it easier for people to break my app's security. Thoughts? – Chris Prince Nov 15 '13 at 06:04
  • Create a different version, changing variable/function names, obfuscate strings differently, change the way of doing loops (using a block enumeration instead of "for") and things like that, change the order or methods, names of classes and other stuff. Obviously, as soon as I use a code like that, I will do the same. I would never use the code as it is, for the same reasons you say. Or if you want to mail it to me in particular instead of making it public, you are totally welcome (utugau....at....gmail) – Duck Nov 15 '13 at 13:34