I’m working through a progression of entries on the process of validating receipts with OpenSSL for iOS in Swift.
Just want the code? Here you go!
To-date, I’ve explained how to get OpenSSL into your project (the easy way), and I’ve walked through how to prepare to test receipt validation, including how to set everything up in the Apple Developer member center, and in iTunes Connect.
There are at least 5 steps to validate a receipt, as the Receipt Validation Programming Guide outlines:
1 – Locate the receipt.
If no receipt is present, validation fails.
2 – Verify that the receipt is properly signed by Apple.
If it is not signed by Apple, validation fails.
3 – Verify that the bundle identifier in the receipt matches a hard-coded constant containing the CFBundleIdentifier value you expect in the Info.plist file.
If they do not match, validation fails.
4 – Verify that the version identifier string in the receipt matches a hard-coded constant containing the CFBundleShortVersionString value (for macOS) or the CFBundleVersion value (for iOS) that you expect in the Info.plist file.
If they do not match, validation fails.
5 – Compute the hash of the GUID as described in Compute the Hash of the GUID.
If the result does not match the hash in the receipt, validation fails.
The thought I had when I saw 5 steps is, “This is going to become too much responsibility for a single Type to handle”. I easily got overwhelmed when I analyzed the most extensive write-up on the subject, found at Objc.io.
Validation organization strategy overview
To help keep my head wrapped around this process, I’ve developed a strategy that has kept me sane so far. It incorporates 3 components:
1 – ReceiptValidator
First, I’ve created a top-level Type called
ReceiptValidator. My idea is to have a single method,
validateReceipt() that will either run and succeed, or start propagating errors.
2 – Map validation steps to separate Types
Second, I’ve tried to take each of the steps involved in validating the receipt and create a simple Swift Type to encapsulate the logic that needs to happen in that step.
So when step 1 says to “locate and load the receipt”, I created a struct called
ReceiptLoader that has two methods:
ReceiptValidator currently holds references to instances of each of these little helper Types, and the validator class itself calls methods on those instances to get the overall job of validating the receipt done.
3 – Throw errors when a validation step fails
Third, whenever some step along the way fails, I’m utilizing Swift’s error handling features. I’ve created an enum called
ReceiptValidationError with various descriptive values. Whenever a validation error occurs, one of the values in the
ReceiptValidationError enum is thrown.
The enum’s definition is simple right now, but it will grow as time goes on with various other error conditions related to receipt validation (and why validation might fail):
This enum simply implements the
Error “marker” protocol, which allows its values to be used in Swift’s error-throwing system. For this blog entry, we’ll stick with simply throwing the value
couldNotFindReceipt whenever a receipt can’t be found and needs to be re-requested from the App Store.
ReceiptValidator is where everything for validating receipts launches for me at this point. Calling a single method,
validateReceipt() will kick off the 5+ step process that Apple describes.
The first thing that needs to happen is to load a receipt that’ll be validated. If a receipt is not found on the device, a new receipt needs to be requested from the App Store.
ReceiptLoader in the overview. An implementation will follow, but we’ll let this instance do the locating and loading of the receipt.
With that architecture in mind, here’s what
ReceiptValidator looks like right now:
So the validator simply lets the
ReceiptLoader instance do it’s loading job. If it doesn’t return any data containing a receipt to work with, the validator will catch the error and return the
ReceiptValidationResult case with the error cast to a
ReceiptValidationError as an associated value.
The View Controller is what calls
validateReceipt(), so it will be waiting to deal with the
ReceiptValidationResult that’s returned by the
ReceiptLoader has the sole responsibility of going to receipt storage location on a user’s device and attempting to discover the receipt and pull out the contents of that file in the form of a
Data instance if it’s there.
Here’s the implementation with explanation to follow:
loadReceipt() method will do the job and either return a
Data instance with the receipt contents that can be extracted and parsed in later steps, or it will throw the
ReceiptValidationError.couldNotFindReceipt enum value.
The rest of the implementation is all around making sure the receipt is there and accessible by utilizing standard
The View Controller is the kick-off point of all-things receipt validation. To have some code in front of us, take a look at this implementation. Explanatory details are below:
When the app starts and the controller has loaded, it will prepare itself in a couple of ways:
First, it will initialize a
ReceiptValidator and an
SKReceiptRefreshRequest (in case a receipt isn’t present on the device).
Subtle but important, the
SKReceiptRefreshRequest instance’s delegate is set to the view controller itself in
Control is then handed over to the
ReceiptValidator instance to begin its multi-step process (of which we’ve got step 1 down up to this point).
The View Controller acts as the main error handler for now. If a receipt couldn’t be found, signaled by the throwing of
ReceiptValidationError.couldNotFindReceipt by the validator, the receipt refresh request is started.
The View Controller also acts as the
SKRequestDelegate, and thus gets called back whenever the request finishes (or fails with an error).
If receipt validation fails once the receipt is downloaded to the device, Apple recommends that in iOS, we do not attempt to terminate the app.
Rather, they recommend logging that the receipt validation failed and then initiating a grace period, or disabling functionality in your app, depending on the situation.
That about wraps up locating and loading the receipt. The real challenges of using OpenSSL to extract the receipt, verify its authenticity, parse it, and more are still ahead. I’ll be sure to chronicle my journey as I jump those hurdles. Stay tuned!
- Preparing to Test Receipt Validation for iOS
- OpenSSL for iOS & Swift the Easy Way
- Extracting a PKCS7 Container for Receipt Validation with Swift
- Receipt Validation – Verifying a Receipt Signature in Swift
- Receipt Validation – Parse and Decode a Receipt with Swift
- Finalizing Receipt Validation in Swift – Computing a GUID Hash