Updated for Swift 3 (Feb 9, 2017)

Apple has given us a way to send a device’s receipt to their server to verify that it’s authentic. But if you want to prepare for those pesky hackers this is not enough, simply because you can’t control the connection between each person’s device and Apple’s server. The old solution for this was to send the receipt to your own server which you can keep secure, and then send the data to Apple and return the response back to the device. But no one wants to do all that so lets take a look at validating receipts locally on devices.

Apple has very high level documentation on this but it does not go anywhere close to what we need: https://developer.apple.com/library/mac/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html

A few articles and sources that I used for references can be found here, I would suggest reading up on each one if you are new to the subject.

http://www.objc.io/issues/17-security/receipt-validation/

http://swiftrien.blogspot.com/2015/05/osx-receipt-validation-in-swift-part-1.html

Apple also has various WWDC videos on this subject that are worth watching as well.

OpenSSL

If you don’t know already, Apple explains that you need OpenSSL in order to do this locally on a device, but iOS does not come with OpenSSL naturally. They do advise though that when including OpenSSL you need to add it as a static framework so it cannot be swapped out beneath you with a different version of OpenSSL that tells your app that everything is OK.

  1. People have already published various scripts for creating a static framework from the latest version of OpenSSL and the latest version of iOS on your machine. It will also compile all the frameworks (i386, x86_64, armv7, armv7s, armv64), One example https://github.com/x2on/OpenSSL-for-iPhone
  2. With the addition of Bitcode this past year and everyone’s need to have an app with Bitcode, make sure you’ve selected a script or an OpenSSL framework that supports Bitcode, else you will have to disable it for your app.
  3. You should end up with 2 files, libcrypto.a and libssl.a along with a folder containing all the header files.
  4. After adding these files to your project and you can build with no errors, you need to add the imports to your Bridging-Header.h file.
#import <openssl/pkcs7.h>
#import <openssl/x509.h>
#import <openssl/asn1.h>

Important Notes about OpenSSL:

Doing all of this with Swift 1.2 and Xcode 6.4, there were a few things I ran into. In the header file of rsa.h, there was an error with the line

int (*rsa_mod_exp) (BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx);

Since I’m not using RSA for this I simply commented this line out and everything compiled for me.

The second issue is discussed here http://swiftrien.blogspot.com/2015/05/osx-receipt-validation-in-swift-part-4.html, without these additions, I could not convert the Objective-C code into Swift correctly.

What I did, in the header file pkcs7.h after the declaration of the struct named PKCS7 on line 217, I added the following lines from provided in the above link.

 

Apple Root CA

One of the things mentioned through out Apple’s documentation and videos is validating the receipt with their Root CA. You can find it here http://www.apple.com/certificateauthority/ its the first one at the top left called Apple Inc. Root Certificate. Download this and drop it into your project.

The Receipt

I’m not going to cover ever aspect of this because there is already great information already out there, i.e. http://www.objc.io/issues/17-security/receipt-validation/. One thing I will note is the payload, or the actual part of the receipt that has the data we want about the in app purchases. Through the madness of decoding the ASN.1 payload, we are looking for different values that relate to different fields in the receipt. If you look at other source code for this you will most likely see a long switch statement somewhere checking for these. You can go here to learn what each field type is and what the field value matches to.

https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html

If you take a look at the link above, you might notice a section towards the bottom called In-App Purchase Receipt Fields, and each item’s field type has a value starting at 1700 which is much higher than the previous ones. If you are familiar with the receipt’s JSON, you’ll remember that each in app purchase has its own set of values, which is what these are. The field type 17, which is the In-App Purchase Receipt, has a field value of SET, which contains the in-app purchase receipt attributes, this set contains all the values that start with 1700. All of this is important to remember if you want to pull the product Ids from the receipt, which you do! Note: If the only validation you perform is sending the receipt to Apple and receive the nicely structured JSON of the receipt back, you don’t have to do any of this, but this allows you to validate your receipts offline and at anytime, and provides better security.

Time to get Swifty

There are 3rd party libraries out there on Github you can use for all this validation if you want, but as of this writing, they are all in Objective-C. Which might be fine for you, but maybe you want to do all this yourself and in Swift. Note: The general consensus with this is everyone should not use the exact same code for if a hacker learns how to bypass one app, then he can do the same to many more if everyone’s code is the exact same. So I will leave it up to you to structure it how you wish and name your variables to your own.

Now that the receipt has been verified, its time to parse it into a readable format. This is the most lengthly part.

Here is the method I used above for pulling out the product Ids from the receipt. You can easily modify this to do read in more fields if you desire.

If you are having trouble with getting figuring out which type belongs to what, like the V_ASN1_OCTET_STRING, if you print out the value of the variable ‘type’ you should get an integer. You can then go here http://www.obj-sys.com/asn1tutorial/node10.html to determine what data type that value belongs to.

15 thoughts on “Validating In-App Purchase Receipts Locally in Swift!

  1. Hi, Great article.
    One question. Why do you have the following line?

    if id != nil {
    pIds.addObject(id!)
    }

    the pIds are not declared and you’re not returning anything, so I’m trying to figure this out.

    1. Thanks for the comment!

      So pIds is just an array that I use to store all of the in app purchase’s ids. In my ViewController I can check this array and see which products have been bought and then update the UI accordingly.

      At the very end after
      if !computedHashData1.isEqualToData(hashData1!)
      {
      print(“Receipt Hash Did Not Match!”)
      return
      }
      else
      {
      print(“Receipt Hash Matches!”)
      if pIds.count > 0
      {
      //Loop through all ids, if we find one that matches set it as bought
      for item in pIds
      {
      let id = item as! String
      if id == productId1 {
      //update variable or a way to track this
      }
      else if id == productId2 {
      //update variable or a way to track this
      }
      }
      }
      }

  2. Thank you for your tutorial, it was really helpfull.
    I am encountering an issue regarding the receipt validation with this methods, some of my user ( 5 out of 300 ) don’t get their receipt validate when they install the application for the first time. But if they uninstall and then reinstall the application, everythings works fine.

    Has anyone encountered the same issue ?

    1. Make sure you have plenty of checks in your process, and log any instances of a receipt not existing or validation failing via Raygun or Google Analytics events. For instance, if the receipt is not already on the device use SKReceiptRefreshRequest to pull down receipt.

      //Get the Path to the receipt
      let receiptUrl = NSBundle.mainBundle().appStoreReceiptURL

      //Check if it’s actually there
      if NSFileManager.defaultManager().fileExistsAtPath(receiptUrl!.path!)
      {
      //Now lets do some validating
      }
      else
      {
      let refreshRequest = SKReceiptRefreshRequest()
      refreshRequest.delegate = self
      refreshRequest.start()
      print(“Did not find a receipt, refreshing receipt”)
      }

      Then add the delegate methods for SKRequestDelegate

  3. Hi Scott,

    Thanks for your blog post it has been most helpful. I was using your code and stumbled into a little problem. My receiptUrl was found on the device and stored in the StoreKit folder in the following location:

    /private/var/mobile/Containers/Data/Application/197EE0FE-0097-42B6-BCE3-B8D7B1A9575B/StoreKit/sandboxReceipt.

    However, the following line of code was always returning false:

    if NSFileManager.defaultManager().fileExistsAtPath(receiptUrl!.path!).

    Does your code only work with live production receipts or is it possible to extract data from the sandbox receipt?

    Kind regards,

    Tom

  4. Hi Scott,

    This is really helpful, thank you!

    Any chance of getting this updated for Swift 3?

    I’m trying to use it in a project and I’m getting lots of compile errors.

    Thanks!

    Andrew

  5. Please take a look at the 3rd code block on line 101,
    bundleVersionString1 = NSString(bytes: str_ptr!, length: str_length, encoding: String.Encoding.utf8.rawValue)

  6. Hi Scott,

    It seems that this part:
    let octets = pkcs7_d_data(pkcs7_d_sign(receiptPKCS7).pointee.contents)
    var ptr = UnsafePointer(octets?.pointee.data)
    let end = ptr?.advanced(by: Int((octets?.pointee.length)!))

    Can be rewritten in pure Swift without using any C code. The bellow code seems to be working:
    let pkcs7signed = receiptPKCS7.pointee.d.sign
    let pkcs7signedStruct = pkcs7signed?.pointee.contents
    let octets = (pkcs7signedStruct?.pointee.d.data)!

    var ptr = UnsafePointer(octets.pointee.data)
    let end = ptr?.advanced(by: Int(octets.pointee.length)

    Do you see any issue with it? It seems to be working fine in pure Swift.
    Thanks for info

Leave a Reply