Apple codesign
Apple codesign
Section titled “Apple codesign”Sign macOS applications, iOS apps, frameworks, and disk images using Apple Developer certificates managed by QCecuring with HSM-backed private keys.
Architecture
Section titled “Architecture”┌──────────────┐ ┌──────────────────┐ ┌──────────────┐│ codesign │──────►│ QCecuring Agent │──────►│ QCecuring ││ CLI │ │ (Keychain / │ │ Platform ││ │ │ PKCS#11) │ │ │└──────────────┘ └──────────────────┘ └──────────────┘ │ │ │ ▼ │ ┌──────────────┐ │ │ HSM │ ▼ │ (Private │┌──────────────┐ │ Keys) ││ Apple │ └──────────────┘│ Notary ││ Service │└──────────────┘Key points:
- Apple Developer certificates are stored in QCecuring with HSM-protected keys
- The QCecuring Agent integrates with macOS Keychain or provides PKCS#11 access
codesignCLI uses the certificate transparently- Notarization is performed after signing via
xcrun notarytool
Step 1: Import Apple Developer Certificate
Section titled “Step 1: Import Apple Developer Certificate”Prerequisites
Section titled “Prerequisites”- Active Apple Developer Program membership
- Developer ID Application certificate (for distribution outside App Store)
- Developer ID Installer certificate (for
.pkgfiles)
Import Certificate into QCecuring
Section titled “Import Certificate into QCecuring”- Generate a Certificate Signing Request (CSR) from QCecuring:
qcecuring-agent apple generate-csr \ --key-algorithm RSA4096 \ --label "Developer ID Application" \ --output developer-id.csr-
Submit the CSR to Apple Developer Portal:
- Navigate to Certificates, Identifiers & Profiles
- Create a new Developer ID Application certificate
- Upload the CSR file
- Download the issued certificate (
.cer)
-
Import the certificate into QCecuring:
qcecuring-agent apple import-cert \ --cert-file developer_id_application.cer \ --label "Developer ID Application - YourOrg"Alternative: Import Existing Certificate
Section titled “Alternative: Import Existing Certificate”If you already have a .p12 file:
qcecuring-agent apple import-p12 \ --p12-file Certificates.p12 \ --password "$P12_PASSWORD" \ --label "Developer ID Application - YourOrg"Step 2: Configure macOS Keychain Integration
Section titled “Step 2: Configure macOS Keychain Integration”Option A: Keychain Integration (Recommended)
Section titled “Option A: Keychain Integration (Recommended)”The QCecuring Agent registers the certificate in a macOS Keychain, making it available to codesign and Xcode.
# Install and configure the agent for Keychain integrationqcecuring-agent apple configure-keychain \ --cert-id "developer-id-app-prod" \ --keychain "qcecuring-signing.keychain-db"
# Verify the certificate is accessiblesecurity find-identity -v -p codesigning
# Expected output:# 1) A1B2C3D4... "Developer ID Application: YourOrg (TEAM_ID)"# 1 valid identities foundOption B: PKCS#11 Integration
Section titled “Option B: PKCS#11 Integration”For environments where Keychain integration is not suitable:
# Configure PKCS#11 for codesignexport QCECURING_PKCS11_MODULE="/usr/local/lib/qcecuring-pkcs11.dylib"
# List available signing identitiespkcs11-tool --module $QCECURING_PKCS11_MODULE --list-objects --type certStep 3: Sign .app Bundles and .dmg Files
Section titled “Step 3: Sign .app Bundles and .dmg Files”Sign an Application Bundle
Section titled “Sign an Application Bundle”# Sign a .app bundle with hardened runtimecodesign --force --options runtime \ --sign "Developer ID Application: YourOrg (TEAM_ID)" \ --timestamp \ --deep \ MyApp.app
# Verify the signaturecodesign --verify --verbose=4 MyApp.app
# Check the signing detailscodesign --display --verbose=4 MyApp.appSign with Entitlements
Section titled “Sign with Entitlements”# Sign with entitlements filecodesign --force --options runtime \ --sign "Developer ID Application: YourOrg (TEAM_ID)" \ --entitlements MyApp.entitlements \ --timestamp \ MyApp.appExample entitlements file (MyApp.entitlements):
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>com.apple.security.cs.allow-jit</key> <true/> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> <key>com.apple.security.cs.disable-library-validation</key> <true/></dict></plist>Sign a Disk Image (.dmg)
Section titled “Sign a Disk Image (.dmg)”# Create a DMG from the signed apphdiutil create -volname "MyApp" \ -srcfolder MyApp.app \ -ov -format UDZO \ MyApp.dmg
# Sign the DMGcodesign --force \ --sign "Developer ID Application: YourOrg (TEAM_ID)" \ --timestamp \ MyApp.dmg
# Verifycodesign --verify --verbose MyApp.dmgSign a Package (.pkg)
Section titled “Sign a Package (.pkg)”# Sign an installer packageproductsign --sign "Developer ID Installer: YourOrg (TEAM_ID)" \ --timestamp \ MyApp-unsigned.pkg \ MyApp.pkg
# Verifypkgutil --check-signature MyApp.pkgSign Frameworks and Dylibs
Section titled “Sign Frameworks and Dylibs”# Sign embedded frameworks (before signing the main app)codesign --force --options runtime \ --sign "Developer ID Application: YourOrg (TEAM_ID)" \ --timestamp \ MyApp.app/Contents/Frameworks/MyFramework.framework
# Sign helper toolscodesign --force --options runtime \ --sign "Developer ID Application: YourOrg (TEAM_ID)" \ --timestamp \ MyApp.app/Contents/MacOS/helper-toolStep 4: Notarize with Apple
Section titled “Step 4: Notarize with Apple”Notarization is required for apps distributed outside the Mac App Store on macOS 10.15+.
Submit for Notarization
Section titled “Submit for Notarization”# Submit the signed app/dmg for notarizationxcrun notarytool submit MyApp.dmg \ --apple-id "developer@example.com" \ --team-id "TEAM_ID" \ --password "$APP_SPECIFIC_PASSWORD" \ --wait
# Alternative: submit a zip of the .appditto -c -k --keepParent MyApp.app MyApp.zipxcrun notarytool submit MyApp.zip \ --apple-id "developer@example.com" \ --team-id "TEAM_ID" \ --password "$APP_SPECIFIC_PASSWORD" \ --waitStore Credentials for CI/CD
Section titled “Store Credentials for CI/CD”# Store notarization credentials in Keychain (interactive, one-time setup)xcrun notarytool store-credentials "notarytool-profile" \ --apple-id "developer@example.com" \ --team-id "TEAM_ID" \ --password "$APP_SPECIFIC_PASSWORD"
# Use stored credentialsxcrun notarytool submit MyApp.dmg \ --keychain-profile "notarytool-profile" \ --waitStaple the Notarization Ticket
Section titled “Staple the Notarization Ticket”# Staple the ticket to the app/dmg (allows offline verification)xcrun stapler staple MyApp.appxcrun stapler staple MyApp.dmgxcrun stapler staple MyApp.pkg
# Verify staplingxcrun stapler validate MyApp.appCheck Notarization Status
Section titled “Check Notarization Status”# Check submission historyxcrun notarytool history \ --keychain-profile "notarytool-profile"
# Get detailed log for a submissionxcrun notarytool log <submission-id> \ --keychain-profile "notarytool-profile" \ developer_log.jsonComplete Signing and Notarization Workflow
Section titled “Complete Signing and Notarization Workflow”#!/bin/bashset -e
APP_NAME="MyApp"IDENTITY="Developer ID Application: YourOrg (TEAM_ID)"NOTARY_PROFILE="notarytool-profile"
echo "=== Signing embedded frameworks ==="find "${APP_NAME}.app/Contents/Frameworks" -name "*.framework" -exec \ codesign --force --options runtime --sign "$IDENTITY" --timestamp {} \;
echo "=== Signing helper tools ==="find "${APP_NAME}.app/Contents/MacOS" -type f -perm +111 ! -name "$APP_NAME" -exec \ codesign --force --options runtime --sign "$IDENTITY" --timestamp {} \;
echo "=== Signing main application ==="codesign --force --options runtime \ --sign "$IDENTITY" \ --entitlements "${APP_NAME}.entitlements" \ --timestamp \ "${APP_NAME}.app"
echo "=== Verifying signature ==="codesign --verify --deep --strict --verbose=2 "${APP_NAME}.app"
echo "=== Creating DMG ==="hdiutil create -volname "$APP_NAME" \ -srcfolder "${APP_NAME}.app" \ -ov -format UDZO \ "${APP_NAME}.dmg"
codesign --force --sign "$IDENTITY" --timestamp "${APP_NAME}.dmg"
echo "=== Submitting for notarization ==="xcrun notarytool submit "${APP_NAME}.dmg" \ --keychain-profile "$NOTARY_PROFILE" \ --wait
echo "=== Stapling ticket ==="xcrun stapler staple "${APP_NAME}.dmg"
echo "=== Done ==="xcrun stapler validate "${APP_NAME}.dmg"Xcode Integration
Section titled “Xcode Integration”Configure Xcode to Use QCecuring Certificate
Section titled “Configure Xcode to Use QCecuring Certificate”- Ensure the QCecuring Agent is running and the certificate is in the Keychain
- In Xcode, navigate to Signing & Capabilities
- Uncheck Automatically manage signing
- Select the QCecuring-managed certificate from the Signing Certificate dropdown
- Set the correct Team and Bundle Identifier
Xcode Build Settings
Section titled “Xcode Build Settings”CODE_SIGN_IDENTITY = "Developer ID Application: YourOrg (TEAM_ID)"CODE_SIGN_STYLE = ManualDEVELOPMENT_TEAM = TEAM_IDOTHER_CODE_SIGN_FLAGS = --timestamp --options runtimeArchive and Export via xcodebuild
Section titled “Archive and Export via xcodebuild”# Archivexcodebuild archive \ -project MyApp.xcodeproj \ -scheme MyApp \ -archivePath build/MyApp.xcarchive \ CODE_SIGN_IDENTITY="Developer ID Application: YourOrg (TEAM_ID)" \ CODE_SIGN_STYLE=Manual
# Exportxcodebuild -exportArchive \ -archivePath build/MyApp.xcarchive \ -exportPath build/export \ -exportOptionsPlist ExportOptions.plistExample ExportOptions.plist:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>method</key> <string>developer-id</string> <key>signingStyle</key> <string>manual</string> <key>signingCertificate</key> <string>Developer ID Application</string> <key>teamID</key> <string>TEAM_ID</string></dict></plist>Troubleshooting
Section titled “Troubleshooting”| Issue | Cause | Resolution |
|---|---|---|
No identity found | Certificate not in Keychain | Run qcecuring-agent apple configure-keychain |
errSecInternalComponent | Keychain access issue | Unlock keychain: security unlock-keychain |
The signature is invalid | Binary modified after signing | Re-sign after any post-processing |
Notarization rejected: hardened runtime | Missing --options runtime flag | Add --options runtime to codesign command |
Notarization rejected: unsigned binaries | Embedded frameworks not signed | Sign all nested code before the main bundle |
The timestamp was not verified | Timestamp server unreachable | Check network; Apple’s TSA is used by default with --timestamp |
| Xcode can’t find identity | Agent not running or wrong Keychain | Verify with security find-identity -v -p codesigning |
| Stapler fails | App not yet notarized | Wait for notarization to complete before stapling |
Best Practices
Section titled “Best Practices”- Always use hardened runtime (
--options runtime) for notarization compatibility - Sign inside-out: frameworks → helpers → main app → DMG/PKG
- Always include
--timestampfor long-term signature validity - Use
--deeponly for initial signing; prefer explicit per-component signing - Store notarization credentials securely (Keychain profile or CI secrets)
- Test the full workflow on a clean macOS installation before release
- Keep Apple Developer certificates in QCecuring with automatic renewal alerts
- Use separate certificates for development and distribution