A fascinating look into how the App Store and StoreKit operate.
January 17, 2018
January 17, 2018
Reading the contents of an App Store receipt file without Apple’s servers
Apple receipts are not a black box. In the half-decade that receipt files have been around, I’ve almost always treated them as tokens to be sent off to a service for validation, rather than actual data. I recently discovered that the App Store receipt’s contents are a full snapshot a user’s in-app-purchase (IAP) history and that Apple documents how to validate and extract this information. It is far from simple but it provides a fascinating look into how the App Store and StoreKit operate.
App Store Receipts
Starting with iOS 7, Apple introduced an alternative method for accessing a user’s IAP history. Previously developers had to rely on the SKPaymentQueue as the source of truth, listening to transactions and keeping track. With iOS 7, Apple introduced NSBundle.appStoreReceiptURL that gave developers a disk location they could find this new “receipt” file. The intention was to have developers use this file as the source of truth for IAPs, rather than the StoreKit purchase queue which can be difficult to build around.
Most developers’ experience with App Store receipt is very brief. It entails grabbing the URL, reading the contents of the file, encoding it in base 64, and sending it off to a server for validation via Apple. The data itself is a black box to most, more of a token than an actual container of information. However, this couldn’t be further from the truth. The receipt is a complex but decently documented file that contains almost all of the information that you would normally get by validating via Apple’s /verifyReceipt endpoint.
Validating the Receipt Signature
The file itself uses an old file encoding called ASN.1, a binary file encoding protocol similar to Protobuf or Apache Thrift. Using ASN.1, Apple wraps the actual receipt data (also an ASN.1 blob) inside of a cryptographic container known as a PKCS#7 container. It is a container that stores signed data, a signature, and certificates required to verify the included signature.
We are using Python to decode and validate the receipt. First, we parse the PKCS#7 container and extract the essential parts: the certificates, the signature, and the receipt data.
Apple relies on cryptographic signatures to guarantee that the contents of the receipt are valid. (It may be useful to brush up on asymmetric key cryptography if you aren’t familiar with the concept at a basic level.) Apple hashes the receipt data, then encrypts that hash with their private key. This ensures that the only way to decrypt the encrypted hash is using Apple’s published public key. This process is called signing, and the encrypted hash is called a signature.
Apple hashes and encrypts the receipt data to create a signature then packages the signature, certificates, and receipt data together into the receipt file.
Our job is to ensure the receipt data we have is the same data that Apple signed. Since Apple generated the signature with their private key (which only Apple has access to), we can trust the data in the receipt file without talking to Apple directly. To do this, we use Apple’s public key, also known as a certificate. We first decrypt the signature, to get back to the hash, and then compute the hash ourselves using the included receipt data and the same hashing function. If our computed hash matches the one we’ve decrypted from Apple, we’ve proven that the receipt data contained in the receipt file is the same receipt data that Apple signed.
For security reasons, Apple doesn’t like to use their root certificate to generate such simple signatures. If the private key for Apple’s root certificate were on every iTunes server, the chance of compromise would be much higher. Instead, Apple uses a certificate chain of trust to grant signing privileges to other, less critical certificates. Apple typically includes three certificates in the receipt file: the Apple Root Certificate, a World Wide Developer Relations Intermediate Certificate, and the iTunes App Store Certificate. The iTunes App Store Certificate is the one used to generate the signature of the receipt data.
To bootstrap our trust chain, we need to obtain a trusted copy of the Apple Root Certificate. We can download a trusted Apple Root Certificate from Apple. Assuming your machine hasn’t been man-in-the-middled by some very tenacious IAP cracker, you can trust this certificate. Having a known trusted certificate, we can bootstrap trust to the other included certificates included in the receipt file.
The trusted version of the Apple Root Certificate can be used to validate the signature on the WWDR Certificate.Once we’ve validated that the WWDR Certificate included in the receipt is trusted, we can, in turn, use it to validate the iTunes App Store Certificate. Fortunately, the OpenSSL Python wrapper provides tools for doing this.
Now we have the iTunes App Store Certificate extracted and validated; we can use it to validate the signature on the receipt data.
We’ve now unpacked the PKCS#7 container, extracted the certificates, validated them using a trusted root cert, and used them to validate the signature on our receipt data. We can now trust that no one has modified the contents of the receipt data since Apple generated the signature. The next phase is unpacking the receipt data and reading the actual information about a user’s purchases.
Extracting the Receipt Fields
The signed receipt data itself is an ASN.1 container. However, this one doesn’t use a public specification such as PKCS#7 or X.509. Instead, it is a custom format created by Apple exclusively for App Store receipts. You can see the documented fields here (it’s likely you’ve used this page for reading Apple JSON responses). The following ASN.1 specification describes the contents of the receipt data:
The definition is relatively loose, a record called a ReceiptAttribute that is a tuple of(Integer type, Integer version, Octet String value) and a set of these, that we call Payload. The type field is used in conjunction with Apple’s documentation to tell you how to parse the value field, which is a binary blob. The asn1crypto module lets us define custom types to automate some of the parsing work.
We can now go over all the ReceiptAttribute tuples and move them into a dictionary since there should only be one of each type for all but one of the types. We also convert them to native Python types. There are, however, multiple instances of the in_app type. We will store these for later.
The dictionary receipt_attributes now has some of the same data you are accustomed to seeing in response from Apple’s /verifyReceipt endpoint.
Extracting In-App Purchases
The data we’ve extracted so far has to do with the app purchase or download, but we are usually more interested in the IAPs. To see what IAPs there are we look at the in_app attributes that we siphoned off when going through the receipt’s attributes. The value field of in_app attributes is itself another ASN.1 container with a similar definition as the top level receipt.
We can perform the same operation on the contents of the individual in_app receipts to extract the fields as native Python objects.
And thats the way the news goes! We’ve successfully unpacked and validated the information from an Apple receipt without sending it off to Apple.
There are limitations to validating Apple receipts this way. The biggest being that you won’t get access to fields associated with subscriptions: latest_receipt_info and pending_renewal_info. The receipt file you unpack is just a snapshot, and you potentially need to be able to update a user’s subscriptions even if they haven’t sent you the most recent receipt. Additionally, validating receipts yourself is error-prone and could be equated with “rolling your own crypto”: risky and usually unneeded. For instance DO NOT USE THIS CODE to validate your receipts. We skipped over some important things like checking certificate expirations. You are better off using Apple’s /verifyReceipt service or something like RevenueCat if you don’t want to handle the risk.
RevenueCat is the best way to implement subscriptions in your mobile app. We handle all the complicated parts so you can get back to building. Request an invite today at https://www.revenuecat.com/