Mac App Distribution and Signing Fundamentals
Notarization Rules
- Apps distributed via the Mac App Store do not require separate notarization, as Apple performs equivalent security checks during the review process.
- Applications distributed outside the Mac App Store must be notarized. Without this, macOS will display warnings stating the app "may harm your computer."
Code Signing Requirements
-
Mandatory Signing: All Mac applications must be code-signed. If you build an
.appfile using Unity, you must manually sign the code. -
Identifying Executables: Use the
filecommand to determine if a binary is the main executable. A main executable is identified byMach-O … executable.$ file /Applications/MyGame.app/Contents/PlugIns/AudioKit.bundle/Contents/MacOS/AudioKit /Applications/MyGame.app/Contents/PlugIns/AudioKit.bundle/Contents/MacOS/AudioKit: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit bundle x86_64] [arm64] # Output indicates AudioKit is a bundle, not the main executable. -
Signing Order: Sign dependencies before the components that rely on them (inside-out). For example, for a suite containing
ConfigTool.app,Core.framework,Share.appex, andBackgroundDaemon:- Sign
Core.frameworkfirst. - Sign
Share.appex. - Sign
ConfigTool.app. BackgroundDaemoncan be signed independently at any time.
- Sign
-
Entitlements: These files (
.entitlements) define the permissions granted to the process at runtime. -
Provisioning Profile: Embed the Distribution Provisioning Profile inside the app bundle at
XXX.app/Contents/and rename it toembedded.provisionprofile.
Verification
Verify the signature details using codesign:
% codesign -d -vv /Applications/MyGame.app/Contents/PlugIns/unitypurchasing.bundle
Executable=/Applications/MyGame.app/Contents/PlugIns/unitypurchasing.bundle/Contents/MacOS/unitypurchasing
Identifier=com.unity.purchasing.unitypurchasing
Format=bundle with Mach-O universal (x86_64 arm64)
...
Signing Specific Components
Use the following syntax to sign individual binaries:
% codesign -s <Identity> <FilePath>
Example workflow:
# Signing native libraries (Do not use --entitlements here)
% codesign -f -s '3rd Party Mac Developer Application: Company Name' "/Builds/MyGame.app/Contents/Frameworks/UnityPlayer.dylib"
# Signing the main application with entitlements
% codesign -f -o runtime -s '3rd Party Mac Developer Application: Company Name' \
--entitlements "/Builds/Game.entitlements" "/Builds/MyGame.app"
Critical Notes:
- No Entitlements on Libraries: Do not apply
.entitlementsto libraries (.dylibor.bundle) as it will cause the app to crash on launch. - No Sudo: Do not run
codesignwithsudo. It relies on user-specific keychain access. - Avoid
--deep: This flag applies the same entitlements to all nested code. If your app has helper tools requiring different permissions,--deepwill break the signature.
Distribution Formats
Depending on the distribution method, choose the appropriate format:
- Mac App Store: Submit via Xcode or Transporter (usually
.pkgor direct.app). - Out side Mac App Store:
- Zip Archive: Ensure the app is signed before zipping.
- DMG: Standard disk image.
- Installer Package (.pkg):
- App Store distribution: Sign with
3rd Party Mac Developer Installer: <TeamID>. - Outside App Store: Sign with
Developer ID Installer: <TeamID>.
- App Store distribution: Sign with
Implementation Steps
If uploading directly via Xcode, manual signing is handled automatically.
1. Configure Info.plist
Modify the Info.plist inside the Unity-generated .app bundle:
- Disable Encryption Export Prompt:
<key>ITSAppUsesNonExemptEncryption</key> <false/> - Verify Package Type: Ensure it is set to
APPLfor applications.<key>CFBundlePackageType</key> <string>APPL</string> - Set Category:
<key>LSApplicationCategoryType</key> <string>public.app-category.games</string> - Minimum OS Version:
<key>LSMinimumSystemVersion</key> <string>13.0.0</string>
2. Prepare Entitlements File
Generate an .entitlements file (you can use a dummy Xcode project to generate the template by enabling App Sandbox).
Add your Team ID and App ID:
<key>com.apple.developer.team-identifier</key>
<string>TEAMID12345</string>
<key>com.apple.application-identifier</key>
<string>TEAMID12345.com.mycompany.mygame</string>
Verification: Confirm sandboxing is enabled via command line:
% codesign -dvvv --entitlements - /Applications/MyGame.app
3. Embed Provisioning Profile
Place your embedded.provisionprofile file inside MyGame.app/Contents/.
4. Execution Scripts
Run the following commands in sequence:
# 1. Sign Plugins (Bundles)
$ codesign -f -s '3rd Party Mac Developer Application: Company' "MyGame.app/Contents/PlugIns/unitypurchasing.bundle"
# 2. Sign Frameworks (Libraries)
$ codesign -f -s '3rd Party Mac Developer Application: Company' "MyGame.app/Contents/Frameworks/libmonobdwgc-2.0.dylib"
# 3. Sign Main App
$ codesign -f -s '3rd Party Mac Developer Application: Company' \
--entitlements "/Builds/Game.entitlements" "/Builds/MyGame.app"
# 4. Build and Sign Installer Package for App Store
$ productbuild --component /Builds/MyGame.app /Applications \
--sign "3rd Party Mac Developer Installer: Company" /Builds/MyGame.pkg
Troubleshooting
Error 90869: Invalid bundle (Missing x86_64)
- Cause: The app supports arm64 only, but the deployment target is set below 12.0.
- Fix: Update
LSMinimumSystemVersioninInfo.plistto 12.0 or higher (e.g., 13.0).
Error 90236: Misssing required icon
- Cause: The App Icon (
icns) is missing or invalid. - Fix: Ensure the
Contents/Resourcesfolder contains a validAppIcon.icnswith 512x512 and 512x512@2x sizes.
Error ITMS-90889: Missing provisioning profile
- Cause: The
.appbundle does not contain theembedded.provisionprofile. - Fix:
- Ensure
com.apple.developer.team-identifieris in the.entitlementsfile. - Copy the provisioning profile to
MyGame.app/Contents/embedded.provisionprofile.
- Ensure