I am the author of Implementing In-app Purchases on iOS and eight other courses on Pluralsight.
Deepen your understanding by watching the course!
Receipt Validation – Verifying a Receipt Signature in Swift
- You’ve prepared to test receipt validation by setting up your app in iTunes Connect.
- You’ve brought in a cryptography library like OpenSSL to be able to work with the PKCS #7 container that acts as the “envelope” for the receipt. Perhaps you’ve even done it the “easy way” with CocoaPods.
- You’ve located and loaded the receipt for validation.
- You’ve extracted the PKCS #7 container.
The aim of this guide is to help you take a look inside the PKCS #7 container, and verify the presence and authenticity of the signature on the receipt.
Just want the code? Here you go!
Recap from the previous guide
In Loading a Receipt for Validation with Swift, I began the process of breaking out the various steps of the receipt validation process into separate single-responsibility structs with clearly named functions to help clarify what each piece of code is doing.
Recall that I’ve created a main Type called ReceiptValidator
, with references to several smaller single-responsibility Types that it uses to accomplish the overall validation process.
Accordingly, I’ve created a ReceiptLoader that finds the receipt on the file system and loads it into memory.
As of the last entry in this series of guides, I’ve also got a ReceiptExtractor
to extract the receipt contents from its PKCS #7 container.
If a validation step ever fails along the way, I’ve decided to take advantage of Swift’s error throwing features to clearly describe what failed. So far, there’s only two cases:
|
|
Preparation step: Download Apple’s root certificate
You need a copy of Apple’s root certificate in order to fully complete this phase of receipt validation.
How do you get a copy of it? Great question (with an answer)!
If you go to https://www.apple.com/certificateauthority/, you can get your hands on a copy by downloading the “Apple Inc. Root Certificate” file:
Once you have it, you need to add the certificate to your Xcode project, and add it to your app’s target:
What can go wrong with receipt signature verification?
I’ll start off the code piece of this guide by asking, “What could go wrong?”. That’ll help define a few more ReceiptValidationError
cases, and might point us in a direction when it comes to implementing a new Type to use within the ReceiptValidator
.
Right off the bat, I can think of two or three things that could go awry at this stage of the receipt validation process:
1 – The receipt that we loaded may not be signed at all
2 – We don’t have a copy of Apple’s root certificate to validate the signature with
3 – The signature on the receipt is invalid because it doesn’t match against Apple’s root certificate
I’ll add those three new error states to the ReceiptValidationError
enum now:
|
|
ReceiptSignatureValidator concept
Another step, another Type. This has been my strategy so far, so I’m stickin’ to it!
I’m validating the presence and authenticity of the signature on the receipt, so I picked the name ReceiptSignatureValidator
for this one.
When I identified three new ReceiptValidationError
cases earlier, I had in mind that they could potentially point me in a direction when implementing this new ReceiptSignatureValidator
Type.
What if this Type had two functions?
checkSignaturePresence
checkSignatureAuthenticity
In checkSignaturePresence
, I’ll look, and if the receipt isn’t signed at all, I’ll throw the receiptNotSigned
Error case.
In checkSignatureAuthenticity
, I’ll look, and if the Apple root certificate is missing from the bundle for some reason, I’ll throw appleRootCertificateNotFound
. And if the signature on the receipt doesn’t jive with Apple’s root certificate, I’ll throw receiptSignatureInvalid
.
Here’s the skeleton of the struct:
|
|
Both checkSignaturePresence
and checkSignatureAuthenticity
need to peek into the PKCS #7 container that encapsulates the receipt data, so each function asks for a reference to an UnsafeMutablePointer<PKCS7>
as one of its arguments.
If you’re following along with the series, you’ll be glad to know that the ReceiptExtractor
that we built previously has a method called extractPKCS7Container
that actually returns a UnsafeMutablePointer<PKCS7>
, so you can just use the a call to extractPKCS7Container
with the new ReceiptSignatureValidator's
functions.
ReceiptSignatureValidator implementation
Now to actually implement ReceiptSignatureValidator
…
Checking signature presence
Checking for the presence of a signature is actually relatively simple. Take a look:
|
|
The PKCS #7 container has a type code associated with it if it’s signed. All we need to do is access that type code, and compare it against the NID_pkcs7_signed
constant.
In order to be valid, the receipt must be signed, so I’ve implemented this as a guard.
Checking signature authenticity
Loading Apple’s root certificate
Now comes the part where we check whether the signature on the receipt is authentic or not.
First, we’ve got to load up Apple’s root certificate (assuming it exists in the app bundle). Here’s a function that can be nested inside of ReceiptSignatureValidator
to do the job:
|
|
This function guards against the absence of Apple’s root certificate. If it can’t be found in the main bundle, the function throws appleRootCertificateNotFound
. This error is obviously preventable, but hey – never hurts to protect yourself if you’re using this code in multiple projects and forget to grab a copy of Apple’s root certificate.
① As long as the .cer file exists in the app bundle, the next step is to create a new in-memory BIO (basic input-output) pointer. That’s what let appleRootCertificateBIO = BIO_new(BIO_s_mem())
does.
② Next, we’ve got to write the contents of the certificate to memory so we can work with it:
|
|
BIO_write
needs a location to write to, namely, our appleRootCertificateBIO
pointer.
It also needs to know what to write: (appleRootCertificateData as NSData).bytes
Finally, it needs to know the length of the data to write: Int32(appleRootCertificateData.count)
③ Once that’s complete, we can obtain pointer to an X509
, which will be used for the next step: verifying the authenticity of the signature on the receipt with the x509 certificate from Apple’s root certificate authority. let appleRootCertificateX509 = d2i_X509_bio(appleRootCertificateBIO, nil)
gives us our return value!
Verifying signature authenticity
The final step is to take the X509
pointer, and use it to verify the authenticity of the signature on the PKCS #7 Container.
Once again, here’s a function that can take both items and do the work:
|
|
① The X509 Store is what holds the information for verification, so we use X509_STORE_new()
to create one.
② Next, X509_STORE_add_cert
function is used to prepare the X509 Store, and the X509 Certificate for verification purposes.
③ OpenSSL keeps an internal table of digest algorithms and ciphers. It uses this table to lookup ciphers via certain functions. OpenSSL_add_all_digests()
is called to load the necessary digest algorithms for verification.
④ The final step is to use the PKCS7_verify
function, passing it the PKCS #7 Container, and the x509 Certificate Store.
⑤ PKCS7_Verify
will return 1 if the signature is valid. If PKCS7_Verify
returns any integer value other than 1, the signature is to be interpreted as invalid.
Putting it all together
Final ReceiptSignatureValidator implementation
The final version of the ReceiptSignatureValidator
looks like this:
|
|
Additions to ReceiptValidator
The ReceiptValidator
struct that’s been growing to accommodate each of the steps now looks like this (additions highlighted):
|
|
Handling errors
The final piece is to attempt to do something intelligent with any of the possible error conditions that could be included with the ReceiptValidator
validation result. Here’s a sample implementation at the call site for validateReceipt()
(probably in a view controller somewhere in your app:
|
|
In the case where the receipt signature is invalid, my only thought right now is to request a new receipt from the app store and attempt to re-validate it.
Upcoming hurdles
Wait, there’s more? In short, yes. We’ve made significant progress, but there’s still more work to be done if you want to fully validate a receipt for your app, or for an in-app purchase.
- Preparing to Test Receipt Validation for iOS
- Loading a Receipt for Validation with Swift
- OpenSSL for iOS & Swift the Easy Way
- Extracting a PKCS7 Container for Receipt Validation with Swift
- Receipt Validation – Parse and Decode a Receipt with Swift
- Finalizing Receipt Validation in Swift – Computing a GUID Hash