DOMAIN:IOS_DEVELOPMENT:CODE_SIGNING¶
OWNER: martijn
ALSO_USED_BY: valentin, leon (release), alex/tjitte (CI/CD)
UPDATED: 2026-03-24
SOURCE: Apple Developer Documentation — Code Signing, Provisioning
SIGNING:DEVELOPER_PROGRAM_TYPES¶
| Program | Fee | Distribution | Use Case |
|---|---|---|---|
| Apple Developer Program (Individual) | $99/yr | App Store, TestFlight, Ad Hoc | Solo developer, freelancer |
| Apple Developer Program (Organization) | $99/yr | App Store, TestFlight, Ad Hoc, Custom Apps, Unlisted | Business entity (BV, GmbH, Ltd) |
| Apple Developer Enterprise Program | $299/yr | In-House only | Internal apps for 100+ employee orgs |
| Free (Personal Team) | $0 | Xcode direct install only | Development/learning only |
RULE: free tier = 7-day signing, 3 app limit, no distribution, no push notifications, no CloudKit
RULE: Organization account requires DUNS number + Apple enrollment review
RULE: Enterprise requires DUNS + 100+ employees + Apple manual review (stricter)
SIGNING:CERTIFICATE_TYPES¶
| Certificate | Purpose | Created In | Max Active | Validity |
|---|---|---|---|---|
| Apple Development | Sign app for development/debug | Xcode or Developer Portal | Unlimited | 1 year |
| Apple Distribution | Sign app for App Store/TestFlight | Xcode or Developer Portal | 3 | 1 year |
| Apple Push Notification (APNs) | Push notifications | Developer Portal | 2 (sandbox + production) | 1 year |
| In-House Distribution | Enterprise distribution only | Developer Portal | 3 | 3 years |
| Pass Type ID | Apple Wallet passes | Developer Portal | per pass type | 1 year |
Certificate Chain¶
Apple Root CA
└── Apple Worldwide Developer Relations CA
├── Apple Development: {Team Name} ({Team ID})
├── Apple Distribution: {Team Name} ({Team ID})
└── Apple Push Services: {Bundle ID}
RULE: certificates are team-wide, not per-app
RULE: IF certificate expires THEN new builds cannot be signed, but existing installed apps continue working
RULE: IF certificate is REVOKED THEN enterprise apps stop working immediately
Creating Certificates¶
TOOL: Xcode → Settings → Accounts → Manage Certificates → + (recommended)
TOOL: manual: create CSR in Keychain Access → upload to Developer Portal → download .cer
RUN: security find-identity -v -p codesigning — list all valid signing identities in keychain
ANTI_PATTERN: creating certificates on different machines without exporting
FIX: export .p12 from Keychain Access (cert + private key) and share securely with team
FIX: better: use Fastlane match (see CI/CD section below)
SIGNING:PROVISIONING_PROFILES¶
| Profile Type | Signs With | Devices | Purpose |
|---|---|---|---|
| iOS Development | Development cert | Registered devices | Run from Xcode on test devices |
| Ad Hoc | Distribution cert | Up to 100 registered | Test distribution builds |
| App Store | Distribution cert | ANY (Apple validates) | App Store + TestFlight |
| Enterprise (In-House) | In-House cert | ANY in organization | Internal distribution |
Profile Contents¶
A provisioning profile bundles:
1. App ID (bundle identifier, explicit or wildcard)
2. Certificate(s) (which signing identities can use this profile)
3. Device UDIDs (development and ad hoc only)
4. Entitlements (capabilities the app is allowed to use)
5. Expiration date (1 year, or 3 years for enterprise cert-based)
RULE: profiles expire annually — set calendar reminder 30 days before
RULE: expired profile = cannot install, cannot build for that distribution type
RULE: App Store profiles do NOT include device UDIDs — Apple validates at install time
Managing Profiles¶
TOOL: Xcode → Signing & Capabilities → Automatic signing (recommended for development)
TOOL: Developer Portal → Profiles → create/download/renew
TOOL: fastlane sigh (download/create profiles programmatically)
RUN: security cms -D -i /path/to/profile.mobileprovision — inspect profile contents
RUN: open /path/to/profile.mobileprovision — view in Finder
RUN: find ~/Library/MobileDevice/Provisioning\ Profiles/ — all installed profiles
SIGNING:ENTITLEMENTS_AND_CAPABILITIES¶
Entitlements declare what OS features an app can use. Configured in:
1. App ID in Developer Portal (registers capability)
2. Xcode → Target → Signing & Capabilities (configures entitlement)
3. .entitlements file in project (embedded in binary)
Common Entitlements¶
| Entitlement | Key | Requires |
|---|---|---|
| Push Notifications | aps-environment |
APNs cert, App ID config |
| iCloud | com.apple.developer.icloud-container-identifiers |
iCloud container |
| App Groups | com.apple.security.application-groups |
Group identifier |
| Associated Domains | com.apple.developer.associated-domains |
AASA file on server |
| HealthKit | com.apple.developer.healthkit |
Privacy description |
| Sign in with Apple | com.apple.developer.applesignin |
Service ID config |
| Apple Pay | com.apple.developer.in-app-payments |
Merchant ID |
| Background Modes | UIBackgroundModes |
Info.plist declaration |
| Keychain Sharing | keychain-access-groups |
Group identifier |
| Network Extensions | com.apple.developer.networking.networkextension |
Apple approval |
RULE: entitlements in app binary MUST match entitlements in provisioning profile
RULE: IF mismatch THEN build succeeds but install/launch fails with cryptic error
RULE: some entitlements require explicit Apple approval (network extensions, CarPlay, DriverKit)
ANTI_PATTERN: adding entitlement in Xcode but forgetting to enable in Developer Portal
FIX: Xcode automatic signing handles this — IF using manual signing, update both places
ANTI_PATTERN: requesting entitlements the app doesn't actually use
FIX: Apple reviews entitlement usage — unused entitlements cause rejection
SIGNING:XCODE_MANAGED_VS_MANUAL¶
Automatic Signing (Recommended for Development)¶
IF: single developer or small team
THEN: use Xcode automatic signing
- Xcode creates/manages development certificates
- Xcode creates/manages provisioning profiles
- Xcode registers new devices automatically
- Less control but fewer signing errors
Manual Signing (Required for CI/CD)¶
IF: CI/CD pipeline or team of >3 developers
THEN: use manual signing with Fastlane match
- Full control over certificates and profiles
- Reproducible across machines
- Required for most CI/CD setups
SIGNING:CI_CD_SIGNING¶
Fastlane Match (Recommended)¶
STANDARD: Fastlane match documentation
CONCEPT: store encrypted certificates + profiles in a private Git repo or cloud storage
ALL team members and CI machines use the same signing identity
# Matchfile
git_url("https://github.com/client-org/ios-certificates.git")
storage_mode("git")
type("appstore") # or development, adhoc
app_identifier("com.client.appname")
team_id("TEAM_ID_HERE")
TOOL: fastlane match development — create/fetch development cert + profile
TOOL: fastlane match appstore — create/fetch App Store distribution cert + profile
TOOL: fastlane match adhoc — create/fetch Ad Hoc cert + profile
TOOL: fastlane match nuke distribution — revoke ALL distribution certs (DANGEROUS)
RULE: match password MUST be stored in CI secrets (not in repo)
RULE: match repo MUST be private
RULE: IF switching to match THEN nuke existing certs first (one-time migration)
Xcode Cloud¶
IF: using Apple's native CI/CD
THEN: signing is managed automatically
- Xcode Cloud has access to Apple Developer account
- No need for manual cert management
- Configure in Xcode → Product → Xcode Cloud
GitHub Actions / Other CI¶
# Example: install certificate and profile on CI runner
- name: Install signing certificate
env:
CERTIFICATE_BASE64: ${{ secrets.P12_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.P12_PASSWORD }}
run: |
CERT_PATH=$RUNNER_TEMP/certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
echo -n "$CERTIFICATE_BASE64" | base64 --decode -o $CERT_PATH
security create-keychain -p "" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "" $KEYCHAIN_PATH
security import $CERT_PATH -P "$CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
ANTI_PATTERN: storing .p12 files in the code repository
FIX: use CI secrets, Fastlane match, or 1Password CLI for cert distribution
ANTI_PATTERN: using a developer's personal certificate in CI
FIX: create dedicated CI certificate or use Fastlane match
SIGNING:COMMON_ERRORS¶
"No signing certificate found"¶
CHECK: is the certificate installed in the keychain?
CHECK: is the private key present? (certificate without key = useless)
CHECK: has the certificate expired?
RUN: security find-identity -v -p codesigning — verify
FIX: re-download from portal, or re-export .p12 from another machine
"Provisioning profile doesn't include signing certificate"¶
CHECK: the profile was created with a different certificate
FIX: regenerate profile in Developer Portal with current certificate
FIX: or download correct certificate that matches profile
"A valid provisioning profile matching the application's Identifier could not be found"¶
CHECK: Bundle Identifier in Xcode matches App ID in Developer Portal
CHECK: profile is installed (~/Library/MobileDevice/Provisioning Profiles/)
CHECK: profile is not expired
FIX: Xcode → Signing & Capabilities → refresh, or re-download from portal
"The executable was signed with invalid entitlements"¶
CHECK: entitlements in .entitlements file match profile's allowed entitlements
CHECK: capability enabled in Developer Portal AND in Xcode
FIX: regenerate provisioning profile after enabling capability in portal
"This app could not be installed at this time" (device install failure)¶
CHECK: device UDID is in the provisioning profile (Ad Hoc/Development)
CHECK: profile is not expired
CHECK: iOS version on device is >= deployment target
FIX: add device UDID to portal → regenerate profile → re-sign build
SIGNING:YEARLY_RENEWAL_CHECKLIST¶
ANNUAL (at Apple Developer account renewal):
□ Renew Apple Developer membership (auto-renew or manual)
□ Regenerate expiring distribution certificates
□ Regenerate all provisioning profiles
□ Update Fastlane match repo with new certs/profiles
□ Test CI/CD pipeline with new signing identity
□ Update push notification certificates (APNs)
□ Verify all production apps still downloadable
□ IF enterprise cert renewal THEN redistribute all in-house apps
30 DAYS BEFORE EXPIRY:
□ Calendar alert for certificate expiration
□ Calendar alert for profile expiration
□ Calendar alert for push cert expiration
□ Pre-generate new certs in test environment