Receipt Validation – Verifying a Receipt Signature in Swift

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:

Apple Certificate Page

Once you have it, you need to add the certificate to your Xcode project, and add it to your app’s target:
Apple Root Certificate Target Membership

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.

  • Pingback: Michael Tsai - Blog - Receipt Validation in Swift()

  • Zy Mazza

    I’ve downloaded the root certificate, added my app as a target, yet I’m still getting appleRootCertificateNotFound. Does anyone have any idea what could cause this?

    • Zy Mazza

      Never mind, I had somehow given the certificate a different name.

      • Glad you figured it out, Zy! My only thoughts were the name of the certificate, and maybe double-checking including it in your target. But you got it resolved — awesome!

        • Zy Mazza

          Thanks for such a great tutorial. Do you have any idea why the receiptValidator would put up the .success case even after the subscription has expired? Am I supposed to check for expired subscriptions somewhere else?

          • My thinking on this was to leave app-specific verification steps out of the actual “is this receipt, in and of itself, valid, authentic, and structured well?” steps.

            The .success case was expanded later on in the series to include a parsed receipt (and parsed in-app purchase receipt(s)), so what you’d do is use the validator to ensure that you can trust the receipt data. Then use the .success case with it’s parsed receipt (an associated value with the enum case) and check things like, “Is the subscription expired?” or “Did they purchase x feature?”, etc.

            The final validator is written about here: https://www.andrewcbancroft.com/2017/07/31/finalizing-receipt-validation-in-swift-computing-a-guid-hash/#final-receipt-validator

            The full code repo is up on GitHub as well: https://github.com/andrewcbancroft/SwiftyLocalReceiptValidator

          • Zy Mazza

            Just started digging into that part of the tutorial. What would the method look like for actually getting the ParsedReceipt and where would you put it? Inside the validationresult switch statement?

  • Zy Mazza

    Excellent tutorial. Thank you so much.