Sign, notarize, and staple a macOS app¶
To distribute a macOS app outside the App Store so that it opens without a
Gatekeeper warning, you sign it with a Developer ID certificate, notarize it with
Apple, and staple the notarization ticket to it. When you configure a Developer ID
identity and a notary profile, kivyforge package -p macos does all three.
Before you begin¶
- A paid Apple Developer Program membership. A free Apple ID cannot create Developer ID certificates.
- A Developer ID Application certificate and its private key in a keychain.
- The Xcode command-line tools, which provide
codesignandxcrun notarytool. - An app that builds with
kivyforge package -p macos.
Sign and notarize the app¶
-
Store your notary credentials in the keychain as a named profile. Use an app-specific password, not your Apple ID password:
xcrun notarytool store-credentials PROFILE_NAME --apple-id APPLE_ID --team-id TEAM_ID --password APP_SPECIFIC_PASSWORDReplace
PROFILE_NAMEwith a name for the profile, such askivyforge-notary;APPLE_IDwith your Apple ID email address;TEAM_IDwith your Team ID; andAPP_SPECIFIC_PASSWORDwith an app-specific password generated for your Apple ID. -
Add the signing table to your
pyproject.toml:[tool.kivy.macos.signing] identity = "Developer ID Application: NAME (TEAM_ID)" team_id = "TEAM_ID" notary_profile = "PROFILE_NAME"Replace
NAME (TEAM_ID)with the name of your certificate as it appears in Keychain Access, andPROFILE_NAMEwith the profile from the previous step.identityalone gives a Developer ID signature without notarization.identityandnotary_profiletogether give sign, notarize, and staple.
-
Check the setup:
kivyforge doctor -p macosdoctorchecks thatcodesignis available, that exactly one keychain certificate matchesidentity, that the identity is a Developer ID type, and thatnotarytoolis installed. It checks the notary profile's credentials only when you submit. -
Package the app:
kivyforge package -p macospackagesigns every Mach-O binary in the bundle from the inside out, submits the app to Apple's notary service, waits for the result, and staples the ticket to the.app. Notarization needs outbound network access and can take several minutes.
To override the configuration for one run, pass --signing-identity or
--notary-profile. To sign without notarizing even when a profile is
configured, pass --no-notarize.
Verify¶
package ends with a line such as
Packaged build/macos/APP_NAME.app (Developer ID notarized + stapled). To confirm
with Apple's own tools that Gatekeeper accepts the app and that the ticket is
stapled:
spctl --assess --type execute --verbose "build/macos/APP_NAME.app"
xcrun stapler validate "build/macos/APP_NAME.app"
Replace APP_NAME with your app's display name.
To check the signature itself, independently of Gatekeeper:
codesign --verify --deep --strict --verbose=2 "build/macos/APP_NAME.app"
What gets signed¶
kivyforge signs every Mach-O binary inside the .app: the launcher, the bundled
Python runtime, every wheel extension module, and any
native binaries you declare. Each
signature uses the Hardened Runtime and a secure timestamp, which notarization
requires.
The app's signature carries these entitlements, merged with your
[tool.kivy.macos.entitlements] table. Your values win.
| Entitlement | Default | Why |
|---|---|---|
com.apple.security.cs.allow-unsigned-executable-memory |
true |
ctypes and cffi allocate writable, executable memory. |
com.apple.security.cs.disable-library-validation |
true |
The app loads wheel binaries signed by other teams. |
kivyforge rejects com.apple.security.get-task-allow = true when an identity
is set, because notarization always rejects it.
Troubleshooting¶
errSecInternalComponent on every Mach-O¶
First check that the keychain holding your identity is unlocked. A locked keychain produces the same error, and it is the more common cause:
security unlock-keychain KEYCHAIN_PATH
Replace KEYCHAIN_PATH with the keychain, for example
~/Library/Keychains/login.keychain-db, then run package again.
If signing still fails on every binary with errSecInternalComponent, the
private key's access control list in your login keychain may be corrupted. Apple recommends keeping a Developer ID identity in its own keychain
for this reason, and kivyforge doctor warns when your identity is in
login.keychain-db.
To move signing to a dedicated keychain:
-
Create and unlock a new keychain, add it to the search list, and make it the default:
security create-keychain -p KEYCHAIN_PASSWORD signing.keychain-db security list-keychains -d user -s signing.keychain-db login.keychain-db security unlock-keychain -p KEYCHAIN_PASSWORD signing.keychain-db security set-keychain-settings signing.keychain-db security default-keychain -s signing.keychain-dbReplace
KEYCHAIN_PASSWORDwith a password for the new keychain. -
In Xcode, open Settings > Accounts > Manage Certificates and create a new Developer ID Application certificate. Xcode stores it in the default keychain, which is now
signing.keychain-db. -
Allow
codesignto use the key, then restore your login keychain as the default:security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k KEYCHAIN_PASSWORD signing.keychain-db security default-keychain -s login.keychain-db -
If
doctororcodesignreports that the identity is ambiguous, delete the old certificate fromlogin.keychain-dbin Keychain Access.
What's next¶
- Package a macOS app in a .dmg around the notarized app.
[tool.kivy.macos]reference.- Signing prerequisites for every platform.