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:
- targets the OS X platform
- uses the bundle identifier
com.example.MyApplication
- uses the bundle version
1.3.0
- load a receipt file located at
~/Library/Application Support/MyApplication/MASReceipt/receipt
- calls a block if the validation succeeds (the default for OS X)
- calls a block if the validation fails (the default for OS X)
$> /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:
- 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. - 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:
- 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. - 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'