Latest

Receipt Validation – Verifying a Receipt Signature in Swift

The goal of this information is that will help you take a look contained in the PKCS #7 container, and verify the presence and authenticity of the signature on the receipt.

Just want the code? Right here you go!

In Loading a Receipt for Validation with Swift, I began the method of breaking out the varied steps of the receipt validation process into separate single-responsibility structs with clearly named features to assist clarify what each bit of code is doing.

Recall that I’ve created a fundamental Sort referred to as ReceiptValidator, with references to several smaller single-responsibility Varieties that it uses to perform the general validation course of.

Accordingly, I’ve created a ReceiptLoader that finds the receipt on the file system and masses it into memory.

As of the last entry in this collection of guides, I’ve also obtained a ReceiptExtractor to extract the receipt contents from its PKCS #7 container.

If a validation step ever fails alongside the best way, I’ve determined to benefit from Swift’s error throwing features to clearly describe what failed. Thus far, there’s only two instances:

1enum ReceiptValidationError : Error
2 case couldNotFindReceipt
3 case emptyReceiptContents
four

You need a copy of Apple’s root certificates in order to completely complete this part of receipt validation.

How do you get a copy of it? Great query (with an answer)!

For those who go to https://www.apple.com/certificateauthority/, you will get your arms on a copy by downloading the “Apple Inc. Root Certificate” file:

Upon getting it, you’ll want to add the certificates to your Xcode undertaking, and add it to your app’s goal:
Apple Root Certificate Target Membership

I’ll start off the code piece of this guide by asking, “What could go wrong?”. That’ll help define a few more ReceiptValidationError instances, and may point us in a course in terms of implementing a new Sort to make use of inside the ReceiptValidator.

Proper off the bat, I can consider two or three things that would go awry at this stage of the receipt validation process:

1 – The receipt that we loaded is probably not signed at all
2 – We don’t have a copy of Apple’s root certificate to validate the signature with
three – The signature on the receipt is invalid as a result of it doesn’t match towards Apple’s root certificates

I’ll add those three new error states to the ReceiptValidationError enum now:

1enum ReceiptValidationError : Error
2 case couldNotFindReceipt
three case emptyReceiptContents
four case receiptNotSigned
5 case appleRootCertificateNotFound
6 case receiptSignatureInvalid
7

One other step, one other Sort. This has been my strategy up to now, so I’m stickin’ to it!

I’m validating the presence and authenticity of the signature on the receipt, so I picked the identify ReceiptSignatureValidator for this one.

Once I recognized three new ReceiptValidationError instances earlier, I had in mind that they might probably point me in a path when implementing this new ReceiptSignatureValidator Sort.

What if this Sort had two features?

  • 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 cause, 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:

1struct ReceiptSignatureValidator
2 func checkSignaturePresence(_ PKCS7Container: UnsafeMutablePointer) throws
3 // Implementation coming
4
5
6 func checkSignatureAuthenticity(_ PKCS7Container: UnsafeMutablePointer) throws
7 // Implementation coming
eight
9

Each checkSignaturePresence and checkSignatureAuthenticity have to peek into the PKCS #7 container that encapsulates the receipt knowledge, so every perform asks for a reference to an UnsafeMutablePointer as certainly one of its arguments.

When you’re following along with the collection, you’ll be glad to know that the ReceiptExtractor that we constructed beforehand has a technique referred to as extractPKCS7Container that really returns a UnsafeMutablePointer, so you’ll be able to just use the a call to extractPKCS7Container with the brand new ReceiptSignatureValidator’s features.

Now to truly implement ReceiptSignatureValidator…

Checking signature presence

Checking for the presence of a signature is actually relatively simple. Take a look:

1struct ReceiptSignatureValidator
2 func checkSignaturePresence(_ PKCS7Container: UnsafeMutablePointer) throws
three let pkcs7SignedTypeCode = OBJ_obj2nid(PKCS7Container.pointee.sort)
four
5 guard pkcs7SignedTypeCode == NID_pkcs7_signed else
6 throw ReceiptValidationError.receiptNotSigned
7
8
9
10 func checkSignatureAuthenticity(_ PKCS7Container: UnsafeMutablePointer) throws
11 // Implementation coming
12
13

The PKCS #7 container has a sort code associated with it if it’s signed. All we need to do is entry that sort code, and examine it towards the NID_pkcs7_signed fixed.

As a way to be legitimate, the receipt have to be signed, so I’ve carried out this as a guard.

Checking signature authenticity

Loading Apple’s root certificate

Now comes the half where we verify whether or not the signature on the receipt is genuine or not.

First, we’ve obtained to load up Apple’s root certificate (assuming it exists in the app bundle). Here’s a perform that may be nested inside ReceiptSignatureValidator to do the job:

1fileprivate func loadAppleRootCertificate() throws -> UnsafeMutablePointer
2 guard
3 let appleRootCertificateURL = Bundle.essential.url(forResource: “AppleIncRootCertificate”, withExtension: “cer”),
4 let appleRootCertificateData = attempt? Knowledge(contentsOf: appleRootCertificateURL)
5 else
6 throw ReceiptValidationError.appleRootCertificateNotFound
7
eight
9 //①
10 let appleRootCertificateBIO = BIO_new(BIO_s_mem())
11
12 //②
13 BIO_write(appleRootCertificateBIO, (appleRootCertificateData as NSData).bytes, Int32(appleRootCertificateData.rely))
14
15 //③
16 let appleRootCertificateX509 = d2i_X509_bio(appleRootCertificateBIO, nil)
17
18 return appleRootCertificateX509!
19

This perform guards towards the absence of Apple’s root certificates. If it may well’t be discovered in the primary bundle, the perform throws appleRootCertificateNotFound. This error is clearly preventable, however hey – never hurts to guard yourself in case you’re using this code in a number of tasks and overlook to grab a copy of Apple’s root certificates.

① As long as the .cer file exists in the app bundle, the subsequent step is to create a new in-memory BIO (primary input-output) pointer. That’s what let appleRootCertificateBIO = BIO_new(BIO_s_mem()) does.

② Subsequent, we’ve received to write down the contents of the certificate to memory so we will work with it:

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

BIO_write wants a location to write down to, specifically, our appleRootCertificateBIO pointer.

It also must know what to write down: (appleRootCertificateData as NSData).bytes

Lastly, it must know the length of the info to put in writing: Int32(appleRootCertificateData.rely)

③ Once that’s complete, we will get hold of pointer to an X509, which will probably be used for the subsequent step: verifying the authenticity of the signature on the receipt with the x509 certificates from Apple’s root certificates authority. let appleRootCertificateX509 = d2i_X509_bio(appleRootCertificateBIO, nil) provides us our return value!

Verifying signature authenticity

The final step is to take the X509 pointer, and use it to confirm the authenticity of the signature on the PKCS #7 Container.

Once again, right here’s a perform that can take both gadgets and do the work:

1fileprivate func verifyAuthenticity(_ x509Certificates: UnsafeMutablePointer, PKCS7Container: UnsafeMutablePointer) throws
2 //①
three let x509CertificateStore = X509_STORE_new()
four
5 //②
6 X509_STORE_add_cert(x509CertificateStore, x509Certificates)
7
eight //③
9 OpenSSL_add_all_digests()
10
11 //④
12 let outcome = PKCS7_verify(PKCS7Container, nil, x509CertificateStore, nil, nil, zero)
13
14 //⑤
15 if end result != 1
16 throw ReceiptValidationError.receiptSignatureInvalid
17
18

① The X509 Store is what holds the knowledge for verification, so we use X509_STORE_new() to create one.

② Subsequent, X509_STORE_add_cert perform is used to organize the X509 Retailer, and the X509 Certificates for verification functions.

③ OpenSSL keeps an inner desk of digest algorithms and ciphers. It makes use of this table to lookup ciphers by way of sure features. OpenSSL_add_all_digests() known as to load the required digest algorithms for verification.

④ The ultimate step is to use the PKCS7_verify perform, passing it the PKCS #7 Container, and the x509 Certificates Retailer.

⑤ PKCS7_Verify will return 1 if the signature is valid. If PKCS7_Verify returns any integer worth aside from 1, the signature is to be interpreted as invalid.

Last ReceiptSignatureValidator implementation

The ultimate model of the ReceiptSignatureValidator appears like this:

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

Additions to ReceiptValidator

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

1struct ReceiptValidator
2 let receiptLoader = ReceiptLoader()
three let receiptExtractor = ReceiptExtractor()
four let receiptSignatureValidator = ReceiptSignatureValidator()
5
6
7 func validateReceipt() -> ReceiptValidationResult
8 do
9 let receiptData = attempt receiptLoader.loadReceipt()
10 let receiptContainer = attempt receiptExtractor.extractPKCS7Container(receiptData)
11
12 attempt receiptSignatureValidator.checkSignaturePresence(receiptContainer)
13 attempt receiptSignatureValidator.checkSignatureAuthenticity(receiptContainer)
14 return .success
15 catch
16 return .error(error as! ReceiptValidationError)
17
18
19

Handling errors

The final piece is to aim to do something clever with any of the attainable error circumstances that could possibly be included with the ReceiptValidator validation end result. Right here’s a sample implementation on the name website for validateReceipt() (in all probability in a view controller somewhere in your app:

override public func viewDidLoad()
super.viewDidLoad()

let validationResult = receiptValidator.validateReceipt()

change validationResult
case .success:
// Allow app features
case .error(let error):
print(error)
receiptRequest.start()

“`

In the case where the receipt signature is invalid, my only thought proper now’s to request a new receipt from the app store and try and re-validate it.

Wait, there’s more? Briefly, yes. We’ve made vital progress, however there’s still more work to be carried out if you want to absolutely validate a receipt in your app, or for an in-app buy.