Mac App Store receipt validation
In the last installment of Preparing for Mac App Store Submission, I discussed the (general) source code modifications that you may need to make in order to convert your downloadable Mac OS X project into one acceptable for the Mac App Store (MAS).
Now, in this fifth part, I will talk about a more specific addition to your source code, functionality for checking the presence and validity of a MAS receipt file. While this change is not required, and would not cause your submission to be rejected, it is strongly recommend for reasons detailed below. In fact, we will start there…
0. Consider the reason for receipt validation
Before implementation, it is good to understand, in general terms, the purpose of receipt validation.
When somebody “purchases” an application from the Mac App Store, a receipt file is added to the downloaded bundle authenticating it; this includes free products as well. However, the level of checking provided by the system itself is minimal. Soon after the MAS launch, it was discovered that there was a fairly simply method [details deliberately omitted] by which even a simple-minded software thief could circumvent the very basic protections and run stolen MAS applications.
The solution, of course, is to verify the validity of the software receipt for your application, which helps prevent “script kiddies” from easily stealing and distributing your products. Of course, it is never quite as simple as that, especially since code signatures and encryption are inherently complex, and the program needs to validate a receipt file that has not been created yet (making this code rather tricky to test).
Keep in mind that this validation process is optional, so you can choose not to do it all, to fully validate the receipt file, or to implement only partial checks; the choice is up to you.
The official Apple documentation for this process is in Validating Mac App Store Receipts.
1. Obtain a sample receipt for testing
As mentioned above, your first challenge is that the receipt checking code needs to be able to validate a receipt file from a purchase that is to be made in the future. To do this, you may want to get a sample receipt for testing.
This used to be fairly easy, as Apple provided a sample receipt, along with the associated validation data, to Mac developers. Unfortunately, recently (as in, since this series began), the certification signature on the original sample receipt expired and, instead of providing a newer one, Apple has decided to remove the sample receipt entirely in favor of connecting to the iTunes servers to obtain one directly. Whatever.
Now, the process involves getting the application to initially quit with an exit code of 173, which indicates a failed verification, which is supposed to trigger iTunes to prompt you for your (test) account credentials and then download a valid receipt and insert it into the application bundle, as described in Test During the Development Process. In practice, this definitively does not work all of the time during development, and the prerequisites are not clear.
What is clear is that the application needs to be signed before Mac OS X (version 10.6.6 or higher) will request iTunes credentials, and that they need to be for a test account. However, any old test account will apparently not do, and a failure to produce and/or download a receipt displays no error message (just no receipt and only a vague “returnCode:1” in the console). It appears to be that the application needs to already be in iTunes Connect and that the test account must be associated with that application via the master company account. Unfortunately, for the moment, this remains as an exercise for the reader.
Another (unproven) approach could be simply borrowing a receipt from any purchased application from the Mac App Store. You will need to know the exact bundle identifier (fairly easy), version number (also easy), and computer GUID (not quite as easy), but you could use such a receipt exactly like the sample receipt previously provided by Apple.
Important: Do NOT include any receipt files in your project. Receipts are specific to the application, version, and system, and they are generated and added by the Mac App Store/iTunes; including a receipt in your submission bundle will result in summary rejection of the application.
2. Locate the receipt file
The first step to take in your source code is to locate the receipt file for checking. In the release version, intended for distribution, the location of the receipt is always within your application bundle at ‘Contents/_MASReceipt/receipt‘.
If you decide to use a sample receipt for testing, you will probably want to define its (different) location, the parameters expected from the sample receipt, and a definition to use for conditional compilation. In our case, the debug versions define USE_SAMPLE_RECEIPT and the other values (from the old sample receipt) like so:
#ifdef APPSTORE #define RECEIPT "Contents/_MASReceipt/receipt" #ifdef _DEBUG #define USE_SAMPLE_RECEIPT #endif #ifdef USE_SAMPLE_RECEIPT #define SAMPLE_RECEIPT "/Users/Gregg/Desktop/receipt" #define SAMPLE_BUNDLE "com.example.SampleApp" #define SAMPLE_VERSION "1.0.2" #define SAMPLE_GUID { 0x00, 0x17, 0xF2, 0xC4, 0xBC, 0xC0 } #endif #endif
Note that we explicitly define different path variables (RECEIPT versus SAMPLE_RECEIPT) and only define the sample parameters when USE_SAMPLE_RECEIPT is defined. This is additional protection against accidentally using a property intended only for testing in release code. For the same reasons, we also include this check in the code before the main loop:
#ifndef _DEBUG #ifdef USE_SAMPLE_RECEIPT ErrorMessage ( "Warning! Sample receipt is being checked in release version." ); #endif #endif
So, our first verification function is GetReceipt(), which returns the path of the (untouched) receipt file. The routine obtains the path to the bundle and then generates the path directly to the receipt for later verification, adjusted appropriately if USE_SAMPLE_RECEIPT is defined. Additionally, just for another sanity check, it also verifies that the actual bundle identifier and short version string match definitions within the code (so we can safely use the definitions later).
3. Check the signature
The next step in validating a receipt is to check the signature to verify that it is properly signed by Apple. Sample code for doing this (as well as the next two steps) can be found in the Implementation Tips section of the Validating Mac App Store Receipts document, as well as via a nice web search.
Our second verification function is CheckSignature(), which returns a PKCS7* data pointer (and is essentially a fleshed out version of Listing 1-4 in the section linked above). It opens the receipt file, validates the data types, adds the “Apple Root CA” certificate, and verifies the receipt signature (via PKCS7_verify), then cleans up everything except the receipt pointer, which is returned for use in the next function.
At this point, you know that the receipt file exists, it contains data, and its signature is valid.
4. Verify the receipt contents
The next step is validating a receipt is to verify the receipt contents and confirm that the receipt is intended for your application and the current version number. The document referenced above gives more details about implementation, but the process is essentially stepping through the objects in the receipt and processing each one appropriately.
There are four types of objects within each receipt that are relevant to the validation process: bundle identifier, bundle version, opaque value, and hash. In this step, you want to verify that the bundle identifier and bundle version match the hard-coded definitions for your project. Note that matching against values retrieved from the bundle is not as safe, since the ‘Info.plist’ file could potentially be modified to match an invalid receipt. Also, you will want to store the bundle identifier, opaque value, and hash object values for checking during the next step.
Our third verification function is VerifyData(), which accepts the PKCS7* data pointer returned by the previous function. It loops through the objects in the receipt and processes them appropriately, failing if either bundle identifier or bundle version do not match our hard-coded values (IDENTIFIER and VERSION, or SAMPLE_BUNDLE/SAMPLE_VERSION), or if either of these objects is missing from the receipt. Additionally, it saves the objects necessary for the next step.
At this point, you know that the receipt is intended specifically for the current version of your application.
5. Verify the system hash
The next (and, perhaps, final) step in validating a receipt is to verify the system hash to confirm that the receipt is intended for the particular system on which it is being launched. This step prevents the outright copying of entire application bundles from one system to another. Of course, on failure, users are presented with the opportunity to enter their iTunes account information to obtain a valid receipt automatically, so legitimate customers are protected.
The general process for this step involves two parts: obtaining the computer’s GUID and then computing the hash of the GUID, which is then compared to the hash value stored in the last step. The sample code in the Apple documentation referenced above describes both steps sufficiently (in listings 1-3 and 1-7, respectively).
Our fourth verification function is VerifySystem(), which calls a CopyMacAddress() function to perform the first part of the process, then uses that information to check the system hash. More specifically, the GUID returned is added to a digest, along with the opaque value and the bundle identifier, and an expected hash is calculated. The routine verifies that the computed hash length is the same as that of hash and then checks that the contents are identical.
At this point in the process, you now know (to a reasonable degree) that the receipt file exists and is completely valid for that system running the current version of your application.
6. Take extra validation steps
If you are particularly concerned (or paranoid), you can take extra validation steps, such as verifying the certification authorities, double-checking the bundle signature, or (apparently) even sending a receipt back to the App Store for verification. You can also take steps to obfuscate your code and make it harder for crackers to find and modify the application to circumvent your checks, and Apple provides some suggestions.
However, just taking the general steps documented here will probably eliminate 99% of the problem, and with robust coding, error checking, and testing, theft of the Mac App Store version of your game should be minimal. (In fact, you will likely lose significantly more money to downward pricing pressures than to piracy, but that is another topic altogether…)
Conclusion
With the addition of receipt validation checking to your project source code, your application should not be subject to simple receipt spoofing, once accepted into the Mac App Store. In the next (final) installment, Part 6: App Sandboxing implementation, I will discuss the process of preparing for and implementing application sandboxing, as required for the Mac App Store by June 1, 2012 (recently pushed back from March 1st).