iOS Code Signing: Certificates and Provisioning Profiles
iOS Code Signing: Certificates and Provisioning Profiles
Before an iOS app can run on a real device or be distributed through the App Store, Apple requires it to be cryptographically signed. This signing chain proves that the binary came from a legitimate developer and has not been tampered with since it was built. Understanding how signing works is essential for every Flutter developer targeting iOS, because signing mismatches are the most common cause of build failures and App Store rejections.
The Signing Chain: How Apple Verifies Your App
Apple uses a hierarchical trust model with three key components that must all align before a device will run your app:
- Developer Certificate — Issued by Apple after you join the Apple Developer Program. It proves who you are. There are two kinds: a Development certificate (for debug builds on registered devices) and a Distribution certificate (for TestFlight and App Store releases).
- App ID / Bundle ID — A unique reverse-domain string (e.g.
com.yourcompany.yourapp) registered in your Apple Developer account. It identifies what app is being signed. - Provisioning Profile — A signed file from Apple that ties together your certificate, your bundle ID, and (for development) a list of authorised device UDIDs. It answers: which certificate can sign this app, on which devices, with which entitlements?
pubspec.yaml-derived Xcode project does not match the profile's App ID, signing will fail at build time.Developer Certificates
Certificates are X.509 digital certificates stored in your macOS Keychain. You generate a Certificate Signing Request (CSR) on your Mac, upload it to the Apple Developer portal, and download the resulting .cer file. Double-clicking it installs it into Keychain Access alongside the private key that was created when you generated the CSR.
- Apple Development — Used by Xcode to sign debug builds. Tied to your personal developer account.
- Apple Distribution — Used to sign builds uploaded to App Store Connect (for TestFlight and production). Teams typically keep this certificate shared via a secure store.
.p12 backup (File > Export Items in Keychain Access). If you lose the private key, the certificate becomes useless and you must revoke it and create a new one, which invalidates existing provisioning profiles signed with the old certificate.Provisioning Profiles
A provisioning profile (.mobileprovision file) is the glue that binds your certificate, bundle ID, and devices. There are four main profile types:
- iOS App Development — Debug builds on specific registered device UDIDs.
- Ad Hoc — Distribution to up to 100 registered devices without the App Store.
- App Store — Builds submitted to App Store Connect; no device list required.
- Enterprise — In-house distribution for organisations with Apple's Enterprise Program (requires separate membership).
Inspecting a Provisioning Profile from the Terminal
// Decode a .mobileprovision file to read its contents
// Run this in your macOS Terminal (not Dart code):
//
// security cms -D -i ~/Library/MobileDevice/Provisioning\ Profiles/<UUID>.mobileprovision
//
// Key fields to verify:
// <key>AppIDName</key> -- human-readable name
// <key>application-identifier</key> -- TeamID.BundleID
// <key>DeveloperCertificates</key> -- base64 cert(s) embedded
// <key>ProvisionedDevices</key> -- UDIDs (dev/ad-hoc only)
// <key>ExpirationDate</key> -- profile validity date
// In a Flutter project, you can also inspect from the ios/ folder:
// open ios/Runner.xcworkspace
Automatic vs. Manual Signing in Xcode
Xcode offers two signing modes, both accessible via Xcode > Runner target > Signing & Capabilities:
Automatic Signing
With Automatically manage signing checked, Xcode creates and updates certificates and provisioning profiles for you. This is the recommended starting point for new projects. Xcode registers new devices, creates missing profiles, and renews expired ones transparently. The downside is that it requires a persistent internet connection to Apple's servers and can behave unexpectedly in CI environments without a valid Apple ID logged in.
Manual Signing
Manual signing lets you specify the exact provisioning profile and certificate for each build configuration (Debug, Release, Profile). This is essential for CI/CD pipelines where no Apple ID session exists. You download profiles from the Apple Developer portal, store them as files (often base64-encoded in environment variables), and reference them explicitly.
Setting the Bundle ID and Team in Flutter's Xcode Project (Dart-side config)
// Flutter does not have a Dart API for code signing —
// signing is configured in Xcode. However, you can automate
// common tasks with a build script invoked from pubspec.yaml:
// pubspec.yaml (scripts section — conceptual, not standard Flutter):
// scripts:
// set_bundle_id: |
// cd ios
// /usr/libexec/PlistBuddy -c \
// "Set :CFBundleIdentifier com.example.myApp" \
// Runner/Info.plist
// The canonical Flutter way: edit ios/Runner.xcodeproj/project.pbxproj
// or use Xcode GUI. The PRODUCT_BUNDLE_IDENTIFIER build setting drives
// which provisioning profile is selected when using automatic signing.
// Verifying the bundle ID matches your profile (Dart helper script):
import 'dart:io';
void main() async {
final result = await Process.run('xcodebuild', [
'-showBuildSettings',
'-workspace', 'ios/Runner.xcworkspace',
'-scheme', 'Runner',
]);
final lines = result.stdout.toString().split('\n');
final bundleLine = lines.firstWhere(
(l) => l.contains('PRODUCT_BUNDLE_IDENTIFIER'),
orElse: () => 'Not found',
);
print('Bundle ID setting: $bundleLine');
}
Entitlements and Capabilities
Entitlements are key-value pairs that grant your app specific Apple services — Push Notifications, iCloud, Sign in with Apple, App Groups, etc. They are declared in ios/Runner/Runner.entitlements and must match the capabilities enabled in your App ID on the developer portal. The provisioning profile embeds the allowed entitlements; if your binary requests an entitlement not in the profile, signing or device installation will fail.
flutter build ios --release locally at least once before setting up CI. It surfaces signing errors with clearer messages than CI logs, and the local Xcode build will auto-create certificates if you have automatic signing enabled.Summary
Apple's code signing chain requires three aligned components: a developer certificate (who you are), a bundle ID (what app), and a provisioning profile (which cert + which devices + which entitlements). Development certificates are used for debug builds; distribution certificates for TestFlight and App Store submissions. Xcode's automatic signing handles routine development seamlessly, while manual signing is the reliable choice for CI/CD pipelines. Keeping your private key backed up as a .p12 file and understanding how entitlements are embedded in profiles will save you hours of debugging in a production pipeline.