Services • iOS, OS X • 57:35
Receipts provide a valuable record of the sale for an app or for any In-App Purchases made from within an app. You can add receipt validation code to your app to prevent unauthorized purchases and protect your content. Learn how to verify exactly what people have paid for and how to validate these transactions.
Speaker: James Wilson
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Hi. My name is James Wilson and I'm an engineering manager. And one of the things that my team works is on the frameworks that power the App Store and the iBookstore in OS X, which in course - of course includes the unified receipt format that we introduced last year.
So in this session, "Preventing Unauthorized Purchases with Receipts", what we're specifically going to talk about is a way in which you can use this concept of a receipt to protect your revenue and enforce protection of your business model directly into your app, as well as into your servers that are issuing content based on an in-app purchase having been made.
Ultimately, we use receipts to know exactly what the customer has paid for. And you can do this both within your app, so you can build logic into your app binary itself that verifies the receipt, knows that it came from Apple and knows exactly what the user has paid for, as well as you can engineer logic into your servers to do that same verification to make sure a real monetary transaction has occurred with the App Store to make sure that you're going to be paid for it. All this comes down to one simple thing, protecting your revenue.
Now, the receipt is a lot like the physical proof of purchase, docket or receipt that you get when you're shopping in a store. It's the same sort of concept. It's a trusted record of the app and in-app purchases that have been made. And just like that physical receipt that you get when you're shopping in a store, it's the way that you can build security into your app to make sure that you are going to be paid for those features and content that you're unlocking and providing to your users. You know, just like a department store can look at your receipt and know exactly what you paid for before you walk out with goods, you can do the exact same thing in your app, and that's what we're going to show you in this session.
The receipt itself is stored on the device and there's APIs that you can use to access it. And the receipt is issued by the App Store. We issue a receipt every time a transaction takes place. So when an app is purchased and installed, there's a receipt in there. When an in-app purchase occurs, a receipt is issued and it's available to you to verify that purchase. Likewise, when previous transactions are restored, another receipt is issued that will allow you to verify the authenticity of those purchases.
And we achieve that by having the receipts signed in a way that is verifiable by you at the code level. So once you get this receipt, you can confirm it really came from Apple and hasn't been tampered with at all. But most importantly, the receipt file is unique to your app on a single device. It makes sure that when you verify that receipt, you can be absolutely certain that this purchase happened for your app, from this user, on this single device alone.
Now, when we're working with receipts, as you'll see in this session, it's a lot like a recipe. There's a recipe that Apple gives you, but there are things that you are going to need to bring to the table as well. There's decisions that you will need to make along the way to make sure that your receipt implementation, your ability to protect your revenue in your app, suits your unique needs.
There's a lot of flexibility built into this system. Now, in particular, what Apple provides you with is the receipt format specification, and we've built this receipt on a bunch of open standards. There's nothing proprietary about these receipts and the great thing about that is, because they're all open standards that are very, very widely used in many places, you have lots of options available for you for how to work with receipts.
We give you the receipt itself and there's APIs to find where the receipt is located. And we're giving you instructions on how to do on-device receipt validation, and that'll be the main focus of this session. And we also provide an online service that allows your servers to verify the authenticity of a receipt. But what you, as the developer, need to choose, is the level of security that's appropriate for your product. And likewise, that will translate into determining the level of complexity in your implementation that suits your needs.
There's three key decisions that you're going to need to make, and we'll call these out as we go through this session to help you with that. The first decision you're going to need to make is how will you verify the signature in the receipt? What will you use to determine that the receipt definitely came from Apple and hasn't been tampered with since we minted it? Second is how will you verify that the receipt is intended for your app on this device? And lastly, you will need to decide how you wish to interpret that purchase data that's contained within the receipt itself so you know exactly what the user paid for.
When you're making these decisions, the primary thing that factors into this is the value of your products and therefore how much importance you place on protecting that revenue. Here's one way to look at it. If you are a very high value product, you're typically selling perhaps an in-app purchase that's designed to be a one-off purchase, large value, and that one single in-app purchase is the primary revenue stream for your app and the primary enabler for all the features and content that you're selling to the user.
Now, in that sense, you're a lot like a high-end jewelry store. And when you think about your shopping experience in a jewelry store, you notice that they implement very complex levels of security to make sure that not one single pearl can walk out that door without having been paid for.
You know, when you go to a jewelry store, you see jewelry hidden behind glass cases. You see security staff positioned at the door. You might see security wandering through, making sure that everything's okay. There might be metal detectors, you know, proof of purchase checking, et cetera. It's a very high level of security and quite complex. But the reason they go to these lengths is because their business model is centered around that one-off large purchase that you're not going to make very frequently.
But at the complete other end of the spectrum is lower value product and these are typically the lower value in-app purchases, dollar, $2, maybe up to $5, and they're typically things that are consumables and your business model probably relies on customers coming back again and again to enjoy the same product, buying more currency, buying more gas for their car, more blocks so they can build amazing things. In this sense, you're a lot more like a grocery store. Think about your shopping experience in a grocery store. The grocery store wants you to feel incredibly comfortable and happy shopping there so that you'll come back again and again and again and keep spending with them.
Grocery stores are filled with hundreds and hundreds of products that are probably selling for $1 or $2 and not much more. They rely on the same sort of business model that you would if you were selling those low-value consumables. They want you to have a great shopping experience to feel comfortable and welcome and to enjoy it every single time. And they're not going to care too much if a couple of grapes walk out of the store without being paid for, unless you keep coming back again and again and stealing the grapes. So let's take a look at how we work with receipts in the workflow.
Starting off with the basics, the receipt itself is stored in the App Bundle or in your Sandbox container, and there's an API and NS bundle that gets you the URL to locate where that receipt is. It's a single flat file. So it's really easy to work with. It's not too complicated. Contained within that single flat file is purchased data about the app purchase itself, as well as in-app purchases that have been made.
And there's also a signature that allows us to check the authenticity. You see, we take that purchase information, what you're particularly interested in as a developer, and wrap it around certificates and signatures that make sure that when we write that purchase information into the receipt, we sign it and add a signature that means you can verify that it's exactly as we intended when we minted the receipt and no one's tried to fake it by adding in any products, changing purchase information, et cetera.
And we built this on open standards. In particular, the signing that we use to wrap that payload of data in a signature so it can't be tampered with is known as a PKCS#7 Cryptographic Container. Don't let the acronyms fool you here. There's a lot of information available online and these are very widely used in many, many places.
The data itself in that payload, the purchase information, we encode that. That is, we structure it at the byte level, using a standard called ASN.1. Now, you would be amazed at the huge number of places that ASN.1 is used. It's a standard that has been around for a very, very long time, which is great because when you combine the fact that both of these are very widely used, it means that you're going to find that you're not the first person to try and implement support for this. In fact, as we progress through this session, you'll see that all those decisions you need to make, they generally span this gamut. From at one end of the extreme, there will be off the shelf offerings that you can simply pick up and run with straight away.
In the middle ground, there's probably a lot of sample code out there that you can find and maybe copy and adjust it to suit your needs somewhat. But then, because these are open standards and the specifications are freely available, you're completely empowered to write your own implementation if that was important to you. That is, if you want complete control over how you protect your revenue, you can do that.
So some of the options. OpenSSL, which I'm sure everyone has heard of, is a cryptographic library that serves many functions beyond just HTTPS and secure sockets that it's more widely known for. OpenSSL includes support for not only doing the PKCS#7 signature verification, but also for reading ASN.1. So it's kind of a one-stop shop if you wanted a library that was available that could do this for you. There's also some other command line tools that can help you out with this, and I'm sure there is a lot of other offerings out there. But you can still create your own if that's what you want to do.
So what I'm going to take you through in this session is a three-step process that you can use to verify these receipts. The first thing we have to do is make sure we know that the receipt is authentic and trusted. That is, it came from Apple and no one's tampered with it.
Once we know we have a receipt that is authentic and trusted, we can move on to confirming that the receipt was in fact intended for our app on this device. Because just as it's important to make sure that no one's tampered with the receipt, we also want to make sure no one's just copied it from another device.
Now, once we have a receipt that we know we trust and it's for our app on this single device, we can go ahead and start interpreting that purchase data to know exactly what the user has paid for. I'm going to start this off with a demo. I have here a very simple project that's just going to get us started in terms of getting a receipt to work with.
I mean, all of this is great, but if you haven't been able to get a receipt to actually start verifying the signature with, this process can be difficult. So let's see how we go about doing that. Really simple app. Application did finish launching and the first thing I do is I call this NSBundle API to get the URL to the App Store receipt.
I'm simply going to use NSFileManager here to determine if the file exists or not. That is, do we have a receipt to work with? If we do, I've set myself up a method that can go ahead and do that validation for me. But if not, if I don't have a receipt, what I do is, on OS X, I exit my app with this special exit code of 173. That tells the OS and the App Store that you believe your receipt is bad or missing and it lets the App Store go and get a receipt for you.
So this is all in the documentation and it seems pretty straightforward at this point. So I'm going to clean this project to make sure we have no receipt. I'm going to build it. I'm going to run it. Now, notice I've got two breakpoints set up here. The first is I wanted to check that I got my App Store receipt. And if we see down here in the debug console, I definitely did.
And I'm going to ask FileManager if that receipt exists. It says no. Of course, because I'm running from Xcode and I have not yet put my app on the store. So of course we don't have a receipt. But we want one so we can start testing out validation. So the documentation says to exit(173). Let's do that.
And nothing happens. This is the first point the developers kind of get stuck with. They get started with receipt validation, put their code in place, run it in Xcode, and go why doesn't this do anything? The reason for that is when you run your app in Xcode like that and it hits that exit(173) code, only Xcode sees that exit code.
So the OS and the App Store is not aware that you're actually running your app and wanting to get a receipt. So here's a trick to help you do that. This time when I run the app and I hit the same breakpoint, I'm going to go down here to the Dock, control+click on the app icon down here, and I use options, show in Finder.
That brings up Finder to my debug-built version of the app that we're running. Because remember, I want this to exit with that special code and for the App Store to see that exit code so it gets me a new receipt. So watch what I do here. I go back to Xcode and I stop that instance of the app.
Back to Finder and now I run the app from Finder. And look what happened. We did the same logic, receipt wasn't there, and we exited 173. Now, the App Store has seen that and is prompting me to sign in so that we can go and get a receipt to start working with. And because my app was signed with my development certificate, I'm connected to the App Store's test environment using my iTunes-connected test accounts to get a test receipt for us to start working with.
So I sign in, we get our receipt, and my really simple app has finished launching with the receipt. Now, just to double check, if I go back here into Xcode, we go to our URL to the receipt, and this time we have the receipt and we're ready to start working with the validation of that receipt.
Now, if you are working with iOS receipts, one other tip that you might find handy is if you're having trouble getting a receipt in the test environment, make an in-app purchase. When you make an in-app purchase, you will get a new receipt and you can use that to test your validation code. So let's go to first step, verifying the signature in that receipt. So we know that this receipt is authentic, untrusted and unaltered.
So this verification of the signature, make sure the receipt hasn't been altered since it left Apple, because we don't want anyone tampering with it, and that it came from Apple so that no one else can just mint their own receipts that look otherwise valid, but are in fact frauds.
This is where the PKCS#7 cryptographic container comes in. It's how we wrap that protected body of purchase information around those signatures and certificates so that we can confirm authenticity. Now, your options available for you for doing this authenticity check, we mentioned before there's - OpenSSL can do this for you, there's no doubt many other frameworks out there that can do this for you. But of course, you can roll your own if you choose.
To get you started, though, the first thing we need to do is find that receipt. We call [NSBundle mainBundle] appStoreReceiptURL. That gives a URL to the receipt. Once we've got that URL, we need to get it read into memory so we can start working on it, and that's as simple as using something like [NSData alloc] initWithContentsOfURL. I'm going to walk you through here an OpenSSL example of how to do this signature verification. This is straight out of our documentation we have online.
But one thing to note is, this example makes two assumptions. We assume that you've already read the receipt into memory and have stored it in this variable called b-receipt; And we also assume that you've got a copy of Apple's root CA, our Route Certificate Authority, which you can find online, and you have that in your App Bundle and you've read it into memory and stored it in this variable called -x509;. Why 509? Because x509 is the standard by which that CA is encoded.
So once we've got those two key ingredients, the first thing we want to do is convert that blob of binary that represents the receipt in memory into a useful data structure that we can start working with. And we do that by calling the really simply and aptly named d2i-PKCS7-bio.
Don't let these names fool you. What you'll see here is despite the fact that these were named by someone very arcane, these are simple concepts. We're reading a file into memory, we're setting it up in a structure so we can work with it, and we're checking - we're calling some functions on it and checking the result. Anyone can do that.
So once we've got our data structure, we've got our receipt in one hand. But the other thing we need is our certificate that we expect this receipt to match up against. Right? We want to compare these two things. We have a receipt, but we want to make sure that it came from Apple and hasn't been altered since Apple created it. So we set up this certificate store and we add into that store Apple's Root CA. Because if the certificate - if the signature in the receipt is valid, it has to appear that it's come from Apple's Root CA.
Now, we get into the meat of this. We call the much better named PKCS7-verify function, pass into it our receipt and our certificate store, and we just check the result. If the result is equal to one, that means our receipt signature is valid. It came from Apple, is trusted, hasn't be altered since Apple minted the receipt.
Now, you also get another bonus from calling this function and that is that it will return back to you the actual payload of the receipt itself. So that body of purchase information that you can start inspecting to know what the user paid for. And you get that back in this example in the b-receiptPayload variable.
So I have another project here that builds upon that simple example that we saw before. You see here that I have my same did finish launching method where I check the receipt. We know we've got a receipt from the last demo. And so, I've fleshed out my validate receipt URL method with these exact same calls that we just saw in that slide before. So I'm going to build this.
Huh, 12 warnings, 12 errors. Not a great start. Again, this is another point that the developers can find really frustrating with this process. When you take this example code and try and use it, you're going to run into a few roadblocks. But we can get through this. The first thing is, the reason you're getting all these compiler warnings here, telling you that all these methods were deprecated, is because, in fact, they are.
Why am I up here on stage telling you to use deprecated methods? We deprecated these in OS X 10.7 because we want developers that, especially if you're concerned about, you know, strong cryptography and security like this, we want developers to create and roll their own build of OpenSSL as a static library and integrate that OpenSSL build straight into your application's binary.
Think of it this way. When you build your app and link against things like UIKit and AppKit, that's an external dependency. Your app can run on one machine, on one version of the OS, and run just fine on another version of the OS, even though AppKit and UIKit might be very different. They're external dependencies that can be swapped out from under you and, as long as those APIs match up to what your app is expecting, it'll run.
But imagine how easy it would be for someone who wanted to try and attack your app, to try and rip you off, to simply swap out an external dependency, like OpenSSL, for a version of OpenSSL that they brought themselves that simply said, "Yeah. Everything's fine. This receipt's valid.
Trust me." You see? When you use that external dependency for strong cryptography and privacy sort of stuff like this, you can run into problems where you create a big attack vector that makes it really easy for someone to circumvent the logic you're putting in your app to protect your revenue and enforce your business model.
So what we need to do here is build our own OpenSSL. Now, that's not as nearly as scary as it sounds and I've got some tips for you in the next slide. In fact, I've already built my own OpenSSL and I have it here on the desktop in a folder called OpenSSL.
Now, when you build OpenSSL, it produces two things for you, a folder called "include", full of the header files for what we want to work with, and also a "lib" folder that contains that .a static library that we've built. Now, using this is actually pretty simple. I like to keep my projects pretty clean, so I'm going to go ahead and create myself a group here called OpenSSL, if I can spell.
There we go. And all I have to do is simply drag and drop this built product from OpenSSL straight into my project, header files and static libraries. Make sure you click this "Copy items into destination group folder". That makes sure that we copy those headers and the static library you've built directly into your project itself. Otherwise, you'll have a dangling external dependency that might go away. So finish. I add in my freshly built OpenSSL to my product. Cool. Build it.
Build succeeds, but I still have 11 compiler warnings and, if you're like me, you care a lot about your project building with zero warnings. So why are we still getting these deprecated warnings? Because Xcode is trying to build against the SDK that ships with the OS that has that deprecated OpenSSL.
The way we get around that is we have to tell Xcode that for this app that we're building, we actually want Xcode to use our OpenSSL that we just added to the project. So I went to my project settings here. I have clicked on this target and I'm going to go to Build Settings. And as an option, you'll find an item called Header Search Paths. I'm going to double-click on that and I need to tell Xcode here where to find those OpenSSL files that we just added.
An easy trick for doing that is, pop this open here on the side panel, find one of the headers, open up this side inspector, and there you'll see the full path to where that header file is located in the project. Now, notice in this case, I've got it within my project folder and it's in a folder hierarchy called Include/include.
Okay. So we just need to tell Xcode to go and allow files to be imported from that location for headers. So I double click here and rather than having to enter the full directory path, I can use a shortcut of saying it's in SourceRoot/Include/include, because that's where we saw the file just before.
I add that in, put it to the top, because I want that before anything else in the system. I want to make sure we look at my OpenSSL files first. Done. We see that was updated there and close this file and go back to our AppDelegate, where we had all these warnings.
Done. All those compiler warnings went away. Because now, we're building our project against our OpenSSL that we built, that we know the integrity of, that we're going to ship built into our app binary to protect our revenue and check our receipts. So let's run this and see how it goes.
So we've already got a receipt from the earlier demo that we did and now we can enter our validateReceiptAtURL method here. These are the same calls we saw just on the slide before. I load the receipt into memory. I load the Root CA in the memory. d2i-PKCS7-bio to load that ball of receipt into a usable data structure called P7.
Create my certificate store and now I'm ready to do the magic. Does this receipt stack up against that Root CA Certificate? I step over that and if I look down here in the console, sure enough. Result is 1. That means we can now move forward knowing that we have a real, valid and trusted receipt that we can start getting purchase information out of. And well, before we do that, we need to confirm that it's for - intended for our app on this device.
But now, we're over the hurdle of not only working with the receipt that we got from the test environment, but we now got OpenSSL integrated into our project to be ready to be built for both iOS and OS X. Although I'm demoing everything here using OS X, everything I cover here applies to iOS, just as it does on OS X, and if there's any platform differences, I'll call them out for you.
A couple of tips on building OpenSSL for you. First is, you need to build it as a static library, not a dylib, not a framework, not something that can be external to your binary. You need it to be a static library so that when you build your app and link it, it takes that .a and puts it right into your app's binary so no one else can swap it out from you.
Now, if you're building for multiple architectures, which you almost certainly will be if you're building for iOS, for all the different arm architectures, then what you need to do is build a separate .a, a separate static library, for each architecture you want to support, be it arm, armv7, arm64, whatever you might need to build for.
Or for x86-64 in OS X. Now, when you create those individual .a static libraries for each of those architectures, you use the lipo command tool to take all of those little .a architecture slices and combine them into one fat binary that is one single .a static library that combines all of our slices for all of our architectures we want to support.
And then, you can just drag that straight into your project, like we did just before. Now, when you build OpenSSL, the first thing you do is use a configure script to configure OpenSSL for the platforms you're building on. Here's two tips that you'll find handy. If you're building for OS X 64-bit, configure it with the darwin64-x86-64-cc host type.
If you see documentation trying to tell you to use some of the BASD generic types, you're going to run into trouble. So this is much easier and it ensures a proper build of OpenSSL on x86-64. But if you're using - if you're building for iOS, try using the iPhone OS cross host type in the configure script to configure it for iOS accordingly.
But you will definitely not be the first person ever that has tried to build OpenSSL for either OS X or iOS. We deprecated OpenSSL a while back in OS X and we never shipped an OpenSSL in iOS that developers can use. So lots of folks have been through this for lots of different reasons. There's lots of examples available to you online.
Two notes about doing the verification. No matter how you do the verification, be it OpenSSL, roll your own, or another offering, do check the expiry date on the certificate. When you're contacting a web server via HTTPS, it's important that you verify the expiry date of the signature. You don't want to be talking to a web server out there using a secured transport with a certificate that has since expired.
But the reverse is true here for receipts. Keep in mind the fact that we mint this receipt at a point in time and that comes with your app when someone downloads it or makes an in-app purchase. Now, at that point in time, there will be certificates included in that receipt that have an expiry date.
If that - if someone buys your app today and then a year down the track, that certificate expires within the receipt, that does not make the receipt invalid. The receipt is just as valid as it was the day that you purchased it. And it would be a terrible experience for that user if all of a sudden your app stopped working only because the receipt has a certificate in there that has since expired.
But what you do absolutely want to make sure you do when you do your receipt validation is evaluate the trust of the certificates right up to the Root CA. You see, the certificates exist in a chain. But at the head of the chain, at the root, has to be Apple's Root Certificate Authority.
That's the only way you can be absolutely certain that it came from us and not from someone else trying to pose as Apple. I want to take a quick word, though, about examples and sample code. I've mentioned a lot throughout this session that there are a lot of samples out there. There's examples I'm sure, there's off the shelf offerings that you could literally download and put straight in your project that would do this for you.
But that convenience comes at a price. You have to keep in mind that reusing code brings with it bugs and vulnerabilities. No matter how good the developer is, no matter how well you use the sample code, no matter how much you might have paid for it, any code comes with bugs and possible vulnerabilities.
And the more people that use the exact same way of doing receipt validation, the more there is the chance of one exploit affecting many, many apps. You know back in that jewelry example imagine if every jewelry shop around the world used the same lock on their cabinets, and then one day someone finds out a really easy way to open that lock.
Not going to be good for anyone owning a jewelry store, right? Same sort of principle here. Although this can seem so convenient to just go and grab something off GitHub or somewhere else, and drop it into your project, and, "Hey, I've got receipt validation and protected revenue." You need to understand the risks of what you're bringing in because that's your revenue stream at the end of the day, not the third-party that wrote that code, not the nice individual that put up some sample code to help you get this done. It's not their revenue. It's your revenue.
So make sure you make decisions that suit your product. Maybe you're okay with those sorts of vulnerabilities, and bugs, and the possibility of a single exploit affecting your app. Maybe that suits the business model and level of protection you want. But either way, know the risks and own those risks yourself. All right, so we've got a receipt that we know is trusted and verified, and it came from Apple. Now we need to make sure that it is intended for your app on this device only.
To do that, we need to look at the receipt payload itself. Now this is a very high-level look at how a receipt is structured. It's structured as a series of attributes. You could think of these attributes as a cross between an NSArray and an NSDictionary. They're like an NSDictionary because they have things like a type and a value, a lot like a dictionary has a key and an object. But they're like an Array because there is a sequence of these throughout the receipt that you can loop over to interpret.
They also have a version on each of these attributes, but that's not too important to us right now. The key thing we need to know is inside that body of data in the receipt is a series of attributes. Each attribute has a type that tells us what it is, what it means, and then a value that corresponds to what this attribute is telling us.
So the first thing we need to do is check if this receipt was actually intended for our app. In those attributes there'll be one called Type 2 and one called Type 3. All the attributes are named by a number. So attribute Type 2 in the receipt, if you read that attribute's value, it will be the Bundle Identifier that this receipt was intended for. And then Type 3 is the Bundle Version that this receipt was intended for.
So you can check the bundle identifier, make sure it's for your app. You can check the bundle version if you want an even higher level of security. Perhaps you want to make absolutely certain that someone's not using a receipt from an older version of your app that perhaps doesn't have the same features and content.
The one thing to note is use hardcoded values for the comparison. Why? Because if you simply grab the values out of your Info.plist and compare them against the values in the receipt it's all too easy for someone to go and edit your Info.plist and make it look exactly like the receipt that they're trying to fake you with. So hardcode those values into your app that you're expecting, that way they're part of the code signed at binary, much harder to try and circumvent.
Now that was a very high-level look at how this receipt data is structured in terms of attributes. But as I said before, this is encoded, as in the bytes are arranged, using a standard called ASN.1. There's two other attributes along with Type 2 and Type 3. So we use Type 2 and Type 3 to confirm that this receipt was for our app, but now we need to confirm that it was actually intended for this device only because we don't want someone to simply be able to copy a - a receipt around to their friends so that they can use the app without paying for it.
So included in the receipt is an attribute called Type 4 which is an opaque value, just a blob of bytes, and Type 5 which is a SHA-1 hash. Now if you're not familiar with hashing, hashing is a way to take a large bit of data and boil it down to a unique, smaller data that will be absolutely unique to that larger data set that was the input to the hash.
So this Type 5 attribute is a SHA-1 hash of 3 key values. It's a Bundle ID, plus a unique device identifier like the GUI on OS X or the device for vendor on iOS, and also this Opaque Value. How does this work to actually confirm that it's intended for your device, for a single device? You see, the App Store knows these three key pieces of information at the time that purchase is made.
When someone taps the buy button on your app or on an in-app purchase and completes that transaction the app store knows three key things. One, we know the Bundle ID of the app making the purchase. Two, we know the device identifier from that device because it's sent to the store when that transaction is made. And three, on server side we create this opaque value, this bit of random entropy that we inject into this process to make it harder to attack.
So the App Store knows these three key things at the time of purchase, creates the SHA-1 hash, bakes it into the receipt along with that opaque value that we used. But the great thing is that your app can know these same three values at the time of doing receipt validation.
So because you know these three values, and you can string them together in that long concatenation of bytes and create the hash, and the App Store knew these three key values and created the same hash and put it in the receipt, if the hash you calculate exactly matches the hash that the App Store created and put in the receipt then you're certain that this receipt was intended for your app on this device only. It's completely unique to this device.
Now about ASN.1, these high-level - this high-level look at the attributes that explains how we structured them, and how you work with the different attributes, and how we store them as an array of attributes with a type and a value. But how are they encoded at the actual byte level? How do you work with this in code? The way you do that is by harnessing ASN.1. ASN.1 is this way in which we can use a textual definition that you see here onscreen to describe how the bytes are arranged in the receipt.
We provide you with this ASN.1 definition of how we've structured the receipt, and what you're seeing up onscreen is that you have a receipt module defined, and in that receipt there is a payload, and that payload is a set of receipt attributes. And the receipt attribute will be part of a sequence, and it contains a type, which relates to that type that we just saw before, as well as a value that is an octet string or a bunch of bytes. And that refers to things like that Opaque Value in the SHA-1 hash that we looked at just before.
Working with ASN.1, the trick is you need to find a way to go from that textual definition that's based on the open standard of OpenSSL to actually being able to read, and write, and work with it in a code level. So there's a few options for you because, of course, this is very widely used. One option is, again, OpenSSL. Just as it did the PKCS7 verification it can read ASN.1 just fine. You can create your own parser if you wanted to, to again have full control over this. Let's look at an OpenSSL example.
When we did the first of verifying the recipe we got back that P7 data structure. Remember that? Now we can use that same data structure to actually get that payload of receipt data that's encoded in ASN.1. It's buried in there in the P7 data structure that we see there. Once we've got that we can call this function called ASN1-get-object to start pulling those objects out of that ASN.1-encoded byte stream. The objects are things like there'll be an object for the top-level receipt.
There'll be an object in there for the Payload. There'll be an object in there for the ReceiptAttribute, and then subjects under there for each of the type, value, and version. You see, it's arranged very hierarchically, and that follows the same structure that we just saw in this ASN.1 textual definition.
Now this is not perfectly coded, but it gives you an example of how in which you would work with ASN.1 in OpenSSL if that's how you choose to do this. You'd set up a while loop to essentially move a - move a pointer throughout that stream of bytes that represents the receipt. We want to basically walk through the receipt ad read it as we go.
So we set up the while loop and start calling ASN1-get-object to pull those attributes and values out of the receipt. So I make one call to get my attribute, for example. Another call to get the type, and notice I'm moving that pointer value around like a finger following words in a book. And I'm calling ASN1-get-object to read each of those words out and so I can use them. After a few more calls to ASN1-get-object I'm ready to switch on that attribute type so I can see what I'm looking for.
In this case, just for a simple example, I'm looking for Attribute Type 2, which we know is a BundleID. And then I can start processing that by calling ASN1-get-object to actually read the string value and compare it against my hardcoded BundleID. But of course I want to show you that this is a real world thing that anyone can do, so here's my demo. Now same as before, I've got a third project here that builds upon the same two samples that we've seen so far.
You see here we have the old familiar loading up of the receipt, certificate store, and then calling PKCS7-verify. But now when we get our result of one, and we know this thing is valid and unaltered, and we want to start reading the purchase information out, I'm going to go ahead and start using OpenSSL to do that, to actually read the information about what someone purchased.
So just like we saw before I find the actual octet string, that stream of bytes that represents the receipt, buried within my P7 data structure. I start calling this ASN1-get-object method to start reading those bits of information out of that byte stream one at a time. When I start doing that in my while loop to walk over each of those bytes you see that I set up that pointer, and each time I call ASN1-get-object here I advance that pointer along, just like the finger following the words in a book.
So I make a few calls to ASN1-get-object because I want to get things like the attribute type, attribute version, and then the actual object itself. Set up my switch statement, and I've got a case 2 here looking for BundleID, and a final call here looking for ASN1-get-object that allows me to form the string which will be a bundle ID. Let's run it and see it work.
Notice by the fact that I had done the OpenSSL integration before in this project I don't have to mess around with that again. This project is already set up just like we had before. So I've got a receipt, great. We move into our validateReceipt. We've gone and called the PKCS7-verify here to make sure the receipt is trusted and unaltered, and it is. Now we can start reading it. So I've got the byte stream. I start calling ASN1-get-object to read that receipt in. Then I set up my while loop to iterate over each of that series of attributes in the receipt.
And as we see as we sail down this first attempt at getting an object out of the receipt, we get all the way down to our Switch statement, and if we pull up the debug console here you'll see, in fact, what it found first is Attribute Type 4. So as you iterate over this while loop, it's essentially finding each of those attributes that we saw in the receipt, and you set up the Switch statement here to work with them accordingly.
So in fact, if we let this run we would see a bunch of breakpoints hit here in our Switch statement, switching and - as we looped over each of those attributes, we can see this time we've got Type 4. Advance it again and we get different values as we go forward. And we can begin working with those to pull out things like the bundle ID to confirm that, the hash value, perform that hash to confirm that it is intended for this device.
All right, we're nearly there. We have a receipt that we know is trusted. We've confirmed it's for our app on this device. Now we can get down to the money end of this and find out what the user has in fact paid for so we know what to unlock, and we know what content to provide them.
Now along with those Attributes Type 2, 3, 4, and 5 that we just looked at, you will also see one or more attributes of Type 17. Type 17 is a record of an in-app purchase having been made, and this is how you verify that that in-app purchase was real and authentic and is going to result in money making its way to you.
The value of the Type 17 attribute is in fact a nested set of attributes itself. So you find the Type 17 attribute, get the value, and that value is going to be a nested set of attributes that tell you the quantity, product identifier, and the transaction identifier and purchase date for an in-app purchase that has been made. And again, we give you the ASN.1 representation of that so you know how it's structured.
Now if you have a paid app in the Store and have been wanting to make the transition to making a freemium app, that is free with in-app purchases, one thing you don't want to do is leave those loyal, paying customers out in the cold. If they've already paid the full price for your app, they shouldn't have to go and do a bunch of in-app purchases just to get back what they've already paid for.
So here's a trick you can do. Included in the receipt now is this Type 19 attribute called Original Application Version. That is the application version that the user originally purchased. So if two years ago I purchased this app the receipt will always reflect the version that was in the store at the time I made that purchase all those years ago.
And that's your way of knowing whether to treat the app as a paid version and give them what they've paid for or to know that they purchased the app when you were a freemium app to not unlock features and content until you see those in-app purchases being made. Now in terms of in-app purchases, different types of content have different lifecycles in terms of what is seen in the receipt.
If you are dealing with consumable and non-renewing subscriptions, so things like gas in a race car in a racing game, coins, blocks, currency, non-consumable - sorry, consumables are things that you buy, use on a single device, and they get used up. Those sorts of items, because they are that purchase that you can make again and again and are intended to be used on a single device, they only appear once in the receipt, and only in the receipt that's issued at the time of purchase.
So when that purchase is made - if you have 500 gallons of gas in the racecar - you need to interpret the receipt at that point in time and validate it, and then stash that state . That is, up the gas tank by 500 gallons or whatever it might be - you need to stash that state yourself. That transaction will not be present in any subsequent receipts that we issue, nor will it be present if you perform the restoreCompletedTransactions. They're a one-shot deal.
When the transaction is done you inspect the receipt, stash that state yourself. Now the total opposite to that is non-consumable and auto-renewable subscriptions. If you're selling non-consumables, like levels in a game, for example, those are things that a user buys once, and they would rightly expect to be able to use that game level on other devices. If I go and buy a new phone and re-download your app I expect to get back all the lower levels that I've already paid for.
So because these non-consumables and auto-renewable subscriptions have that sense of permanency about them, they are always in the receipt. And you can use the StoreKit APIs to restore completed transactions, which is how the user gets back all those things they've bought from you before in terms of non-consumable and auto-renewable subscriptions that are still valid.
What we've talked about so far is all about the happy path of a receipt being valid and true. What if receipts are invalid? What if when you validate this it's either missing or something appears wrong with it? The fact is, that doesn't mean something bad has happened. That does not necessarily mean someone is trying to rip you off.
There are many real world scenarios that your app will definitely run into where a receipt is missing or invalid through no fault of the user, not because anyone's trying to rip you off. But if you see this happen, what you need to do is refresh the receipt. On iOS, if the receipt doesn't exist or appears to be invalid you'd refresh it using StoreKit. StoreKit has the SKReceiptRefresh operation which allows you to - you can alloc and init this SKReceiptRefreshRequest object, set a delegate so you get the callbacks when the refresh is done, and then start the refresh.
When that request completes you should get back a new receipt that you can then re-verify. But note that, getting a new receipt, we have to talk to the App Store. So of course a network connection will be required which your user may not always have. Store sign-in will be required so that we can go and verify that this person really is them and check their purchase history before we issue the receipt.
But what you need to do is avoid continuous loops of refresh, validate, refresh validate, refresh, validate because each time you do that, the user's going to get stuck in this horrible loop of prompt for off. "Yes, I signed in." Still bad. "Oh, sign in again." And all this network traffic goes back and forward. So what you should do is, on launch, validate the receipt.
If it's missing or invalid you can refresh it once. If it's still missing and invalid that's it. Don't call refresh again. But OS X is of course a little different. As we saw before, if the receipt is invalid or missing you exit with code 173 to tell the OS and the App Store that you need to get a new receipt.
The OS sees that exit code, tells the App Store, and the App Store will go and get a new receipt for you. But just like on iOS, it will require a network connection and store sign-in will be requested. So this is definitely not something you want to do automatically on launch. Now when you - do you receive validation, and it appears to be invalid or missing, and you request a new receipt even if then you still don't have a receipt or it still appears to be invalid, what you do next is entirely up to you.
Invalid or missing receipts will happen. Everyone's app will see a condition where this happens, and it could be entirely legitimate. Here's an example. If I sync an app from iTunes to my iOS device, it lands on the iOS device with no receipt. Because it wasn't purchased directly from the App Store, the App store doesn't have that unique device identifier. We can't issue a receipt. So it lands on the device with no receipt, and when your app launches it'll find no receipt and need to refresh it if you want to validate the receipt.
So it's a real world scenario that will happen, but you need to configure the - you need to consider the case that that refresh of the receipt may not be possible. All right, what if I got my wonderful app from the - from the iTunes Store or the App Store, and I'm really excited about using it on the plane flight I'm about to get on. I plug my iOS device in, sync the app across, unplug, jump on the plane, no Wi-Fi.
"Ah, I can't get a receipt." It's up to you now to decide how you want to handle that. Okay, you should ideally match the user experience to the value of your app. And this comes back to the concepts we were talking before. I mean, you can allow full access to the app's content and features even if the receipt's invalid if you choose.
You could maybe have a - you could maybe have a grace period where you will allow some use of the app. You could limit access to certain areas of the app; perhaps there's one area of the app that is particularly valuable that you wouldn't want to let them access even if they can't get a receipt.
Or you could block functionality entirely. You could have the app do nothing until a valid receipt is seen. On OS X only, you could in fact force that app to quit if you wanted to, but that concept does not exist on iOS. On iOS, the app is always running, so it's up to you to decide what to do.
But again, think about which business model you are more like. Are you like the grocery store that wants a comfortable, really happy buying experience and usage experience every time, and maybe you don't care if one or two bananas goes out the door? Maybe you don't care too much if a receipt's invalid for a little bit of time.
Or maybe you are more like that high-end jewelry store, and maybe it's totally not okay for your app to be used in any way unless there's a valid receipt. It's up to you, but you should really think about the value of your product and how you want the user's experience to be if they don't have a receipt.
Now everything we've talked about so far is about validating that receipt on a device, but you can also do that same process on your servers. If you have servers issuing content based on an in-app purchase being made here's what you can do. When you request that content, send the receipt up to your server. Your server can talk to Apple's validation servers, and we will return back a block of JSON that will tell you whether or not the receipt is valid, and it will also include the purchase information.
Your server can interpret the purchase information and decide only to hand back that content based on that real monetary transaction having taken place. It's a great way to secure and lock down the access to that content that you're hosting online. Now for this server - server-to-server validation it's only designed for your servers to validate the receipt before they issue content. And it's your app that needs to send the receipt to your server. You need to secure that end-to-end communication channel, and then only your server sends the receipt to Apple's validation server.
Never, never, never ever send the receipt directly from your app on a device to the validation servers. Way too easy for someone to sit in the middle and return a false positive, and you'll also be exposing your shared secret for using that validation service. But the good thing is, the response you get back is in JSON, which is really easy to pass for virtually any server platform out there.
Now what you absolutely have to make sure you do if you're implementing receipt validation is, test it thoroughly. A bug in this area of code could be disastrous for you because it could lock someone out of the features and content they really have paid for, and I guarantee you that's a one-way trip to one-star reviews.
So test really thoroughly. How does your app behave when there's no receipt? How does it behave when the receipt looks to be invalid? How does it behave when the app - when the receipt appears to be valid after refresh? What if the refresh failed and it's still invalid? How does your app behave? And if you're selling your app using the Volume Purchase Program for business and education, be aware of the extra fields that are in the receipt to tell you whether your app has been allocated to a user or revoked.
But the really important message that I want to make sure gets through is that these are not edge cases. Your app will launch without a receipt. Your app will launch with a receipt that looks invalid. You know that example before I gave you of an iOS app being synced from iTunes? When you buy a new Mac and you move your apps from your old Mac to your new Mac, guess what? The receipt moves from the old Mac to the new Mac. So when your app launches it's going to see a receipt that was not intended for the new Mac.
So these are real things that happen. They're definitely not edge cases at all. Now testing this on iOS, you need to use the App Store's test environment. To do that, you run the app in Xcode, perform an in-app purchase to get the receipt, but you must have your app signed with a development certificate.
OS X? Similar but different. Build it in Xcode, run the app from Finder, remember. So Finder in the OS, and the App Store sees that exit code, and we know you need to get a receipt. When you exit with that receipt you must make sure - exit with that code, sorry, you must make sure that your app is signed with its development certificate.
Now in case you missed the very, very deliberate repetition in those last two slides, your app must be signed with your development certificate. Why? Well, because the first thing we do at the App Store Layer when your app says, "Hey, I want a new receipt," or, "Hey, I want to make an in-app purchase," is we inspect your code signature. If the app appears to be signed with your development certificate we know you're a developer testing your app so we route those requests to the test environment so that you can make in-app purchases, test your receipt validation without actually buying something.
But if we see the app is signed with a production App Store certificate, we know this is an app that's been purchased; it's out there in the wheel - real world, so therefore, we route those requests to the production store so that you get paid for the transactions the customer is making.
Lastly, a quick word on the app submission process, especially as how it pertains to receipts. When you're developing your app, Development-signed of course, and you're working with the App Store's test environment to test your in-app purchase and receipt validation, the receipts you get back are test environment receipts.
Now because your app may not already be on the Store yet, and you're using the test environment there'll be some difference in the fields that are present. When your app is on sale in the App Store and it's Production-signed you will see production receipts that have some more fields in them because your app is real and live on the Store. But App Review is different.
App Review uses test receipts using a Production-signed app. That's not something you can do, only App Review can do. But what you need to be aware of is that, when you're testing your app, if you try to get too clever and think, "Oh, I've found a way to determine that these receipts are from the test environment, and I never, ever, ever, ever want someone out there to be getting a receipt using my app from the test environment," if you try and implement code that rejects a test environment receipt because you're Production-signed, guess what? App Review can't review your app. We won't be able to use your in-app purchases, and therefore, it'll get rejected. So just be aware that you will have your live Production-signed version of the app that you submitted. It will see test receipts during App Review.
So if you'd like more information you can contact our Evangelists. There's also documentation online with the Receipt Validation Programming Guide. And of course there's the Apple Developer Forums, which are a great place to discuss this and ask for help. Some related sessions: on Wednesday I gave a session about optimizing your in-app purchases. That was around creating a trouble-free and smooth in-app purchase experience every single time.
And my colleague Rachel gave an excellent session earlier that morning that was about how to design for a great in-app purchase experience. It's great to protect your revenue, even better to have a trouble-free in-app experience every time, but you've got to also create that irresistible sales experience. So do go and check out these sessions. They were really good. Thanks very much.
[ Applause ]