Chapters

Hide chapters

iOS App Distribution & Best Practices

First Edition · iOS 14.4 · Swift 5.3 · Xcode 12.4

Section I: iOS App Distribution & Best Practices

Section 1: 17 chapters
Show chapters Hide chapters

Section II: Appendices

Section 2: 2 chapters
Show chapters Hide chapters

4. Code Signing & Provisioning
Written by Pietro Rea & Keegan Rush

The previous two chapters took advantage of Xcode’s automatic code signing and provisioning feature. In those chapters, the goal was to submit your app for review as soon as possible, so the automatic setting made sense. This feature does a lot of heavy lifting under the hood. You didn’t have to pay much attention to certificates, entitlements, provisioning profiles or code signing.

Code signing and provisioning are among the most difficult parts of app distribution — and it’s where most developers run into issues. Even the most experienced developers routinely fiddle around with settings until they get something working, without ever understanding how the system should work.

In this chapter, you’ll learn how to do what Xcode did on your behalf so far. Automatic code signing is great, but more advanced setups require manual configuration. If something goes wrong, you’ll also want to know how the underlying system works so you can troubleshoot effectively.

A historical detour

Before diving into the underlying system, you’ll take a brief, historical detour to the 1970s. Apple’s platforms are all based on a version of Unix, the operating system famously developed at Bell Labs in the 1970s. Surprisingly, you can trace why code signing is a pain back to Unix’s original assumptions!

In Unix, a program has the same access and privileges as the user running it. This made sense back in the 1970s because you had to code and load your own programs using punched cards. But in modern times, this is not a good assumption. Nowadays, the programs you run are typically third-party apps that you didn’t write yourself.

For security reasons, your apps should not have the same access as you do. Third-party apps shouldn’t have free rein over your files, data and hardware. A malicious or compromised app that can do everything you can could wreak havoc on your system.

Consider how you would handle this issue if you were Apple. What kind of system would you build to protect users from the consequences of this decades-old assumption? Are you thinking of a system that separates a user’s privileges from those of a third-party app? That kind of system would enforce a strict separation between apps and the operating system’s resources, requiring permission for everything.

If that’s what you were thinking, congratulations! That’s what Apple ended up doing. The resulting system is called App Sandbox. On iOS, iPadOS, watchOS and tvOS, apps have been sandboxed from day one. Apple later added sandboxing to macOS. In 2010, Apple announced that sandboxing would be required for all apps distributed on the macOS App Store.

Introducing App Sandbox

The term “sandbox” helps you understand the purpose of the system. You can think of each app being in a restricted, safe space of its own. By default, a sandboxed app only has access to an app-specific section of the file system, plus the data the app itself creates and saves.

Outside the sandbox, the host device has resources that the app might want to use, like the microphone or the camera. However, for security reasons, the operating system closely guards these restricted resources. To use them, the app needs to validate its identity and request permission from the OS and, sometimes, the user.

Broadly speaking, the resources that require permission fall into five categories:

  1. Hardware: Camera, microphone, sensors, etc.
  2. Network access: Inbound and outbound traffic.
  3. Data from other apps: Contacts, calendars, email, etc.
  4. User files: Files from the file system or the Files app.
  5. Special functionality: Push notifications, HomeKit access, etc.

Note: This is where terminology gets confusing. In technical documentation, you’ll rarely see the word “permission”, but you’ll see “entitlements”, “capabilities” and “resources”. In a nutshell, developers request entitlements to add capabilities to their app so it can access system resources.

The diagram below represents how the App Sandbox works. It’s not based on the real architecture or implementation of the actual system, but it can help you identify and understand all the big pieces.

At a high level, there are resources outside the sandbox, components inside the sandbox and a runtime policy system in the middle that grants or blocks access based on the app’s entitlements. The following sections describe each in detail.

Outside the sandbox

The left-most column of boxes in the previous diagram represents system resources that require permission from the operating system and the user.

Most resources are things you can access, like a user’s contacts. Other resources are restricted roles you can take on in Apple’s ecosystem, like the ability to send push notifications or the ability to integrate with the Health app.

Runtime policy system

At runtime, a sandboxed app might attempt a restricted operation, like getting location updates while the app isn’t running. The policy system intercepts this operation and, based on a number of checks, allows or disallows the action.

For example, in the case of background location updates, you need three things:

  • The user’s explicit consent to start getting location data.
  • A description of why you’re asking for location data in the app’s Info.plist.
  • The Background Modes location entitlement.

The runtime policy system checks that your app met all these requirements to determine if it can proceed.

Apple, understandably, doesn’t publish much documentation about its policy system on iOS and newer platforms. However, you can piece some things together by reading about how Apple rolled out App Sandbox to existing macOS developers.

For instance, spctl is the command-line tool — part of BSD Unix — that manages the security assessment policy subsystem, including Gatekeeper on the Mac. For more information, read App Sandbox’s Design Guide: https://apple.co/2JMJF2u.

Inside App Sandbox

Finally, the right-hand rectangle represents App Sandbox. As mentioned before, each sandbox container has its own file system, which the sandboxed app can access without restriction.

There’s a “locked area” in the middle of the sandbox that contains the app binary. It’s “locked” because Xcode digitally signed it using codesign. After code-signing something, it’s impossible to change anything about it without breaking the code seal, which is easy to check.

The scroll icon next to the app icon represents the provisioning profile, a file embedded in the app’s binary. You can think of it as a public declaration that details who the app is, where it can run and what it wants to do.

Note: There’s more to code signing and codesign. This chapter aims to give you an intuitive understanding of what it does and why, but if you want to learn about the nitty-gritty implementation details, refer to Apple’s Code Signing Guide: https://apple.co/36QwNBm.

Working backward

So far, you’ve read about App Sandbox and why Apple introduced it. This context is important! If you don’t have the background context, it’s easy to think of code signing and provisioning as a list of unrelated chores.

But once you have a grasp of the system behind it, you begin to think in terms of providing the sandbox with a “plugin” that it can understand and interact with.

To accomplish this, you might find it helpful to work backwards from an end goal. To properly interact with App Sandbox, your goal is to furnish a code-signed provisioning profile, represented below.

As previously discussed, App Sandbox makes several checks before letting an app complete a restricted operation. The operating system asks questions and a code-signed provisioning profile answers them. Here are the basic questions the operating system asks:

  • Who are you?: A provisioning profile includes an app’s App ID, which in turn includes a team ID. Chapter 3, “Submitting Your First App for Review”, shows you how to create an App ID in App Store Connect.

  • What would you like to do?: A provisioning profile also includes the list of entitlements for an app. This is a public declaration of your intentions. When your app starts doing something restricted, App Sandbox checks the app’s requests against the intentions in the provisioning profile.

  • Can I trust you?: You answered who you are and what you intend to do, but these answers are just written in plain files. A malicious actor could overwrite them. How can Apple know that you are who you say you are?

    A provisioning profile also includes a developer identity in the form of a certificate. The certificate includes the public key, which Apple can use to verify that the digital signature was created with its corresponding private key.

  • Where can you run?: Different rules govern different distribution types. For example, in ad hoc distribution — that is, distribution outside the App Store — you may only install your app on a maximum of 100 devices. If you’re using ad hoc distribution, your provisioning profile will also include the list of device IDs that may use the app.

Note: Thinking of a code-signed provisioning profile as the “goal” is useful in practice. But in reality, you copy your provisioning profile’s contents to the digital signature. The real source of truth is the digital signature, not the provisioning profile.

Verifying your identity

One of the questions your code-signed provisioning profile needs to answer for the policy system is, “can I trust you?”. To unambiguously identify the app and its developer, you need to embed a certificate in the provisioning profile.

Note: Apple did not invent certificates. The certificates you use to identify yourself are based on the X.509 cryptographic standard. X.509 is also the basis for TLS/SSL and HTTPS.

https://en.wikipedia.org/wiki/X.509

First, inspect the certificates you already have. There are two ways you can do this. The first is to use a built-in app called Keychain Access, which stores passwords, secrets and other sensitive data for the OS. It also manages X.509 certificates.

Open Keychain Access. Under Default Keychains in the left-hand sidebar, click login, then click My Certificates. Next, search Apple in the search bar to filter your certificates. Click the disclosure indicators to see the private keys.

There are two certificates from the previous two chapters: a development certificate and a distribution certificate.

The second way to find your current certificates is by using the security command-line tool. Open Terminal and type the following command:

security find-identity -v -p codesigning

The same two certificates you saw in Keychain Access now print to the console. If you’re part of multiple Apple Developer teams, you might see more.

1) 51CC49FC309619650FEC581586C7BB21B7467F86 "Apple Development: pasi@sweetpeamobile.com (PCQP3A43PK)"
2) C2B75ED2E049C4E8B13E2823A493132E0AE41756 "Apple Distribution: Sweetpea Mobile LLC (2DLBN8DKBU)"
     2 valid identities found

Viewing your certificates

Now, go back to Keychain Access and double-click one of your certificates to open it.

The modal reveals important concepts related to certificates. You’ll go over each in detail.

  1. A certificate authority issues and signs certificates: Something called “Apple Worldwide Developer Relations Certificate Authority” issued the certificate you’re looking at. A certificate authority is part of the X.509 standard. Its role is to tie an entity to a public key. In this case, Apple’s WWDR certificate authority ties your Apple Developer Program team ID to your public key.

  2. X.509 certificates rely on public-key cryptography: In public-key cryptography, a public key and a private key work together. As the name implies, you can give your public key to others while keeping your private key secret.

    In the context of app distribution, Xcode digitally signs your app using your private key. You then give your public key to Apple via the certificate and Apple uses it to verify that the digital signature came from you.

  3. Code signing requires a valid certificate and its private key: Certificates can become invalid for several reasons. Each certificate has a natural expiration date, usually one year from the time you got it. Separately, either you or Apple can revoke a certificate if it becomes compromised. To create a digital signature in Xcode, the certificate’s private key needs to be installed in Keychain Access.

Now that you have some background information on certificates and public-key cryptography, select and delete your old certificates in Keychain Access. Keychain Access will double-check that you know what you’re doing.

Click Delete to confirm.

Next, you’ll create new certificates from scratch.

Creating new certificates

Log into Apple Developer Portal by opening https://developer.apple.com/ in your browser. Open Certificates, Identifiers & Profiles.

Select Certificates in the sidebar. Why are the certificates you just deleted here? The developer portal is unaware of Keychain Access on your Mac. It doesn’t know you deleted your local copy.

Because you also deleted the private keys in the previous step, you can’t use these certificates, even if you download them again. Click each certificate and revoke it.

Now, click the + button next to the Certificates heading. Select Apple Distribution on the next screen.

You need a distribution certificate both to distribute apps in the App Store and for ad hoc distribution, which is covered in Chapter 5, “Internal Distribution”. In contrast, you need a development certificate to install a build for your development device. You won’t create a development certificate in this chapter but the process is essentially the same.

Click Continue to move forward. The next screen asks you to upload a Certificate Signing Request (CSR). This is also part of the X.509 standard and the official way to ask Apple to sign your certificate.

Asking Apple to sign your certificate

Open Keychain Access. From the menu bar, select Keychain Access ▸ Certificate Assistant ▸ Request a Certificate from a Certificate Authority.

In the resulting modal, enter your email address in the User Email Address field. Next to Common Name, type Emitron Distribution Private Key. Select Saved to disk.

Click Continue. Save the certificate signing request somewhere easy to find in a file named CertificateSigningRequest.certSigningRequest.

Note: In the time it took to create a .certSigningRequest file, Keychain Access already created your public key, private key and certificate. The CSR has all the information in your certificate, as well as your public key.

You just need Apple’s cryptographic stamp of approval. That’s why the next step is to upload the CSR to the developer portal.

Uploading the CSR

Return to Apple Developer Portal. Under the Upload a Certificate Signing Request section, click Choose File.

Select CertificateSigningRequest.certSigningRequest from your computer. Click Continue to upload your certificate signing request.

Next, click Download on the resulting screen to finally download the signed certificate.

Your browser downloads a file named distribution.cert. Double-click it to install it in Keychain Access.

Open Keychain Access. Filter by Apple certificates to see that your new certificate is there. The certificate expires exactly one year from today.

The entry in Keychain Access might not look like much, but it unambiguously identifies you as a developer on your Apple Developer Program team using state-of-the-art cryptographic standards.

Since Apple signed your certificate after you uploaded the certificate signing request, the fact that you have that private key in your possession means there’s a direct chain of trust starting from Apple (the certificate authority) to your development team (mentioned in your certificate) to the Mac sitting in front of you.

Once you see the certificate in Keychain Access, you can delete the .certSigningRequest file. You no longer need it.

Adding entitlements

To create a code-signed provisioning profile — the end goal! — the next piece of the puzzle is a list of entitlements. To recap, entitlements answer the question “what would you like to do?” on behalf of your app.

Viewing entitlements

Every sandboxed app uses entitlements, even the ones that Apple makes. Before moving forward, it’ll be helpful for you to dig up some entitlements on your Mac to inspect their format.

Open Terminal. Enter the following command to print the entitlements for a built-in app, like Keynote:

codesign -d --entitlements - /Applications/Keynote.app

codesign is the command-line tool for digitally signing binaries that’s built into every Mac. Xcode uses codesign behind the scenes when you archive a build. The command above will give you a result like this:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.apple.security.automation.apple-events</key>
  <true/>
  <key>com.apple.developer.icloud-container-identifiers</key>
  <array>
    <string>com.apple.Keynote</string>
  </array>
...
</dict>
</plist>%

That’s the beginning of what codesign prints to the console. At its core, an entitlement is a key-value pair.

You’re looking at the text representation of the entitlements .plist, which stores reverse-DNS keys that represent different entitlements. The values can be Booleans (true/false) or other data types, like strings or arrays.

In the .plist above, you can see that Keynote has the Mac-only entitlement for sending Apple Events to other apps, com.apple.security.automation.apple-events. It also has the entitlement to save documents to iCloud, com.apple.developer.icloud-container-identifiers.

Now that you’ve seen how other apps use entitlements, it’s time to add your own.

Creating your own entitlement

Like certificates, entitlements also originate in Apple Developer Portal. Open Apple Developer Portal. Click Certificates, Identifiers & Profiles followed by Identifiers. Then click the Raywenderlich Emitron App ID you created in Chapter 3, “Submitting Your First App for Review”.

Technically, entitlements are part of an app’s App ID. Under Capabilities, select the Associated Domains checkbox. Click Save. Associated Domains let you link an app with a website. The real raywenderlich.com iOS app doesn’t use this, but you’ll add it here for the sake of practice.

Click Confirm to save the change. All provisioning profiles that use this App ID become invalid when you change the ID’s entitlements.

Note: You’re not technically “adding” an entitlement. All entitlements have a default value at runtime, which is usually set to off. Adding an entitlement in Xcode or in Apple Developer Portal merely overrides the default.

Not every entitlement has a checkbox in App Store Connect or is represented in Xcode’s Capabilities tab. Here’s a more comprehensive list of entitlements: https://apple.co/2VT5wrx.

One of the most popular entitlements is Push Notifications. Adding push notifications is also the point where many developers realize they don’t understand code signing and provisioning as well as they need to.

It takes more than adding an entitlement to send push notifications from a back-end server and display them in your app. If push notifications brought you to this chapter, or if you’re generally interested in them, check out Push Notifications by Tutorials by Scott Grosch and the raywenderlich.com team: http://bit.ly/3d47yyL.

Generating a provisioning profile

At this point, you have a distribution certificate and an updated App ID with a new entitlement. The next step is to tie it all together in a provisioning profile. Like certificates, provisioning profiles are just files. On your Mac, Xcode stores all provisioning files in ~/Library/MobileDevices/Provisioning Profiles.

Open Terminal. Execute the following command to look at your provisioning profiles:

open ~/Library/MobileDevice/Provisioning\ Profiles

Terminal opens a Finder window that lists all the provisioning profiles Xcode has ever used.

Double-clicking on any row would open Xcode. Instead, select the first row. Press Space to preview with Quick Look.

Note: Quick Look is a feature in Finder that opens up a preview window in Finder. This saves you from having to open the file’s designated app. Different file formats do different things with Quick Look previews.

The Quick Look preview for .mobileprovision gives you a graphical representation of the provisioning profile. This is often useful for troubleshooting.

A provisioning profile is the combination of a handful of things you’ve seen before:

  1. App ID: The App ID uniquely identifies an app on Apple’s platforms and ties the app to your development team.
  2. Entitlements: Technically part of the App ID, but important enough to get their own section in the preview. Notice that there are entitlements that you didn’t have to set manually, like keychain-access-groups and beta-reports-active.
  3. Certificate: The certificate unambiguously identifies you as the developer and certifies the chain of trust going from Apple to you. Remember that certificates are revokable and have an expiration date. If the certificate on a provisioning profile is invalid or expires, the entire provisioning profile is also invalid.

The list of entitlements on the provisioning profile above doesn’t include the Associated Domains entitlement you added earlier. That’s because you’re looking at an old provisioning profile. It’s time to generate a new one.

Generating a new provisioning profile

Open Apple Developer Portal. Click Certificates, Identifiers & Profiles, then click Profiles.

Click the + button next to Profiles to add a new profile. On the next screen, under Distribution, select App Store to register a new distribution provisioning profile.

On the next screen, select the App ID you created in Chapter 3, “Submitting Your First App for Review”. Click Continue.

Then, select the distribution certificate you created earlier in this chapter. Click Continue.

As a final step, App Store Connect asks you to name your provisioning profile. Enter Emitron Distribution Provisioning Profile. Click Generate.

On the next screen, click Download. Double-click the resulting .mobileprovision file to open it in Xcode. Xcode will acknowledge the file, then move it to /Library/MobileDevice/Provisioning Profiles.

Open this folder in Finder and inspect the most recently added .mobileprovision with Quick Look to verify that this new provisioning profile does have the Associated Domains entitlement you added earlier.

Code signing and distribution

You now have a valid distribution provisioning profile in your possession, along with its associated private key. You’ll use these to generate another build, code-sign it and upload it to App Store Connect.

Open the starter project in Xcode. Navigate to the raywenderlich target.

Click the Signing & Capabilities tab, then select the Release tab. Under Signing, deselect Automatically manage signing.

Note: The Debug build configuration, which you typically use for development builds, still uses automatic code signing. You can change Debug and Release configurations separately.

You don’t need to know the ins and outs of build configurations to understand code signing and provisioning. Chapter 9, “Build Customizations”, covers this topic in detail.

Don’t worry that Xcode complains about a missing provisioning profile. You’ll fix that next.

Next to Provisioning Profile, click the Drop-down button. Select Import Profile…. Navigate to and select the provisioning profile you downloaded in the previous section.

If you missed a step and see an error, double-check your configurations by looking at build settings.

Under the targets list, click raywenderlich. Click the Build Settings tab, then scroll down to the Signing section.

There are four build settings you need to keep in mind if you see any errors with code signing or provisioning: “Code Signing Identity”, “Code Signing Style”, “Development Team”, and “Provisioning Profile”.

Code Signing Identity

Your app’s code-signing identity sets the certificate that uniquely identifies you. Even if you choose to manage signing manually, this build setting has a manual option and an automatic option. The values for the automatic option are Apple Development, iOS Developer, Apple Distribution and iOS Distribution.

If you use one of the automatic options, Xcode tries to deduce the certificate from the provisioning profile selected below. It can do this because provisioning profiles include certificates. In this case, leave this value set to iOS Distribution.

Code Signing Style

This is the same setting you changed earlier in the Signing & Capabilities tab. The possible values are Automatic and Manual. Here, you’re managing signing automatically for Debug builds and manually for Release builds.

Development Team

When you log into Xcode with your Apple ID, your Apple Development Program teams become options you can select. The team you select for this build setting must match the team in your chosen certificate and provisioning profile.

Provisioning Profile

The provisioning profile build setting determines which provisioning profile you’ll embed in the build. You can also change this setting in the Signing & Capabilities tab you saw earlier.

Uploading a manually signed build

Now that your settings look correct, it’s time to generate another build to upload to App Store Connect using your manually created certificate and provisioning profile.

With the raywenderlich target selected, click the General tab and bump the Build number to 3. If you’ve uploaded more builds in the meantime, your build number could be higher.

Set Xcode’s build destination to Any iOS Device (arm64). In the menu bar, click Product ▸ Archive. When Xcode finishes the build process, an Organizer window will open automatically.

Click Distribute App. Go through the Organizer windows using the default values. Since you chose manual signing, Xcode now prompts you to select a distribution certificate and a provisioning profile.

Select Apple Distribution as the distribution certificate and Emitron Distribution Provisioning Profile as the provisioning profile, as shown below.

Click Next to continue. Xcode then uses the certificate and provisioning profile you selected a moment ago to digitally sign the binary using codesign.

Then, click Upload to upload the generated IPA file to App Store Connect.

If all goes well, Xcode signs and uploads the binary and you’ll see a success screen.

Congratulations! You’ve successfully uploaded a build where you managed code signing and provisioning manually. This is one of the thorniest things you can do as an Apple developer — and you came out the other end with flying colors.

Key points

  • Unix-like systems assume that third-party apps should have the same access and privileges as the user running them. Apple designed App Sandbox as a way to fix this assumption.
  • Sandboxed apps need to request permission from the operating system to access hardware facilities, the network, data from other apps, user files and special functionality like push notifications.
  • App Sandbox and runtime policy infrastructure make several checks when an app tries a protected operation. You must furnish a code-signed provisioning profile to answer them.
  • A provisioning profile comprises several parts: an App ID, entitlements, a certificate and (optionally) a list of device IDs.
  • Certificates are based on public-key cryptography and follow the X.509 standard. Certificates contain information about your Apple Developer Program team and are associated with a public key and a private key.
  • Entitlements are key-value pairs that describe what an app intends to do at runtime. They’re stored in an app’s App ID and code-signed with the rest of the binary.
  • When it comes to code signing and provisioning, the relevant build settings are Code Signing Identity, Code Signing Style, Development Team and Provisioning Profile.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.