Contents

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.ProfileExplainer

This 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:34Z

This 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-allow

  • com.apple.security.application-groups

  • Those 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.token

This 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