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:

1
2
3
4
enum ReceiptValidationError : Error {
    case couldNotFindReceipt
    case emptyReceiptContents
}

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:

1
2
3
4
5
6
7
enum ReceiptValidationError : Error {
    case couldNotFindReceipt
    case emptyReceiptContents
    case receiptNotSigned
    case appleRootCertificateNotFound
    case receiptSignatureInvalid
}

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:

1
2
3
4
5
6
7
8
9
struct ReceiptSignatureValidator {
    func checkSignaturePresence(_ PKCS7Container: UnsafeMutablePointer<PKCS7>) throws {
        // Implementation coming
    }
    
    func checkSignatureAuthenticity(_ PKCS7Container: UnsafeMutablePointer<PKCS7>) throws {
        // Implementation coming
    }
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct ReceiptSignatureValidator {
    func checkSignaturePresence(_ PKCS7Container: UnsafeMutablePointer<PKCS7>) throws {
        let pkcs7SignedTypeCode = OBJ_obj2nid(PKCS7Container.pointee.type)
        
        guard pkcs7SignedTypeCode == NID_pkcs7_signed else {
            throw ReceiptValidationError.receiptNotSigned
        }
    }
    
    func checkSignatureAuthenticity(_ PKCS7Container: UnsafeMutablePointer<PKCS7>) throws {
        // Implementation coming
    }
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
fileprivate func loadAppleRootCertificate() throws -> UnsafeMutablePointer<X509> {
    guard
        let appleRootCertificateURL = Bundle.main.url(forResource: "AppleIncRootCertificate", withExtension: "cer"),
        let appleRootCertificateData = try? Data(contentsOf: appleRootCertificateURL)
        else {
            throw ReceiptValidationError.appleRootCertificateNotFound
    }
    
    //①
    let appleRootCertificateBIO = BIO_new(BIO_s_mem())

    //②
    BIO_write(appleRootCertificateBIO, (appleRootCertificateData as NSData).bytes, Int32(appleRootCertificateData.count))

    //③
    let appleRootCertificateX509 = d2i_X509_bio(appleRootCertificateBIO, nil)
    
    return appleRootCertificateX509!
}

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:

1
BIO_write(appleRootCertificateBIO, (appleRootCertificateData as NSData).bytes, Int32(appleRootCertificateData.count))

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
fileprivate func verifyAuthenticity(_ x509Certificate: UnsafeMutablePointer<X509>, PKCS7Container: UnsafeMutablePointer<PKCS7>) throws {
    //①
    let x509CertificateStore = X509_STORE_new()

    //②
    X509_STORE_add_cert(x509CertificateStore, x509Certificate)
    
    //③
    OpenSSL_add_all_digests()
    
    //④
    let result = PKCS7_verify(PKCS7Container, nil, x509CertificateStore, nil, nil, 0)
    
    //⑤
    if result != 1 {
        throw ReceiptValidationError.receiptSignatureInvalid
    }
}

① 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
struct ReceiptSignatureValidator {
    func checkSignaturePresence(_ PKCS7Container: UnsafeMutablePointer<PKCS7>) throws {
        let pkcs7SignedTypeCode = OBJ_obj2nid(PKCS7Container.pointee.type)
        
        guard pkcs7SignedTypeCode == NID_pkcs7_signed else {
            throw ReceiptValidationError.receiptNotSigned
        }
    }
    
    func checkSignatureAuthenticity(_ PKCS7Container: UnsafeMutablePointer<PKCS7>) throws {
        let appleRootCertificateX509 = try loadAppleRootCertificate()
        
        try verifyAuthenticity(appleRootCertificateX509, PKCS7Container: PKCS7Container)
    }
    
    fileprivate func loadAppleRootCertificate() throws -> UnsafeMutablePointer<X509> {
        guard
            let appleRootCertificateURL = Bundle.main.url(forResource: "AppleIncRootCertificate", withExtension: "cer"),
            let appleRootCertificateData = try? Data(contentsOf: appleRootCertificateURL)
            else {
                throw ReceiptValidationError.appleRootCertificateNotFound
        }
        
        let appleRootCertificateBIO = BIO_new(BIO_s_mem())
        BIO_write(appleRootCertificateBIO, (appleRootCertificateData as NSData).bytes, Int32(appleRootCertificateData.count))
        let appleRootCertificateX509 = d2i_X509_bio(appleRootCertificateBIO, nil)
        
        return appleRootCertificateX509!
    }
    
    fileprivate func verifyAuthenticity(_ x509Certificate: UnsafeMutablePointer<X509>, PKCS7Container: UnsafeMutablePointer<PKCS7>) throws {
        let x509CertificateStore = X509_STORE_new()
        X509_STORE_add_cert(x509CertificateStore, x509Certificate)
        
        OpenSSL_add_all_digests()
        
        let result = PKCS7_verify(PKCS7Container, nil, x509CertificateStore, nil, nil, 0)
        
        if result != 1 {
            throw ReceiptValidationError.receiptSignatureInvalid
        }
    }
}

Additions to ReceiptValidator

The ReceiptValidator struct that’s been growing to accommodate each of the steps now looks like this (additions highlighted):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct ReceiptValidator {
    let receiptLoader = ReceiptLoader()
    let receiptExtractor = ReceiptExtractor()
    let receiptSignatureValidator = ReceiptSignatureValidator()

    
    func validateReceipt() -> ReceiptValidationResult {
        do {
            let receiptData = try receiptLoader.loadReceipt()
            let receiptContainer = try receiptExtractor.extractPKCS7Container(receiptData)
            
            try receiptSignatureValidator.checkSignaturePresence(receiptContainer)
            try receiptSignatureValidator.checkSignatureAuthenticity(receiptContainer)
            return .success
        } catch {
            return .error(error as! ReceiptValidationError)
        }
    }
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
override public func viewDidLoad() {
    super.viewDidLoad()

    let validationResult = receiptValidator.validateReceipt()
        
    switch validationResult {
    case .success:
        // Enable app features
    case .error(let error):
        print(error)
        receiptRequest.start()
    }   
}

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.

comments powered by Disqus