The Problem
Your glorious iOS app features in-app purchases. And you have loads of satisfied customers making these purchases. However, the user count doesn't match your in-app sales numbers. How is that possible?
Become a Difficult Target
Face facts… out of the box, everything you have, from the server to the device, is insecure. Making these items hacker resistant takes work. But with a little effort you can improve cost recovery for your iOS in-app sales.
Okay, not all systems are insecure (OpenBSD). But that won't run virtualized, nor will it support the most current server apps. So, work is required.
iOS Pirates
If you're a regular reader of the postings in Apple's Developer Forums, you know that pirates are a common problem. You sold 45 copies of your app yesterday, but you're wondering where the 1,500 users you have pounding on your server came from. This is definitely a concern when client traffic costs you money that has to be paid to a cloud or service provider. Not to mention loss of sales.
Our Story
Our WorldPulse app, which sells quality weather data, needed to ensure in-app purchases were validated. That is because we pay per thousand weather queries.
We also sell a set of apps (see them here) that allow users to download the app for free. In-app purchases are available for added functionality, such as reading faxes, fast large sheet PDF rendering, different file types - like FITS - and extra performance, like the use of folders & Dropbox.
Below, we will outline the steps we took to reduce pirates from infesting our apps. Needless to say, our in-app sales increased 300% over an earlier comparative 6 month period.
Please note you should only use this document as a guideline. If you have a banking app or something of a critical nature, consult a professional. We're not dealing with professional criminals here. Just drive-by stealing using generic hack plugins.
How Do You Know It's Happening?
Our company collects detailed crash logs via HockeyApp http://hockeyapp.net. And because loading code to override the operating system api on a jail-broken device reduces device stability, the iOS devices crash. A lot. Apple lists the loaded binaries in the crash logs binaries, so we see the free in-app purchase modules being loaded.
The First Steps
Follow Apple's rules:
- Don't keep your store keys in the app
- Use SSL for everything
- Perform the receipt validation in the app or on your server
Server?
Choose a hardened Glass Seaside based server on a small Amazon EC2 machine. For this example we selected Ubuntu 12.04. But you could pick your favourite distro from the set of hardened server based Linux distros. Just make sure it runs Gemstone.
SSL?
The nice folks http://www.namecheap.com will sell you a basic SSL certificate along with a domain for under 10 bucks. So there is no excuse not to have one.
Out of the box, most web servers set up your SSL connection to support the lowest "standard" approved by the USA in the 1990s for Windows XP. You may wonder why. Consider what Edward Snowden said and you'll see why: http://en.wikipedia.org/wiki/Edward_Snowden.
You can tighten up this issue by following recommendations made by: https://www.ssllabs.com/ssltest & https://sslcheck.globalsign.com/en_US
You should not have any weak Microsoft Windows devices using your SSL connection. It should be iOS clients to your site only, which allows you to drop the compromised XP clients. After customization, you migrate from the weaker NSA approved security to Snowden approved strong security.
Sadly, various Linux distro conspire here and ship old versions of NGINX (our favourite) and Apache. They then require an upgrade in order to defeat server side attacks on TLS. See: https://bjornjohansen.no/install-latest-version-of-nginx-on-ubuntu
Beware of web tools that mix http & https. See: https://support.google.com/chrome/answer/1342714?hl=en for clues on how to ensure you get a pure https connection and why you didn't get a green padlock in Safari.
Hardened SSL
Our SSL is harden based on ssltest feedback to:
TLS 1.2 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 PFS
It is a bit of greek but stands for: TLS 1.2 Transport Layer Security v 1.2
http://en.wikipedia.org/wiki/Transport_Layer_Security
This Wikipedia entry gives a good overview and shows why out of the box is insecure (that X.509 certificate comes from your SSL certificate provider). But can you trust them? Who has access to the keys? http://en.wikipedia.org/wiki/NSAKEY
Note: Some work in AFNetworking might be required to confirm that SSL connection is actually made to the correct location. A proper certificate with confirmation at the socket layer will ensure kiddy script users are kept at bay.
Diffe-Hellman Key Exchange
1536 bits, which is the max iOS supports, is used to exchange the AES symmetric password:
http://en.wikipedia.org/wiki/Diffie–Hellman_key_exchange
AES Encryption (at 256 bits, which is the max iOS supports)
http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
All indications say 128 bit is fine. The iPhone 5S includes CPU instructions for AES support, so you won't notice the 128 versus 256 impact. Don't fret over CPU costs. Again, that is compromise talk. Your device will encode/decode bytes faster than the network will process the packets.
SHA 256 Hash (which is the max iOS supports)
http://en.wikipedia.org/wiki/SHA-2
CBC
Chained blocks, to ensure repeated text blocks don't supply enough information to guess the key or contents.
http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
PFS (Perfect Forward Security)
http://en.wikipedia.org/wiki/Perfect_forward_secrecy ensures your stolen private SSL key can't decrypt any previously recorded traffic. This is why the NSA records all https traffic into and out of the USA, with hopes the server doesn't enable PFS. Later, all the NSA needs is the stolen private key to decrypt hours, days or years of network traffic.
Secure Server?
Follow the "How to Make Your Machine Secure" for your particular Linux distro, via books or gems found in Google.
Don't forget disk drive encryption. This choice is debatable, but it's better to err on the side of caution.
Okay, Now We Have Secure Communications with the Server
At startup, our app stores the securely fetched in-app purchase keys in the iOS keychain, which is storage for the life of the device user, not the life of the app. It is secure, but we go a bit further and store the keys encrypted with AES 256 using a generated UUID and salt. Then we also store a SHA hash of the data.
When the data is needed, fetch the keychain data. Decrypt and compare to the hash. If that fails, the data is bad or it has been altered. Yes, the keychain is encrypted and unbreakable so far, but let us add another annoying layer just in case the keychain logic is compromised someday.
Some of our in-app purchases are consumables, so store the consumable data metric in the keychain as well. For example, if you purchase the rights to print 5 faxes, print five then delete and reload the app from iCloud. That won't give you the ability to print more for free. The data is remembered. It is difficult to reset the stored value even if you have access to the keychain, since a naive datum 3 out of 5 isn't what is stored in the keychain; rather it is an encrypted 3 with a hash.
Issues:
(a) Only apps with the matching mobile provisioning file can delete or alter items from the keychain. Hacked milage might vary.
(b) Some work with an attached debugger could delete the keys, but this is mitigated by using a framework that prevents the debugger from attaching to the app process. Not a foolproof solution, but again reduces drive-by hacking.
(c) Overriding the call api to capture and playback keys/values is possible, but that requires a great deal of work specific to your app.
Onward
Given a secure area to store and remember data, grab the keys from your server. As part of the request, consider pushing meta-data about the user device and user to the server. Now that you have those purchase keys, send them to the Apple Store framework for the pricing data.
This is all a bit tricky. Async and fraught with hassle; having two remote players, each of which can fail from a communications or from an expected response viewpoint. Read Apple's In-App Purchasing Manual very carefully. For example, would an employee remove an in-app item from sale? Would Apple's server fail to respond? Would yours? What could go wrong? On each step, consider retries and prompts. If you have issues, communicate clearly. This will reduce the possibility of your app being rejected by Apple.
Remember to use the Apple network connectivity code samples to ensure when apple.com or yoursite.com goes away, then the user can see when it is no longer possible to purchase an item.
The App
Don't forget the security levels used for file creation and keychain usage. Read the Apple Security Manual to understand which security model you should use to optimize security and their suggestions on user/password https validation or pinning. You can perform password validation or use a custom X.509 validation for your server. Under iOS 7 you can do VPN by application to ensure a pirate needs authorization to even connect to your https service. That makes looking for exploits on your server side much harder.
Purchasing, Restoring & Validation
MKStoreKit provides the infrastructure to support a purchase. If the purchase is granted by Apple, an encrypted receipt details what was purchased. Now, it needs to be validated. There are various source code solutions posted on GitHub to enable this; one by Matt Thompson who authored AFNetworking https://github.com/mattt/CargoBay. Our company has also used the following code base: https://github.com/evands/iap_validation. Both are viable candidates for reuse. Others might exist, but you would need to compare and contrast as some do not handle test versus production backends correctly.
Another choice is to do the validation on a server.
Server Validation
Send the data up to a server then validate via a Seaside Gemstone (Glass based server) to Apple. Return a hashed salted secret indicating if the validation was successful. Based on our experience of many purchases, the validation code in the app does weed out all the suspects, so only good data has been given to the server. Other developers who use pure server side validation have complained about server side attacks or exploit hunting. Use a hashed salted secret instead of OK / NOT OK to mitigate attacks.
Within your app, on the post server validation step, you might need to retry to the server a couple of times. Backing off in time on each retry, plus ensuring the code is all inlined versus having intent revealing objective-c method names. If this action fails, let the user know and provide a way to try again via prompts.
A major problem is that the in-app purchase and validation is chatty and slow; perhaps taking a few minutes to complete. Give feedback and don't let the user finish until it's done, retried, or canceled.
Restore
"Restore" lets a user restore all his in-app purchases to an app on his device or another device. This required action enables Apple to fire off many purchase async restores to your app, thus to your server. Normally an in-app purchase load might be a magnitude lower. Obviously one a second would be financially great, but a sudden peak at ten could stress things.
As part of migrating to the new in-app purchase model, our company made a choice to void knowledge of past transactions. Then ask the client to restore his purchases (if any) to ensure the data was valid. This led to a few hundred purchases from pirates who now had to actually purchase in-app features which they had previously stolen.
Summary
Switching from being an "easy" target to a "hard" target increased our sales by a factor of 3X.
Need a Package Solution?
If you are considering a GLASS based in-app purchase seaside server, contact me for assistance.