How does iOS code signing work: certificates, App IDs, provisioning profiles, signing identities, export methods and signing on CI?

25.1.1 · L3 · Interview

Predict first

A binary is ad hoc signed and validated, then one byte is appended and it is validated again. What happens?

// === main.swift ===
print("hello")
  • It is adhoc-signed and valid, then fails strict validation after the change
  • It stays valid after any change
  • It cannot be signed without a certificate

Predict first

A CI job creates a temporary keychain, sets a timeout, unlocks it and deletes it. Do the standard security commands do this?

// === main.swift ===
print("hello")
  • Yes: created, timeout set, unlocked, deleted
  • No, keychains can only be made in Keychain Access
  • Only the delete command exists

Mental model. A signed app is a sealed parcel with a courier’s stamp. The profile is the delivery order from the post office saying which couriers may deliver which parcels to which addresses; break the seal and the parcel is refused.

Four things work together. A signing certificate (Apple Development or Apple Distribution) proves who you are; its private key lives in a keychain on the machine that made the request, so a certificate without its key cannot sign, and losing the key means revoking and creating a new certificate. An App ID is your bundle identifier plus the capabilities enabled for it. A provisioning profile is a file signed by Apple that binds them: it lists the App ID, the certificates allowed to sign, the entitlements the app may claim and, for development and ad hoc profiles, the device identifiers allowed to run it. The code signature is a seal over the executable and every resource in the bundle; it records the signing identity and the embedded entitlements, and any change to the signed contents invalidates it (the check below signs a binary, then appends one byte and validation fails). The device verifies the chain to Apple’s root, checks the profile matches and the entitlements are a subset of what the profile grants, and only then launches the app. The profile types map to export methods: development (registered devices), ad hoc (registered devices, no debugger), App Store (what you upload) and enterprise (an organisation’s own distribution).

Xcode’s automatic signing creates and refreshes certificates, App IDs and profiles for you, which is right for local work. Manual signing (or automatic signing driven by an App Store Connect API key with -allowProvisioningUpdates) is what pipelines use, so builds are reproducible and do not depend on a person’s account. On CI never commit certificates or keys. Store them encrypted (a secrets manager, or fastlane match with an encrypted private repository), and in each job create a temporary keychain, unlock it, set a timeout, import the certificate and key, allow codesign to use it without a prompt, build, and delete the keychain at the end, as in the commands checked below. Expiry matters: certificates and profiles last a limited time (about a year), so track them, because an expired distribution certificate blocks new builds (apps already released are unaffected). Inspect a profile with security cms -D -i embedded.mobileprovision and a signature with codesign -dvv. When an error says “no matching provisioning profile”, check the bundle ID, the certificate, the device list and the entitlements, in that order.

The signature seals the contents. Changing the file breaks the seal, which is why you must sign last.

The command-line tool covers the whole lifecycle, so a pipeline can set up and tear down its own keychain.

Common traps

  • Committing a certificate and key to the repository. Anyone with them can sign as you; keep them encrypted and use a temporary keychain on CI.
  • Letting a provisioning profile or certificate expire unnoticed. The next release build fails on the day you need it; track expiry dates.

They might follow up with

  • What is inside a provisioning profile? The App ID, the allowed certificates, the entitlements and, for development and ad hoc profiles, the device list.
  • Why does CI use a temporary keychain? So the job has a known, unlocked place for the identity, and nothing is left behind on a shared runner.

Related questions

  • 25.1.2 What is the difference between a capability, an entitlement and an Info.plist key, and what breaks when they…
  • 25.2.1 What are the main ways to run iOS CI/CD (Xcode Cloud, GitHub Actions, Fastlane, others), and what do they all…
  • 6.1.3 What are entitlements, and what happens when they do not match the provisioning profile?
  • 16.6.1 A CI pipeline takes hours on a large modular app. How do you analyse and cut it: affected builds, caching…

What is the difference between a capability, an entitlement and an… →