Notarize.app 1.0 #2
abra-code
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Notarize 1.0
First public release.
Notarize is a native macOS GUI for Apple's notarization workflow. Drop an app or an installer package on it, pick a credential profile, and click Notarize: it copies, signs, submits to Apple's notary service, waits for the verdict, staples the ticket, and runs the Gatekeeper assessment. Any single step can be run on its own. Notary credentials are set up once through a guided wizard and reused across every target.
Notarize bundles no binaries of its own. It drives the Apple toolchain (
codesign,productsign,pkgutil,xar,ditto,spctl,security,xcrun notarytool,xcrun stapler) and shows you what it ran.Requirements
notarytoolandstapler. Install withxcode-select --install, or install Xcode.A target that was already signed elsewhere can be notarized here without any certificate in your keychain.
What is in 1.0
Apps and installer packages, both handled correctly
Drop a
.appbundle or a flat.pkginstaller package. The two kinds take the same route through the window, and Notarize picks the right tool at every step that touches a signature or an upload:codesign, hardened runtime, entitlementsproductsigndittozipcodesign -dv,codesign --verifypkgutil --check-signaturespctl --type executespctl --type installThe two kinds keep separate default signing identities, because a Developer ID Application certificate and a Developer ID Installer certificate are not interchangeable.
The full pipeline in one click
The Notarize button runs, in order: copy to the output folder when one is set, sign, prepare the upload, submit with
notarytool submit --wait, fetch the notary log on failure, staple the ticket, and assess withspctl. The step rail shows each stage's live status, so a failed run shows exactly where it stopped. The log streams as it goes, and Stop cancels a run in progress.Entitlements survive re-signing
Signing replaces a signature rather than amending it, so re-signing an app with an empty entitlements field would strip every entitlement it had. A sandboxed app would come out unsandboxed, correctly signed, and accepted by the notary service, broken in a way nothing downstream flags.
Notarize reads the existing entitlements out of the signature and re-applies them, and logs how many it carried over. The same holds inside the bundle: nested frameworks, helper tools and XPC services are re-signed and each keeps its own entitlements. The entitlements field governs the outer bundle only.
When a
.entitlementsfile sits next to the app, the field is filled in automatically, so you can see what will be applied before anything is signed. What is in that field is exactly what gets used; nothing hidden is ever picked up from the surrounding folder.It does not re-sign what is already signed
Before signing, the pipeline compares the existing signature against the window: certificate, secure timestamp, hardened runtime, and entitlements. If everything matches, signing is skipped and the log says so. Anything that differs is named as the reason, and the target is signed.
The comparison covers nested code, not just the outer bundle. A valid outer signature says nothing about a helper signed ad-hoc or an unsigned
.dylibsitting inResources. Both passcodesign --verify --deep --strictand are then rejected by the notary service, so Notarize checks the same set it would sign.Running the Sign for Release step on its own always signs, regardless of that comparison.
You can decline signing
The signing identity picker ends with a Don't Code-sign row, which sends the target to the notary service exactly as it arrived. Re-signing is not always harmless: an app assembled by a build system this window cannot reproduce, with nested code carrying entitlements of its own or a designated requirement that has to survive, can come out of a re-sign subtly different from what was tested.
With no certificate of the required class in your keychain, the picker opens on Don't Code-sign and the pipeline checks the existing signature, carrying on if the notary service would accept it and stopping with an explanation if it would not. The log distinguishes the two cases: that you asked for it, or that there was no certificate to use.
The choice is remembered for that target alone and is never adopted as a global default.
Gatekeeper preflight
A notarized and stapled app can still be rejected by Gatekeeper. Stale ad-hoc-signed or unsigned files sitting in nested-code locations pass
codesign --verify --deep --strict, the notary service, andstapler validate, and are then rejected byspctlwith a barerejectedand no reason.Notarize refuses to submit a bundle in that state, and names the offending items, so you find out from a local directory walk rather than after an upload, a wait, a staple, and a rejection in the field. Gatekeeper rejections in the log explain themselves: which nested items are at fault, or a missing ticket, or a broken signature.
Credentials set up once
The Credentials button opens a guided wizard that creates and validates a notary keychain profile with
notarytool store-credentials. Three paths: an Apple ID with an app-specific password, an App Store Connect API key, or registering a profile you already created yourself. Each path links out to where the credentials come from. The wizard does not require an app to be loaded.An app-specific password is piped over stdin and never appears on a command line.
An existing profile is never replaced without asking
notarytool store-credentialstreats its profile name as one "to create or update". It overwrites a matching profile in place with no prompt, and what it replaces cannot be read back out of the keychain by anything, including this app.That is easy to walk into, because the wizard suggests a name and the suggestion survives backing out and choosing a different authentication method, so an API-key save can land on the name an Apple ID profile is already using. Notarize flags a name it already knows as you type, and confirms with a Replace or Cancel alert before writing over anything. Cancel is on the key Escape reaches; canceling leaves the stored credentials exactly as they were.
Profiles created outside this app are checked too. They cannot be listed, because
notarytoolkeeps them in Apple's data protection keychain wheresecurity find-generic-passwordcannot see them, so the wizard asksnotarytoolabout the one name you typed. A name it has never registered andnotarytoolreports as absent is treated as free; anything else, including an expired profile or an unreachable notary service, is treated as in use. Being wrong in that direction costs a confirmation you did not need. Being wrong the other way destroys credentials without asking.Profile names that cannot work, meaning empty, whitespace-only, containing a line break, or starting with a dash, are rejected up front instead of surfacing later as a cryptic failure.
Settings that follow the product
Per-target settings, the signing identity, entitlements, credential profile, and output folder, are remembered between runs. They are keyed by bundle identifier for an app and by package identifier for a package, so they follow the product rather than a file name carrying a version number. Signing identity and credential profile also have global defaults, with a per-target override only when your selection differs from the default.
Your development copy is left alone
When an output folder is set, the target is copied there before signing, so release signing never happens on your working copy.
Signing a package replaces it atomically:
productsignwrites a hidden neighbor that is renamed into place, so an interrupted run leaves either the old package or the new one, never a half-written file.One window per target
A target arrives one of two ways: dropped on the Notarize icon in the Finder, or opened through the Open panel. Either opens a new window, bound to that target for as long as the window is open. Opening a second target opens a second window.
Individual steps and diagnostics
Every pipeline stage in the step rail is also a button that runs just that stage: Sign for Release, Submit and Wait, Staple Ticket, Validate Result. Alongside them, Inspect Signature reports signature, nested-code, staple and Gatekeeper status; Fetch Log retrieves the notary service log for the last submission; Reveal in Finder shows the notarized result.
Known limitations
.pkg, which is a folder, is rejected with an explanation. The notary service does not accept that form either. Flat packages are whatproductbuildproduces..mpkgmust be dropped on the icon. The Open panel filters on the flat-package content type, which.mpkgdoes not claim.ditto -c -k --keepParentwithout--sequesterRsrcstores extended attributes as literal._AppleDouble files inside the bundle, which breaks the signature for anyone who unzips with the Finder, so Notarize does not do it for you.codesignbehaves exactly as the fakes model it.Architecture
Notarize is an OMC 5.1 applet. The OMC framework handles the app lifecycle, the per-target document window, file dialogs, and the credential sheet. The interface is defined declaratively in ActionUI JSON. All business logic runs as POSIX shell scripts in
Contents/Resources/Scripts/, with shared functions inlib.notarize.shand command routing declared inContents/Resources/Command.json.Everything that differs between an app and a package is decided by one function,
target_kind_of, which returnsapporpkgfor a path; the handlers branch on that rather than each guessing from the extension.Signing delegates to a bundled copy of OMC's canonical
codesign_applet.sh, kept byte-identical to the copy at the repository root.License
Apache License 2.0. See LICENSE.
Built with OMC, a low-code app builder for macOS, and ActionUI, a JSON to SwiftUI interface builder.
And yes, you can notarize Notarize with itself.
This discussion was created from the release Notarize.app 1.0.
All reactions