Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top