Skip to content

Apple codesign

Sign macOS applications, iOS apps, frameworks, and disk images using Apple Developer certificates managed by QCecuring with HSM-backed private keys.


┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ 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
  • codesign CLI 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”
  • Active Apple Developer Program membership
  • Developer ID Application certificate (for distribution outside App Store)
  • Developer ID Installer certificate (for .pkg files)
  1. Generate a Certificate Signing Request (CSR) from QCecuring:
Terminal window
qcecuring-agent apple generate-csr \
--key-algorithm RSA4096 \
--label "Developer ID Application" \
--output developer-id.csr
  1. 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)
  2. Import the certificate into QCecuring:

Terminal window
qcecuring-agent apple import-cert \
--cert-file developer_id_application.cer \
--label "Developer ID Application - YourOrg"

If you already have a .p12 file:

Terminal window
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”
Section titled “Option A: Keychain Integration (Recommended)”

The QCecuring Agent registers the certificate in a macOS Keychain, making it available to codesign and Xcode.

Terminal window
# Install and configure the agent for Keychain integration
qcecuring-agent apple configure-keychain \
--cert-id "developer-id-app-prod" \
--keychain "qcecuring-signing.keychain-db"
# Verify the certificate is accessible
security find-identity -v -p codesigning
# Expected output:
# 1) A1B2C3D4... "Developer ID Application: YourOrg (TEAM_ID)"
# 1 valid identities found

For environments where Keychain integration is not suitable:

Terminal window
# Configure PKCS#11 for codesign
export QCECURING_PKCS11_MODULE="/usr/local/lib/qcecuring-pkcs11.dylib"
# List available signing identities
pkcs11-tool --module $QCECURING_PKCS11_MODULE --list-objects --type cert

Terminal window
# Sign a .app bundle with hardened runtime
codesign --force --options runtime \
--sign "Developer ID Application: YourOrg (TEAM_ID)" \
--timestamp \
--deep \
MyApp.app
# Verify the signature
codesign --verify --verbose=4 MyApp.app
# Check the signing details
codesign --display --verbose=4 MyApp.app
Terminal window
# Sign with entitlements file
codesign --force --options runtime \
--sign "Developer ID Application: YourOrg (TEAM_ID)" \
--entitlements MyApp.entitlements \
--timestamp \
MyApp.app

Example 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>
Terminal window
# Create a DMG from the signed app
hdiutil create -volname "MyApp" \
-srcfolder MyApp.app \
-ov -format UDZO \
MyApp.dmg
# Sign the DMG
codesign --force \
--sign "Developer ID Application: YourOrg (TEAM_ID)" \
--timestamp \
MyApp.dmg
# Verify
codesign --verify --verbose MyApp.dmg
Terminal window
# Sign an installer package
productsign --sign "Developer ID Installer: YourOrg (TEAM_ID)" \
--timestamp \
MyApp-unsigned.pkg \
MyApp.pkg
# Verify
pkgutil --check-signature MyApp.pkg
Terminal window
# 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 tools
codesign --force --options runtime \
--sign "Developer ID Application: YourOrg (TEAM_ID)" \
--timestamp \
MyApp.app/Contents/MacOS/helper-tool

Notarization is required for apps distributed outside the Mac App Store on macOS 10.15+.

Terminal window
# Submit the signed app/dmg for notarization
xcrun notarytool submit MyApp.dmg \
--apple-id "developer@example.com" \
--team-id "TEAM_ID" \
--password "$APP_SPECIFIC_PASSWORD" \
--wait
# Alternative: submit a zip of the .app
ditto -c -k --keepParent MyApp.app MyApp.zip
xcrun notarytool submit MyApp.zip \
--apple-id "developer@example.com" \
--team-id "TEAM_ID" \
--password "$APP_SPECIFIC_PASSWORD" \
--wait
Terminal window
# 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 credentials
xcrun notarytool submit MyApp.dmg \
--keychain-profile "notarytool-profile" \
--wait
Terminal window
# Staple the ticket to the app/dmg (allows offline verification)
xcrun stapler staple MyApp.app
xcrun stapler staple MyApp.dmg
xcrun stapler staple MyApp.pkg
# Verify stapling
xcrun stapler validate MyApp.app
Terminal window
# Check submission history
xcrun notarytool history \
--keychain-profile "notarytool-profile"
# Get detailed log for a submission
xcrun notarytool log <submission-id> \
--keychain-profile "notarytool-profile" \
developer_log.json

Complete Signing and Notarization Workflow

Section titled “Complete Signing and Notarization Workflow”
sign-and-notarize.sh
#!/bin/bash
set -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"

Configure Xcode to Use QCecuring Certificate

Section titled “Configure Xcode to Use QCecuring Certificate”
  1. Ensure the QCecuring Agent is running and the certificate is in the Keychain
  2. In Xcode, navigate to Signing & Capabilities
  3. Uncheck Automatically manage signing
  4. Select the QCecuring-managed certificate from the Signing Certificate dropdown
  5. Set the correct Team and Bundle Identifier
CODE_SIGN_IDENTITY = "Developer ID Application: YourOrg (TEAM_ID)"
CODE_SIGN_STYLE = Manual
DEVELOPMENT_TEAM = TEAM_ID
OTHER_CODE_SIGN_FLAGS = --timestamp --options runtime
Terminal window
# Archive
xcodebuild archive \
-project MyApp.xcodeproj \
-scheme MyApp \
-archivePath build/MyApp.xcarchive \
CODE_SIGN_IDENTITY="Developer ID Application: YourOrg (TEAM_ID)" \
CODE_SIGN_STYLE=Manual
# Export
xcodebuild -exportArchive \
-archivePath build/MyApp.xcarchive \
-exportPath build/export \
-exportOptionsPlist ExportOptions.plist

Example 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>

IssueCauseResolution
No identity foundCertificate not in KeychainRun qcecuring-agent apple configure-keychain
errSecInternalComponentKeychain access issueUnlock keychain: security unlock-keychain
The signature is invalidBinary modified after signingRe-sign after any post-processing
Notarization rejected: hardened runtimeMissing --options runtime flagAdd --options runtime to codesign command
Notarization rejected: unsigned binariesEmbedded frameworks not signedSign all nested code before the main bundle
The timestamp was not verifiedTimestamp server unreachableCheck network; Apple’s TSA is used by default with --timestamp
Xcode can’t find identityAgent not running or wrong KeychainVerify with security find-identity -v -p codesigning
Stapler failsApp not yet notarizedWait for notarization to complete before stapling

  • Always use hardened runtime (--options runtime) for notarization compatibility
  • Sign inside-out: frameworks → helpers → main app → DMG/PKG
  • Always include --timestamp for long-term signature validity
  • Use --deep only 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