iOS App Distribution and the Magical World of Code-Signing
October 27th, 2013
It’s a well-known fact that the only apps that Apple allows to run on iOS are the ones that it approves. Developers submit apps to the App Store, Apple gives it the shiny seal of approval, and only then can users run them. For some types of apps though, there’s a different method to getting an app to run. There are three ways you can run an app on an un-jailbroken device which doesn’t require them actually going through Apple. These are:
Development: When developers are working on their app, they can make changes and run them either in the iOS Simulator, or directly on any development device. Unlike some other platforms, however, a device does not have to be purpose-built as a ‘development device.’ Rather, any device can become a development device in just a few easy (or convoluted) steps.
Enterprise Distribution: Businesses who are part of the iOS Developer Enterprise Program can build in-house apps which are distributed to any iOS device inside the organisation. These do not have to go through the App Store.
Ad-Hoc Distribution: Most commonly used for beta testing, ad-hoc distribution allows developers to install builds on devices without going through Apple.
The Magic That Makes This Work
In cryptography, there are two main types of cryptography. (If there are others, I haven’t heard of them.)
Symmetric cryptography is probably what most people think of when they hear the words ‘Encrypt’ and ‘Decrypt’. With symmetric cryptography, you have a password that two parties know. Both parties use this key (password) to encrypt and decrypt the data. Anybody else with this key can encrypt and decrypt data exactly the same way.
Asymmetric cryptography is different. With asymmetric cryptography, there is not a single key, but a pair of keys. Data encrypted with one key can only be decrypted by the other matching key. If you encrypt data, but only hold one of the two keys, you then cannot decrypt the very data that you just encrypted.
Most commonly in applications of asymmetric cryptography, one key is designated the ‘public key’ and the other is designated the ‘private key’. As it’s name suggests, the public key is freely distributed to anyone who wants it. The private key is held near and dear by the owner, and nobody else can have it. This creates two situations:
- Any data encrypted with the public key can only be decrypted by the holder of the private key
- Any data that can be decrypted with the public key is guaranteed to have been encrypted by the holder of the private key.
By using these rules, one can encrypt some data with their private key and everybody else with the private key can trust that it came from the private key-holder. This is used in a technology known as Digital Signatures. By encrypting a file - or for efficiency’s sake, a hash of a file - with a private key, we can be certain that the file originated from the holder of the key.
All of this assumes that the encryption algorithm used hasn’t been broken and that two keypairs don’t collide.
So Back to iOS…
On iOS, all binaries must be signed in such a manner before they are run. For example, if I examine the code signature of Google Maps 1.0.0 using the codesign
utility, I get the following result:
Executable=/Users/path/to/Google Maps.app/Google Maps
Identifier=com.google.Maps
Format=bundle with Mach-O thin (armv7)
CodeDirectory v=20100 size=27304 flags=0x0(none) hashes=1357+5 location=embedded
Hash type=sha1 size=20
CDHash=397440c3e826288b6be5a3cc0cff85dc54e10d84
Signature size=3582
Authority=Apple iPhone OS Application Signing
Authority=Apple iPhone Certification Authority
Authority=Apple Root CA
Signed Time=12 Dec 2012 12:18:49 pm
Info.plist entries=36
Sealed Resources version=1 rules=5 files=713
Internal requirements count=2 size=668
The important lines here are the ones starting with Authority
. Using digital signatures and working from bottom to top, we can see that the Apple Root CA
has trusted the Apple iPhone Certification Authority
, which in turn has trusted Apple iPhone OS Application Signing
. Apple iPhone OS Application Signing
has signed this binary in particular, which signifies that this build came from the App Store. If I take a binary that I have signed, before it goes through Apple, I can see the following:
Executable=/Users/path/to/Backpack2.app/Backpack2
Identifier=com.professorfiddle.backpack
Format=bundle with Mach-O universal (armv7 armv7s)
CodeDirectory v=20100 size=1797 flags=0x0(none) hashes=81+5 location=embedded
Hash type=sha1 size=20
CDHash=08ef1886ca92aecbf7900f1e97e115455f1ba6ed
Signature size=4333
Authority=iPhone Distribution: [NAME REDACTED] ([TEAM ID REDACTED])
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=16 Jul 2013 5:51:49 pm
Info.plist entries=30
Sealed Resources version=1 rules=3 files=89
Internal requirements count=1 size=192
Here I’ve removed my name and my Apple Developer Team ID, but again we see a similar story. Apple Root CA
has trusted Apple Worldwide Developer Relations Certification Authority
, which has trusted me to sign app builds for iPhone Distribution
.
The delegation of trust is done using Certificates, which are basically a signed public key. Since my public key has been signed by Apple Worldwide Developer Relations Certification Authority
, this shows that the holder of Apple WWDR’s private key has signed my key. If you trust anyone who Apple WWDR trusts, this proves that trust relationship. The same thing happens between WWDR and Apple Root CA
. If you trust Apple Root CA, and you trust whoever they trust, then since Apple Root CA signed the Worldwide Developer Relations key, you should also trust Worldwide Developer Relations.
Thus, if you trust Apple, and Apple trusts Apple Worldwide Developer Relations, and Apple Worldwide Developer Relations trusts me, then you should probably trust me.
Provisioning Profile Hell
If it was that simple, and by simple I mean quite complicated but technically sound, then anybody with a developer certificate would be able to sign apps that anybody else could sideload onto their device. So, to go hand-in-hand with codesigning, is the document that Apple have termed a Provisioning Profile.
A Provisioning Profile is a document that lists a few things:
- The Application ID itself. This is the ‘bundle identifier’ of the application, which must match the App ID in the Apple Developer Portal.
- The Developer Certificates which are trusted to sign that application
- An expiry date
- A list of devices which are permitted to run the application
- A Digital Signature to certify that this Provisioning Profile was created by Apple
This creates the following rules. You can run a sideloaded application if:
- The application has a provisioning profile
- The application is signed by a developer certificate that the profile allows
- The device ID is included in the provisioning profile (not applicable for App Store or Enterprise distribution)
- The provisioning profile came from Apple
- The provisioning profile has not expired
Thus, in order to install a sideloaded application, you must pass all these rules.
Creating an Ad-Hoc Distribution Build
So in order to create an Ad-Hoc Distribution build, for beta testers etc., you must do the following:
If you do not have a valid Distribution certificate, create one in the Apple Developer Certificates, Identifiers & Profiles Portal. If you don’t feel comfortable with Keychain Access and Certificate Signing Requests, Xcode can do this step for you. In Xcode 5, this can be done through Xcode > Preferences > your Apple ID > your Developer Team > View Details… > click the ‘+’ under Signing Identities > iOS Development
If you don’t have one yet, create an App ID. This must be done through the Apple Developer Certificates, Identifiers & Profiles Portal. You probably shouldn’t use a Wildcard App ID, as you then can’t use many features such as Push Notifications and Game Center.
Register any devices you want to run the build in the Devices section of the Certificates, Identifiers & Profiles Portal. This is done by the device’s UDID, and developer accounts have a limit of 100 devices per year. Any devices you remove still count towards the limit, however when your developer subscription expires, you can reset the list when you renew your subscription.
Create a Provisioning Profile from the Certificates, Identifiers & Profiles Portal. You need to create an Ad-Hoc Distribution profile for the App ID you created in step 2. Select the certificates you want to be able to sign these builds, and the devices you want to run them on.
Download the profile and drag it onto the Xcode icon in your dock (or anywhere else reasonably) to install the profile in Xcode.
Make sure your project’s Bundle ID (CFBundleIdentifier) matches the App ID you created in step 2. This can be found in your Info.plist file (or SomeName-Info.plist), or by opening the Project in the editor from the Project Navigator (just click on the project itself).
Build and Archive. To do this, select ‘Archive’ from the ‘Product’ menu. Note that the Archive option is unavailable if the current launch target is the iOS Simulator. Set this to ‘iOS Device’ or any device you have connected in order to build for a device.
In the Archives section of the Organizer, select your archive and click on ‘Distribute…’. Select ‘Save for Enterprise or Ad-Hoc Deployment’ and select the Provisioning Profile you created in step 4.
Give the IPA to your friends, upload it to TestFlight or Hockey, etc.
Hopefully that’s enough information not just to create a distribution build, but to be able to troubleshoot the process when something inevitable goes wrong. Which it usually does somewhere along the line.