← Back to DevBytes

macOS Notarization Process

Introduction to macOS Notarization

macOS Notarization is a security process introduced by Apple that verifies applications distributed outside the Mac App Store. When you notarize your app, Apple's automated system scans the software for malicious content and confirms that it is properly signed and free from known security issues. Once approved, Apple issues a notarization ticket that the operating system can verify at runtime, allowing Gatekeeper to let users run your app without scary warning dialogs.

Since macOS Catalina (10.15), notarization has been a mandatory requirement for all software distributed outside the Mac App Store. Without it, users encounter blocking dialogs that prevent them from launching your application by default, which can severely impact adoption and trust.

Why Notarization Matters

Notarization serves several critical purposes in the macOS ecosystem:

Prerequisites

Before you can notarize an application, you need the following:

Creating an App-Specific Password

Because notarization uses your Apple ID credentials, Apple requires an app-specific password instead of your account password. Generate one at appleid.apple.com under the Security section, then store it securely. You will reference it when submitting your app for notarization.

Step 1: Code Signing Your Application

Notarization requires that your app be signed with a valid Developer ID Application certificate. The signature must be "deep," meaning all embedded frameworks, helpers, and binaries must also be signed. Here is a basic example of signing an app bundle:

# Sign embedded frameworks first
codesign --force --deep --options runtime \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  --entitlements entitlements.plist \
  /path/to/YourApp.app/Contents/Frameworks/SomeFramework.framework

# Sign the main app bundle
codesign --force --deep --options runtime \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  --entitlements entitlements.plist \
  /path/to/YourApp.app

The --options runtime flag enables the Hardened Runtime, which is mandatory for notarization. It restricts dangerous capabilities like unsigned memory execution and dynamic code injection unless explicitly allowed via entitlements.

Verifying the Signature

After signing, verify that the signature is valid and that all components are properly sealed:

codesign --verify --deep --strict --verbose=2 /path/to/YourApp.app

You should see output confirming that the app satisfies the designated requirement and that no unsigned components exist. If any embedded binary lacks a signature, notarization will fail.

Step 2: Creating a Distributable Archive

Notarization accepts several archive formats, but the most common are ZIP and DMG. For apps with many files, a DMG is often preferred because it preserves the directory structure and is the final artifact users download. Use ditto to create a ZIP that preserves macOS metadata:

ditto -c -k --keepParent /path/to/YourApp.app /path/to/YourApp.zip

For a DMG, use hdiutil:

hdiutil create -volname "YourApp" -srcfolder /path/to/build_output \
  -ov -format UDZO YourApp.dmg

Regardless of format, the archive must contain only signed binaries. Any unsigned helper tool or framework inside the archive will cause notarization to fail.

Step 3: Submitting for Notarization

Submit your archive to Apple using the notarytool command, which replaced the older altool workflow. notarytool is faster, more reliable, and provides better error reporting. You will need your Apple ID, the team ID, and the app-specific password created earlier:

xcrun notarytool submit /path/to/YourApp.zip \
  --apple-id "you@example.com" \
  --team-id "TEAMID12345" \
  --password "xxxx-xxxx-xxxx-xxxx" \
  --wait

The --wait flag tells the tool to block until Apple finishes processing the submission. Processing typically takes a few minutes, though complex apps can take longer. The output includes a submission ID that you can use to check status later if needed.

Storing Credentials Securely

Typing credentials on the command line exposes them in shell history. notarytool supports storing credentials in the macOS Keychain so you never have to type them again:

xcrun notarytool store-credentials "AC_PASSWORD" \
  --apple-id "you@example.com" \
  --team-id "TEAMID12345" \
  --password "xxxx-xxxx-xxxx-xxxx"

After storing, subsequent submissions reference the keychain profile by name:

xcrun notarytool submit /path/to/YourApp.zip \
  --keychain-profile "AC_PASSWORD" \
  --wait

Step 4: Checking Notarization Status

If you did not use --wait, or if you want to inspect a previous submission, query the status using the submission ID:

xcrun notarytool info <submission-id> \
  --keychain-profile "AC_PASSWORD"

To retrieve the full JSON log, which contains detailed reasons for any failures, use:

xcrun notarytool log <submission-id> \
  --keychain-profile "AC_PASSWORD" \
  notarization_log.json

The log lists every file scanned and any issues found. Common failures include unsigned binaries, incorrect entitlements, or the use of deprecated APIs. Address each issue, re-sign, and resubmit.

Step 5: Stapling the Notarization Ticket

Once Apple approves your submission, the notarization ticket is hosted on Apple's servers. However, if a user downloads your app without internet access, Gatekeeper cannot fetch the ticket. To solve this, staple the ticket directly to your app or DMG:

xcrun stapler staple /path/to/YourApp.app
xcrun stapler staple /path/to/YourApp.dmg

Verify that stapling succeeded:

xcrun stapler validate /path/to/YourApp.app

For DMG files, also verify the notarization status of the enclosed app:

spctl --assess --type open --context context:primary-signature -v /path/to/YourApp.dmg

If validation passes, your app is ready for distribution.

Automating the Notarization Workflow

Manual notarization is error-prone, especially when shipping frequent updates. Most teams automate the entire pipeline in a shell script or CI/CD system. Below is a complete example script that signs, archives, submits, staples, and validates an app:

#!/bin/bash
set -euo pipefail

APP_PATH="build/YourApp.app"
DMG_PATH="build/YourApp.dmg"
SIGN_ID="Developer ID Application: Your Name (TEAMID)"
KEYCHAIN_PROFILE="AC_PASSWORD"

echo "Signing app..."
codesign --force --deep --options runtime \
  --sign "$SIGN_ID" \
  --entitlements entitlements.plist \
  "$APP_PATH"

echo "Verifying signature..."
codesign --verify --deep --strict --verbose=2 "$APP_PATH"

echo "Creating DMG..."
hdiutil create -volname "YourApp" \
  -srcfolder "build/" \
  -ov -format UDZO "$DMG_PATH"

echo "Submitting for notarization..."
xcrun notarytool submit "$DMG_PATH" \
  --keychain-profile "$KEYCHAIN_PROFILE" \
  --wait

echo "Stapling ticket..."
xcrun stapler staple "$DMG_PATH"

echo "Validating..."
xcrun stapler validate "$DMG_PATH"

echo "Done. $DMG_PATH is ready for distribution."

Integrate this script into your CI pipeline, triggering it on tagged releases. Store the keychain profile as a secret in your CI system, and ensure the build runner has the Developer ID certificate installed.

Common Notarization Failures and Fixes

Even experienced developers encounter notarization rejections. Understanding the most frequent issues saves debugging time:

Best Practices

To keep your notarization workflow smooth and reliable, follow these best practices:

Conclusion

macOS Notarization is an essential step in distributing software outside the Mac App Store, providing users with confidence that your app has been scanned and signed by a verified developer. By understanding the full pipeline—from code signing with the Hardened Runtime, through submission with notarytool, to stapling the final ticket—you can ship apps that open cleanly on every Mac. Automating the workflow and following best practices will keep your releases reliable, secure, and ready for whatever new requirements Apple introduces in future macOS versions.

— Ad —

Google AdSense will appear here after approval

← Back to all articles