macOS Code Signing and Notarization
Overview
macOS Gatekeeper prevents unsigned or unnotarized binaries from running. When users attempt to open an unverified binary, macOS blocks execution and displays a security warning. This applies to all binaries distributed outside the Mac App Store, including internal tools and CLI utilities.
To distribute internal binaries that run without Gatekeeper warnings, you need to code sign them with a valid Developer ID certificate and submit them to Apple for notarization. This guide walks through the entire process, from setting up your Apple Developer account to notarizing your final binary.
Prerequisites
Apple Developer Account
You need an active Apple Developer Program membership to obtain the signing certificates required for distribution outside the App Store. This is a paid membership that costs $99/year (or $299/year for organizations via the Apple Developer Enterprise Program).
- Go to developer.apple.com/programs and enroll in the Apple Developer Program.
- Complete the enrollment process, including identity verification. For organizations, Apple may require a D-U-N-S number.
- Once enrolled, you will have access to the Apple Developer portal where you can manage certificates, identifiers, and profiles.
Enterprise Program membership is not required for notarization. The standard $99/year Apple Developer Program is sufficient.
Required Tools
- macOS with Xcode or Xcode Command Line Tools installed
codesign— built-in macOS code signing utilityxcrun notarytool— Apple's notarization tool (ships with Xcode 13+)ditto— built-in macOS archive utility
Signing and Notarizing Workflow
Create a Developer ID Certificate
Generate a certificate signing request and create a Developer ID Application certificate in the Apple Developer Portal.
Sign the Binary
Use
codesignwith the hardened runtime to sign your binary.Package for Notarization
Create a ZIP archive using
dittoto preserve macOS extended attributes.Submit for Notarization
Submit the package to Apple for scanning and verification.
Staple the Ticket (Optional)
Attach the notarization ticket for offline verification.
Step 1: Create a Developer ID Certificate
A Developer ID Application certificate is required to sign binaries for distribution outside the Mac App Store. This certificate tells macOS that the binary comes from an identified developer.
Generate a Certificate Signing Request (CSR)
- Open Keychain Access on your Mac (Applications > Utilities > Keychain Access).
- From the menu bar, select Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority.
- Enter your email address and common name. Leave the CA Email Address field empty.
- Select "Saved to disk" and click Continue.
- Save the
.certSigningRequestfile to a known location.
Create the Certificate in the Apple Developer Portal
- Go to developer.apple.com/account/resources/certificates/list.
- Click the "+" button to create a new certificate.
- Under "Software", select "Developer ID Application" and click Continue.
- Upload the CSR file you generated in the previous step.
- Download the generated certificate (
.cerfile) and double-click it to install it into your Keychain.
Verify the Certificate
Confirm the certificate is properly installed:
security find-identity -v -p codesigning
You should see an entry like "Developer ID Application: Your Name (TEAM_ID)" in the output.
Step 2: Sign the Binary
Use codesign to sign your binary with the Developer ID Application certificate. The --options runtime flag enables the hardened runtime, which is required for notarization.
codesign --force --options runtime \
--sign "Developer ID Application: Your Name (TEAM_ID)" \
/path/to/your-binary
Flags Explained
| Flag | Purpose |
|---|---|
--force | Replaces any existing signature on the binary |
--options runtime | Enables the hardened runtime (required for notarization). Enforces additional security restrictions at runtime such as library validation and DYLD environment variable restrictions |
--sign | Specifies the signing identity. Use the full name as shown by security find-identity |
Verify the signature:
codesign --verify --verbose /path/to/your-binary
Step 3: Package for Notarization
Apple's notarization service requires submissions in a ZIP, DMG, or PKG format. For a single binary, creating a ZIP with ditto is the simplest approach:
ditto -c -k --keepParent /path/to/your-binary your-binary.zip
Always use ditto instead of the zip command. The standard zip utility does not preserve macOS extended attributes and resource forks, which can break the code signature.
Step 4: Submit for Notarization
Submit the packaged binary to Apple for notarization. Apple will scan the binary for malicious content and verify the code signature.
xcrun notarytool submit your-binary.zip \
--apple-id [email protected] \
--team-id YOUR_TEAM_ID \
--password your-app-specific-password \
--wait
Authentication
The --password flag requires an app-specific password, not your Apple ID password. Generate one at appleid.apple.com/account/manage under "Sign-In and Security" > "App-Specific Passwords".
Alternatively, store credentials in your keychain to avoid passing them on every invocation:
xcrun notarytool store-credentials "notary-profile" \
--apple-id [email protected] \
--team-id YOUR_TEAM_ID \
--password your-app-specific-password
Then submit using the stored profile:
xcrun notarytool submit your-binary.zip --keychain-profile "notary-profile" --wait
Understanding the Output
The --wait flag makes the command block until notarization completes. This usually takes between 1-15 minutes. On success, you'll see a status of "Accepted". If it fails, retrieve the detailed log:
xcrun notarytool log <submission-id> \
--apple-id [email protected] \
--team-id YOUR_TEAM_ID \
--password your-app-specific-password
Step 5: Staple the Notarization Ticket (Optional)
Stapling attaches the notarization ticket directly to the binary, so Gatekeeper can verify it offline without contacting Apple's servers. This is recommended for distribution but not strictly required — macOS will check Apple's servers online if no ticket is stapled.
Stapling only works on disk images (.dmg), installer packages (.pkg), and application bundles (.app). You cannot staple a standalone Mach-O binary directly. If you need offline verification for a standalone binary, wrap it in a .dmg or .pkg first.
For a .dmg or .pkg:
xcrun stapler staple your-app.dmg
Quick Reference: Full Workflow
#!/bin/bash
IDENTITY="Developer ID Application: Your Name (TEAM_ID)"
BINARY="./your-binary"
ZIP="your-binary.zip"
# Sign with hardened runtime
codesign --force --options runtime --sign "$IDENTITY" "$BINARY"
# Verify signature
codesign --verify --verbose "$BINARY"
# Package for notarization
ditto -c -k --keepParent "$BINARY" "$ZIP"
# Submit for notarization (using keychain profile)
xcrun notarytool submit "$ZIP" \
--keychain-profile "notary-profile" \
--wait
# Clean up
rm "$ZIP"
echo "Done. Binary is signed and notarized."
Troubleshooting
"your-binary" is damaged and can't be opened
This typically means the binary is not signed or the signature is invalid. Re-sign and verify with codesign --verify.
"your-binary" cannot be opened because the developer cannot be verified
The binary is signed but not notarized. Submit it for notarization and ensure the process completes with an "Accepted" status.
Notarization fails with 'The signature of the binary is invalid'
Ensure you are using --options runtime when signing. Notarization requires the hardened runtime to be enabled.
Certificate not found by codesign
Verify the certificate is installed in your login keychain with security find-identity -v -p codesigning. If the private key is missing, you may need to re-generate the CSR and create a new certificate.
Manual Gatekeeper Bypass (Development Only)
If you need to run an unsigned binary locally during development, you can remove the quarantine attribute:
xattr -d com.apple.quarantine /path/to/your-binary
This should never be used for distribution. It only works on the local machine and does not bypass Gatekeeper for other users.