The primary purpose of Receigen is to provide the required code to validate AppStore receipts. If you distribute your application on and off the AppStore, the receipt file can be used to validate an application purchased directly. By doing so, you can provide your users a version of the application that does not fit in the AppStore rules or an emergency fix.

This blog entry described the whole process about the solution.

Generation

Note: Currently, the generation can only be done on the command line.

The following command generates the receipt validation code in a file named receipt.h that:

$> /Applications/Receigen.app/Contents/MacOS/Receigen --identifier com.example.MyApplication --version 1.3.0 --os osx --success callblock --failure callblock --receipt-path "~/Library/Application Support/MyApplication/MASReceipt/receipt" > receipt.h
Receigen, a smart code generator for Mac App Store receipt validation.
----------------------------------------------------------------------
Generating receipt validation code for CFBundleIdentifier='com.example.MyApplication' and CFBundleShortVersionString='1.3.0'...
Generation done.

Integration

The generated code integration follows the general guides.

Bundle Identifier Check

In the direct-sale scenario, receipt and bundle identifiers may probably not match. This is the case when you provide a beta version when a production version is available on the Mac App Store.

To handle this case, you have two options:

  1. Pass the --receipt-identifier option on the command line when generating the validation code; this option enables the receipt identifier to be different from the bundle identifier.
  2. Pass the --loose-receipt-identifier-check switch when generating the validation code; this switch enables the validation code to silently ignore the identifier mismatch in the receipt while performing all the remaining tests. There is no partial match on the bundle identifier. Even if the bundle identifier defined in the code is totally different from the bundle identifier encoded in the receipt, the receipt will be considered as valid.

Bundle Version Check

In the direct-sale scenario, receipt and bundle versions may probably not match. This is the case when you provide a version 1.2.1 while the 1.2 is available on the Mac App Store.

To handle this case, you have two options:

  1. Pass the --receipt-version option on the command line when generating the validation code; this option enables the receipt version to be different from the bundle version.
  2. Pass to define the --loose-receipt-version-check switch when generating the validation code. This switch enables the validation code to silently ignore the version mismatch in the receipt while performing all the remaining tests. There is no partial match on the bundle version. Even if the bundle version defined in the code is totally different from the bundle version encoded in the receipt, the receipt will be considered as valid.

Certificate Check

In the direct-sale scenario, you may want to generate your own receipts. This requires a solid cryptographic knowledge on how to setup a Public Key Infrastructure and how to generate a PKCS#11 content.

When generating your own receipts, the root certificate is not the Apple’s one but yours. In order for the validation code to check the signature of the receipt, you need to provide the root certificate used to sign the receipt.

To handle this case, pass the --root-ca-content, the --root-ca-name and the --root-ca-fingerprint options on the command line. You can also change the OID to match on the signing certificate by passing the --signer-oid option on the command line.

Pitfalls

The path to the receipt is mostly fixed at code generation time, since it is hard to obfuscate variable arguments at runtime.

If you store multiple receipts named by GUID, as recommended in the blog above, you will need to copy the receipt file you wish to validate to a fixed location (specified at code generation time) before calling Receigen.

Although the path is “fixed”, it is modified in one specific way at runtime: if the path contains the string ~/, that reference will be expanded (or globbed). The exact expansion varies depending upon whether the app sandboxed or not.

In a non-sandboxed app, the string ~/ will be replaced with the user’s home folder. For example, ~/Library/Application Support will be resolved to /Users/<username>/Library/Application Support.

In a sandboxed app, the string ~/ will be replaced with a reference into the application sandbox. For example, ~/Library/Application Support will be resolved to /Users/<username>/Library/Containers/<bundle ID>/Data/Library/Application Support.

Note that the expansion (or globbing) mechanism uses the glob API, and so is unaware of Cocoa special cases. For example, a reference to ~/Library/Group Containers at generate time will end up referring to /Users/<username>/Library/Containers/<bundle ID>/Data/Library/Group Containers/ at runtime (which probably isn’t what you want in this case).

To assist with diagnosis here, by default, Receigen logs messages like the below:

Loading external receipt at '~/Library/Application Support/MyApp/MASReceipts/temp'
Receipt path resolved to '/Users/<username>/Library/Containers/<bundle ID>/Data/Library/Application Support/MyApp/MASReceipts/temp'