I've been working to validate in-app purchase receipts on an app that I want to run on iOS6 and iOS7. My plan so far has been to: (a) first do purely on-device receipt validation without connecting to an Apple server (like that given in these Apple docs and in the WWDC 2013 session "Using Receipts to Protect Your Digital Sales"), then (b) second do receipt validation on my server. Doing it twice should help, and in the off-chance my server is down, will make things more secure too.
I finally got an example receipt coming back from the App Store, and am having problems with the on-device receipt validation. I'm testing on an iOS6 system (an iPad), and getting the receipt using transactionReceipt from the SKPaymentTransaction (not appStoreReceiptURL from NSBundle). Here's the sample receipt I have:
{
"signature" = "AkkgbJWkOGHyXoigel4s39Ut37kiYv+sXwhTa6+ic6LJPOq3DLV3B2zi9aebiezw6nvbJKyFrIYh1mqiKHDp/gJ5RFrslWDxk7ntiKIs1eJ3bpFgDC733Au9f1zUctvAiFwN+9L/FOGWugddwnHMhh9N8eSPoK+BoYUX8ObeCxc7AAADVzCCA1MwggI7oAMCAQICCGUUkU3ZWAS1MA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA5MDYxNTIyMDU1NloXDTE0MDYxNDIyMDU1NlowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrRjF2ct4IrSdiTChaI0g8pwv/cmHs8p/RwV/rt/91XKVhNl4XIBimKjQQNfgHsDs6yju++DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN+mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C/IB3QEpK32RxacCDXdVXAeVReS5FaZxc+t88pQP93BiAxvdW/3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ+/AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV/UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE/UC6Y7053pGXBk51NPM3woxhd3gSRLvXj+loHsStcTEqe9pBDpmG5+sk4tw+GK3GMeEN5/+e1QT9np/Kl1nj+aBw7C0xsy0bFnaAd1cSS6xdory/CUvM6gtKsmnOOdqTesbp0bs8sn6Wqs0C9dgcxRHuOMZ2tm8npLUm7argOSzQ==";
"purchase-info" = "ewoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtcHN0IiA9ICIyMDEzLTEwLTE4IDE2OjQ3OjM4IEFtZXJpY2EvTG9zX0FuZ2VsZXMiOwoJInVuaXF1ZS1pZGVudGlmaWVyIiA9ICI5OTQ2ZTk0M2E4YTI3YzhlM2U0YzY4YmM1N2I5YWQ2MmUwOTFjNmUxIjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDA5MDYwNjMzOSI7CgkiYnZycyIgPSAiMS4wIjsKCSJ0cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDA5MDYwNjMzOSI7CgkicXVhbnRpdHkiID0gIjEiOwoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtbXMiID0gIjEzODIxNDAwNTg3NjYiOwoJInVuaXF1ZS12ZW5kb3ItaWRlbnRpZmllciIgPSAiODBERjQ0RUItNkQ1OS00QTBBLUEwNkEtQzg2RjM0QTcwQ0ZDIjsKCSJwcm9kdWN0LWlkIiA9ICJiaXouU3Bhc3RpY011ZmZpbi5QZXR1bmlhLkFkUmVtb3ZhbFllYXJseSI7CgkiaXRlbS1pZCIgPSAiNzI3MTg5MzU4IjsKCSJiaWQiID0gImJpei5TcGFzdGljTXVmZmluLlBldHVuaWEiOwoJInB1cmNoYXNlLWRhdGUtbXMiID0gIjEzODIxNDAwNTg3NjYiOwoJInB1cmNoYXNlLWRhdGUiID0gIjIwMTMtMTAtMTggMjM6NDc6MzggRXRjL0dNVCI7CgkicHVyY2hhc2UtZGF0ZS1wc3QiID0gIjIwMTMtMTAtMTggMTY6NDc6MzggQW1lcmljYS9Mb3NfQW5nZWxlcyI7Cgkib3JpZ2luYWwtcHVyY2hhc2UtZGF0ZSIgPSAiMjAxMy0xMC0xOCAyMzo0NzozOCBFdGMvR01UIjsKfQ==";
"environment" = "Sandbox";
"pod" = "100";
"signing-status" = "0";
}
In my on-device receipt validation, I'm failing in the step of converting to PKCS #7 representation:
// Without (void *) coercion, we get a warning, but we're only reading from this buffer in BIO_new_mem_buf
b_receipt = BIO_new_mem_buf((void *) [self.receipt bytes], [self.receipt length]);
if (! b_receipt) {
endWithMessage(@"Failed on BIO_new_mem_buf: receipt"); return(ReceiptInvalid);
}
// Convert receipt data to PKCS #7 Representation
p7 = d2i_PKCS7_bio(b_receipt, NULL);
if (! p7) {
endWithMessage(@"Failed on d2i_PKCS7_bio"); return(ReceiptInvalid);
}
So far, I'm struck by the fact that the receipt is ASCII and not binary.
In going back to look at the Apple docs, I find a phrase "In iOS, if the appStoreReceiptURL method is not available (on older systems), you can fall back to validating the transactionReceipt property of an SKPaymentTransaction object with the App Store. For details, see “Validating Receipts With the App Store.”
It seems I may have made an incorrect assumption. I have been assuming that iOS6 and iOS7 receipts are in the same format, just obtained from different places.
Are receipts from the transactionReceipt property of SKPaymentTransaction ASCII and the receipts from appStoreReceiptURL method of NSBundle binary?
Can you really do purely on-device receipt validation with the transactionReceipt-obtained receipts in iOS6?
Thoughts? Chris.
EDITS
I watched the session WWDC 2013 "Using Receipts to Protect Your Digital Sales" again and it seems that clearly the iOS6 receipt format is different from the iOS7 format. I wish that somewhere Apple would provide a comparison between the receipt formats for iOS6 and iOS7. I have yet to have access to hardware running iOS7 though, and since in-app purchases don't work on the simulator, I don't yet have access to an example iOS7 receipt. :(.
No. You must validate in app purchases via an external server. It is rather simple to fool the in app purchase mechanisms if you do not.
You can certainly do on-device receipt validation of a transaction.transactionReceipt using excerpts of the code in VerificationController posted by Apple. I modified that and posted code on the Developers Forum called VerificationControllerPBKSimple. The receipt is signed by Apple so this process is reasonably secure unless someone messes with your compiled Objective-C code on their device - and until the deprecated transaction.transactionReceipt disappears.
You can also do on-device receipt validation of an iOS7 receipt (not a transaction.transactionReceipt) but that requires some fancy coding.