TN3125: Inside Code Signing: Provisioning Profiles
Learn how provisioning profiles enable third-party code to run on Apple platforms.
Overview
Code signing is a foundational technology on all Apple platforms. Many documents that discuss code signing focus on solving a specific problem. The Inside Code Signing technote series is different: It peeks behind the code signing curtain, to give you a better understanding of how this technology works. Read these technotes to make better code signing choices up front, to understand why Apple’s code signing tools work the way they do, to inform your investigation of any code signing issues you encounter, and because learning stuff is fun!
The other technotes in the Inside Code Signing series are:
Provisioning profile fundamentals
Apple platforms, except macOS, won’t run arbitrary third-party code. All execution of third-party code must be authorized by Apple. This authorization comes in the form of a provisioning profile, which ties together five criteria:
Who is allowed to sign code?
What apps are they allowed to sign?
Where can those apps run?
When can those apps run?
How can those apps be entitled?
You create provisioning profiles using the Apple Developer website, either directly using the website or indirectly using Xcode or the App Store Connect API.
When the Apple Developer website creates a profile for you, it cryptographically signs it. When you run an app on a device, the device checks this signature to determine if the profile is valid and, if so, checks that the app meets the criteria in the profile.
There is one interesting edge case with provisioning profiles: When you submit your app to the App Store, the App Store re-signs the app as part of the distribution process. Before doing that, it checks that the app is signed and provisioned correctly. That check means that each individual device doesn’t need to perform further security checks, so the final app doesn’t have a provisioning profile. However, this third-party code was still authorized by a profile, albeit during the App Store distribution process.
Unpack a profile
A provisioning profile is a property list wrapped within a Cryptographic Message Syntax (CMS) signature. To view the original property list, remove the CMS wrapper using the security tool:
% security cms -D -i Profile_Explainer_iOS_Dev.mobileprovision -o Profile_Explainer_iOS_Dev-payload.plist
% cat Profile_Explainer_iOS_Dev-payload.plist
…
<dict>
… lots of properties …
</dict>
</plist>For more details on CMS, see RFC 5652.
To illustrate this point, the traditional property list view of a profile is no longer the source of truth on modern systems. Rather, each profile contains a DER-Encoded-Profile property which holds a binary form of the profile that’s the new source of truth. For more on this switch, see The future is DER.
Still, the property list is easier to read and so the bulk of this technote focuses on that.
For more information about the tools used in these examples, read their man pages. If you’re unfamiliar with that process, see Reading UNIX Manual Pages.
The who
Every profile has a DeveloperCertificates property holding the certificates of each developer who can sign code covered by the profile. For example:
% plutil -extract DeveloperCertificates xml1 -o - Profile_Explainer_iOS_Dev-payload.plist
…
<array>
<data>
MIIFxDCCBKygAwIBAgIQfv+ckbvr2KtCgVI1ZPkPcjANBgkqhkiG9w0BAQsFADB1MUQw
…
PX0ovWucPvYp/HUcOnlbchPf/H63K8Jm5siTJlKsgGYEMX5wCJkh/+mlX1oAOH6CtOLy
kA==
</data>
… perhaps more …
</array>
</plist>To extract a specific certificate, add its index to the key path:
% plutil -extract DeveloperCertificates.0 raw -o - Profile_Explainer_iOS_Dev-payload.plist | base64 -D > cert0.cer
% certtool d cert0.cer
Serial Number : 7E FF 9C 91 BB EB D8 AB 42 81 52 35 64 F9 0F 72
Issuer Name :
Common Name : Apple Worldwide Developer Relations Certification Authority
…
Subject Name :
…
Common Name : Apple Development: …
…
Not Before : 09:15:23 Apr 21, 2021
Not After : 09:15:22 Apr 21, 2022
…To learn more about code-signing certificates, read TN3161: Inside Code Signing: Certificates.
The what
Most profiles apply to a single App ID, encoded in the Entitlements > application-identifier property:
% plutil -extract Entitlements.application-identifier raw -o - Profile_Explainer_iOS_Dev-payload.plist
SKMME9E2Y8.com.example.apple-samplecode.ProfileExplainerThis property holds an App ID, composed of an App ID prefix and a bundle ID. In this example SKMME9E2Y8 is the App ID prefix and com.example.apple-samplecode.ProfileExplainer is the bundle ID.
A profile might refer to a wildcard App ID:
% security cms -D -i Profile_Explainer_Wild_iOS_Dev.mobileprovision -o Profile_Explainer_Wild_iOS_Dev-payload.plist
% plutil -extract Entitlements.application-identifier raw -o - Profile_Explainer_Wild_iOS_Dev-payload.plist
SKMME9E2Y8.com.example.apple-samplecode.*This profile applies to any app whose App ID starts with SKMME9E2Y8.com.example.apple-samplecode.
The where
Most profiles apply to a specific list of devices. This is encoded in the ProvisionedDevices property:
% plutil -extract ProvisionedDevices xml1 -o - Profile_Explainer_iOS_Dev-payload.plist
…
<array>
<string>00008030-001544522E60802E</string>
… perhaps more …
</array>
</plist>App Store distribution profiles have no ProvisionedDevices property because you can’t run an App Store distribution signed app locally.
Developer ID and In-House (Enterprise) distribution profiles have the ProvisionsAllDevices property, indicating that they apply to all devices. For more details about Developer ID provisioning profiles on the Mac, see Entitlements on macOS.
The when
Every profile has an ExpirationDate property which limits how long the profile remains valid. For example:
% plutil -extract ExpirationDate raw -o - Profile_Explainer_iOS_Dev-payload.plist
2022-07-23T14:30:34ZThis validity period varies by profile type, but it’s typically not more than a year. The exception here is Developer ID profiles, which have an expiration date far in the future.
The how
Every profile has an Entitlements property which authorizes the app to use specific entitlements. For example:
% plutil -extract Entitlements xml1 -o - Profile_Explainer_iOS_Dev-payload.plist
…
<dict>
<key>application-identifier</key>
<string>SKMME9E2Y8.com.example.apple-samplecode.ProfileExplainer</string>
<key>com.apple.developer.team-identifier</key>
<string>SKMME9E2Y8</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>SKMME9E2Y8.*</string>
<string>com.apple.token</string>
</array>
</dict>
</plist>The entitlements in the profile act as an allowlist. This isn’t the same as the entitlements claimed by the app. To actually claim an entitlement, include the entitlement in the app’s code signature.
Every entitlement claimed by the app must be in the profile’s allowlist but the reverse isn’t true. It’s fine for the allowlist to include entitlements that the app doesn’t claim.
Some entitlements in the allowlist use wildcard syntax. In the above example, SKMME9E2Y8.* means that the app can claim any keychain access group with the SKMME9E2Y8. prefix. Wildcards don’t make sense in the app’s code signature.
To dump the entitlements claimed by the app, use codesign with the --entitlements argument:
% codesign --display --entitlements - --xml ProfileExplainer.app | plutil -convert xml1 -o - -
…
<dict>
<key>application-identifier</key>
<string>SKMME9E2Y8.com.example.apple-samplecode.ProfileExplainer</string>
<key>com.apple.developer.team-identifier</key>
<string>SKMME9E2Y8</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>SKMME9E2Y8.com.example.apple-samplecode.ProfileExplainer</string>
<string>SKMME9E2Y8.com.example.apple-samplecode.shared</string>
</array>
</dict>
</plist>Every entitlement claimed by this app is authorized by its profile, and thus iOS allows the app to run. Note that the keychain-access-groups value, SKMME9E2Y8.com.example.apple-samplecode.ProfileExplainer, starts with SKMME9E2Y8. and thus is allowed by the wildcard.
Entitlements on macOS
A macOS app can claim certain entitlements without them being authorized by a provisioning profile. These unrestricted entitlements include:
com.apple.security.get-task-allowcom.apple.security.application-groupsThose used to enable and configure the App Sandbox
Those used to configure the Hardened Runtime
In contrast, restricted entitlements must be authorized by a provisioning profile. This is an important security feature on macOS. For example, the fact that the keychain-access-groups entitlement must be authorized by a profile means that other developers can’t impersonate your app in order to steal its keychain items.
A Mac app that uses no restricted entitlements doesn’t need a provisioning profile. This is true even if the app is distributed on the App Store. The only exception to this rule is TestFlight, which always requires a profile.
macOS supports provisioning profiles for both App Store and Developer ID distribution. Some entitlements are not supported by Developer ID profiles. For the details, see Supported capabilities (macOS) in Developer Account Help. For general information about Developer ID signing, see Signing Mac Software with Developer ID
Profile location
In the early days of iOS development it was common to install a provisioning profile on the device as a whole (in the Settings app). That’s still possible, but current best practice is to embed the profile within the app itself:
macOS expects to find the profile at
MyApp.app/Contents/embedded.provisionprofile.Other Apple platforms expect to find the profile at
MyApp.app/embedded.mobileprovision.
Note that macOS also uses a different file name extension for provisioning profiles.
Apps that you download from the App Store don’t contain an embedded provisioning profile because the App Store checks that the app is signed and provisioned correctly as part of its distribution process.
Some macOS products, like daemons and command-line tools, ship as a standalone executable. A standalone executable can’t claim a restricted entitlement because there’s no place to embed the provisioning profile that authorizes that claim. If your standalone executable needs to do this, wrap it in an app-like structure. For an example of this, see Signing a daemon with a restricted entitlement.
The future is DER
Modern systems no longer treat the profile’s property list as the source of truth. Rather, they use the binary form of the profile stored in the profile’s DER-Encoded-Profile property:
% plutil -extract DER-Encoded-Profile raw -o - Profile_Explainer_iOS_Dev-payload.plist
MIINQQYJKoZIhvcNAQcCoIINMjCCDS4CAQExDzANBglghkgBZQMEAgEFADCCAvwGCSqG…This form of the profile is encoded as DER, a binary encoding of ASN.1 that’s common in cryptographic file formats. To extract it, first extract the property to a file:
% plutil -extract DER-Encoded-Profile raw Profile_Explainer_iOS_Dev-payload.plist | base64 -D > Profile_Explainer_iOS_Dev.der This is a whole new copy of the profile, so undo the CMS wrapper again:
% security cms -D -i Profile_Explainer_iOS_Dev.der -o Profile_Explainer_iOS_Dev-payload.der Finally, dump the DER-encoded payload itself:
% openssl asn1parse -in Profile_Explainer_iOS_Dev-payload.der -inform der -i | cut -c 30-
SET
…
SEQUENCE
UTF8STRING :ExpirationDate
UTCTIME :220723143034Z
…
SEQUENCE
UTF8STRING :ProvisionedDevices
SEQUENCE
UTF8STRING :00008030-001544522E60802E
SEQUENCE
UTF8STRING :DeveloperCertificates
SEQUENCE
OCTET STRING [HEX DUMP]:1A6836292903FEFEB3A1303507436AD808BEDE7100E360F8F632579AC7EACA96
SEQUENCE
UTF8STRING :Entitlements
appl [ 16 ]
INTEGER :01
cont [ 16 ]
SEQUENCE
UTF8STRING :application-identifier
UTF8STRING :SKMME9E2Y8.com.example.apple-samplecode.ProfileExplainer
SEQUENCE
UTF8STRING :com.apple.developer.team-identifier
UTF8STRING :SKMME9E2Y8
SEQUENCE
UTF8STRING :get-task-allow
BOOLEAN :255
SEQUENCE
UTF8STRING :keychain-access-groups
SEQUENCE
UTF8STRING :SKMME9E2Y8.*
UTF8STRING :com.apple.tokenThis output contains mostly the same information as the property list, just encoded in DER form.
The one exception is the DeveloperCertificates property. This doesn’t contain a full copy of each certificate, but rather a SHA-256 checksum of the certificate. Assuming the certificate extracted from the property list earlier as cert0.cer, run shasum to confirm that checksum:
% shasum -a 256 cert0.cer
1a6836292903fefeb3a1303507436ad808bede7100e360f8f632579ac7eaca96 …This DER-encoded profile is required starting with iOS 15, iPadOS 15, tvOS 15, and watchOS 8. For more on that change, see Using the latest code signature format.
Revision History
2024-02-06 Added a link to TN3126: Inside Code Signing: Hashes. Made other minor editorial changes.
2022-05-24 Made minor editorial changes.
2022-05-03 Republished as TN3125. Added The future is DER section. Updated the examples to use
plutil. Also made significant editorial changes.2021-07-26 First published as ”What exactly is a provisioning profile?” on Apple Developer Forums.
See Also
Latest
TN3205: Low-latency communication with RDMA over ThunderboltTN3206: Updating Apple Pay certificatesTN3179: Understanding local network privacyTN3190: USB audio device design considerationsTN3194: Handling account deletions and revoking tokens for Sign in with AppleTN3193: Managing the on-device foundation model’s context windowTN3115: Bluetooth State Restoration app relaunch rulesTN3192: Migrating your iPad app from the deprecated UIRequiresFullScreen keyTN3151: Choosing the right networking APITN3111: iOS Wi-Fi API overviewTN3191: IMAP extensions supported by Mail for iOS, iPadOS, and visionOSTN3134: Network Extension provider deploymentTN3189: Managing Mail background traffic loadTN3187: Migrating to the UIKit scene-based life cycleTN3188: Troubleshooting In-App Purchases availability in the App Store