How to sign and notarize Sidekicks for macOS
This guide shows how to sign Sidekicks with a Developer ID certificate and have Apple notarize it, so that it opens on any Mac without Open Anyway. It assumes a paid Apple Developer Program membership of which you are the Account Holder (only that role can create a Developer ID certificate), an Apple Silicon Mac with the project's development tools (Develop the application), and a release built as RELEASING.md describes.
- Part A is done once (allow 30 minutes).
- Part B is done for every release, after
make release-build, until signing is automated (issue #58). Allow 10 to 20 minutes, mostly waiting for Apple.
Keep a Terminal window open at the root of the repository.
What signing and notarizing do
| Step | Tool | What it brings |
|---|---|---|
| Sign | codesign with your Developer ID certificate |
macOS knows who made the app and that it was not changed |
| Hardened runtime | codesign --options runtime |
required by Apple to notarize |
| Notarize | xcrun notarytool |
Apple scans the app and issues a "ticket" |
| Staple | xcrun stapler |
the ticket is attached to the app and the .dmg: Gatekeeper accepts them even offline |
The app and the .dmg are signed, notarized and stapled. The .zip holds the stapled app.
Part A — once
A1. Note your Team ID
- Open developer.apple.com/account, Membership details.
- Note the Team ID (10 characters, e.g.
AB12CD34EF).
A2. Create the Developer ID Application certificate
With Xcode (the simplest):
- Open Xcode → Settings… → Accounts. If it is not there, click +, choose Apple ID and sign in with the email address and password of your Apple Developer account (the Apple ID, not the Team ID).
- Your team shows under the account, with its Team ID: select it, click Manage Certificates….
- Click + at the bottom left, then Developer ID Application.
Without Xcode:
- Open Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority…, give your address, tick Saved to disk. You get a
.certSigningRequestfile. - On developer.apple.com/account/resources/certificates, choose Developer ID Application, profile G2 Sub-CA, upload the request, then download the certificate.
- Double-click the
.cerfile: it goes to the login keychain.
A3. Check that the certificate can be used
security find-identity -v -p codesigning
A line "Developer ID Application: First Last (AB12CD34EF)" must show. That is your signing identity: copy it exactly, without the quotes.
If it is missing, or shows CSSMERR_TP_NOT_TRUSTED, install the Developer ID - G2 intermediate certificate from apple.com/certificateauthority, then run the command again.
A4. Back up the certificate and its key
The private key only exists on this Mac: without a backup, a failure means revoking the certificate and making a new one.
- In Keychain Access, login keychain, My Certificates, unfold Developer ID Application: … (the key shows under it).
- Select the certificate, File → Export Items…, format .p12, choose a password.
- Keep the
.p12and its password in your password manager. Never put it in the repository.
A5. Create an app-specific password for notarization
- Open account.apple.com, Sign-In and Security → App-Specific Passwords.
- Create one called
sidekicks-notaryand copy it (it is not shown again).
A6. Store the notarization profile in the keychain
xcrun notarytool store-credentials sidekicks-notary \
--apple-id "you@example.com" \
--team-id "AB12CD34EF"
Paste the app-specific password when asked. Check:
xcrun notarytool history --keychain-profile sidekicks-notary
A list (empty the first time) shows, with no authentication error.
A7. Set the two variables
Add to your ~/.zshrc, then open a new Terminal:
export SIDEKICKS_SIGN_IDENTITY="Developer ID Application: First Last (AB12CD34EF)"
export SIDEKICKS_NOTARY_PROFILE="sidekicks-notary"
Part B uses them; the automation (issue #58) will too.
A8. Keep the app's identifier
CFBundleIdentifier is com.k33g.sidekicks in build/darwin/Info.plist (and Info.dev.plist). Do not change it any more: macOS ties the permissions given to the app to it.
Part B — for every release
First pause syncing if the repository is in a synced folder (kDrive, iCloud Drive, Dropbox…): it changes the files while codesign and stapler rewrite them, which breaks the signatures and gives "corrupted" .dmg files. Everything then happens in a working folder, bin/sign (ignored by git).
B1. Build the release
As usual (RELEASING.md, steps Before and 1):
make release-build # or SKIP=windows make release-build for a macOS-only release
dist/ holds the unsigned packages, and bin/sidekicks.app the ad-hoc signed app.
B2. Copy the app to the working folder
W="$PWD/bin/sign"
rm -rf "$W" && mkdir -p "$W"
ditto bin/sidekicks.app "$W/sidekicks.app"
Keep this Terminal open, at the root of the repository: the next steps use $W. If syncing cannot be paused, take W="$(mktemp -d /tmp/sidekicks-sign.XXXXXX)" instead.
B3. Sign the app
xattr -cr "$W/sidekicks.app"
codesign --force --options runtime --timestamp \
--sign "$SIDEKICKS_SIGN_IDENTITY" "$W/sidekicks.app"
codesign --verify --strict --verbose=2 "$W/sidekicks.app"
codesign -dv --verbose=4 "$W/sidekicks.app" 2>&1 | grep -E "Authority=Developer ID Application|flags=.*runtime|Timestamp="
The last command must show the Developer ID Application authority, the runtime flag and a Timestamp line.
B4. Notarize the app and staple the ticket
ditto -c -k --keepParent "$W/sidekicks.app" "$W/notarize.zip"
xcrun notarytool submit "$W/notarize.zip" --keychain-profile "$SIDEKICKS_NOTARY_PROFILE" --wait
xcrun stapler staple "$W/sidekicks.app"
spctl -a -vvv -t exec "$W/sidekicks.app"
notarytool waits for Apple's answer (a few minutes) and must end with status: Accepted. spctl must say accepted and source=Notarized Developer ID.
B5. Make the .dmg from the stapled app
wails3 tool package --format dmg --name sidekicks --out "$W" \
--background build/darwin/dmg-background.png \
--volume-icon build/darwin/icons.icns \
--file-icon build/darwin/dmg-file-icon.icns \
--window-width 540 --window-height 380
hdiutil verify "$W/sidekicks.dmg"
The command runs from the root of the repository (for the images) but reads the app and writes the .dmg in $W, with the same layout as make release-build. hdiutil verify must end with checksum … is VALID.
B6. Sign, notarize and staple the .dmg
codesign --force --timestamp --sign "$SIDEKICKS_SIGN_IDENTITY" "$W/sidekicks.dmg"
xcrun notarytool submit "$W/sidekicks.dmg" --keychain-profile "$SIDEKICKS_NOTARY_PROFILE" --wait
xcrun stapler staple "$W/sidekicks.dmg"
hdiutil verify "$W/sidekicks.dmg"
spctl -a -vvv -t open --context context:primary-signature "$W/sidekicks.dmg"
hdiutil attach -nobrowse -readonly "$W/sidekicks.dmg" && hdiutil detach "/Volumes/sidekicks"
spctl must say accepted and source=Notarized Developer ID; the .dmg must mount then unmount without error. If the volume is not called sidekicks, detach the one hdiutil attach printed.
B7. Replace the macOS packages of dist/ and compute the checksums again
names=($(scripts/package.sh names macos arm64)) # the .dmg, then the .zip
cp "$W/sidekicks.dmg" "dist/${names[0]}"
rm -f "dist/${names[1]}" && ditto -c -k --keepParent "$W/sidekicks.app" "dist/${names[1]}"
cmp "$W/sidekicks.dmg" "dist/${names[0]}" && echo "identical copy"
spctl -a -vvv -t open --context context:primary-signature "dist/${names[0]}" # accepted · Notarized Developer ID
(cd dist && rm -f SHA256SUMS && shasum -a 256 -- * > "$W/SHA256SUMS") && cp "$W/SHA256SUMS" dist/SHA256SUMS
cat dist/SHA256SUMS
Do not go on if cmp reports a difference or spctl refuses: dist/ would still hold the unsigned .dmg of B1, and so would the release.
B8. Try it on another Mac
- Copy the
.dmgto another Mac (or another user account), through the web or AirDrop: it must carry the quarantine attribute of a real download. - Open it, drag sidekicks to Applications, launch it: it opens directly, without Open Anyway.
- Go through what the hardened runtime could hinder: a local agent, an agent in a container or in sbx, the Terminal tab, opening a file, drag and drop, Settings → Open….
B9. Tag and publish
Follow the rest of RELEASING.md (steps 2 to 4): the files to attach are those of dist/. Once the release is published, download its .dmg and check that shasum -a 256 gives the same sum as SHA256SUMS (a truncated upload also gives "corrupted image"). Then delete $W and resume syncing.
Variants
- A separate macOS release, on a version already published (e.g.
v1.0.0-macos, issue #57): do not changeinfo.version(the app stays1.0.0; Windows refuses a version that is not made of numbers, Apple too forCFBundleShortVersionString, and the packages would be called…-1.0.0-macos-macos-…). Build macOS only withSKIP=windows make release-build, do part B, then create the tag by hand:git tag -a v1.0.0-macos -m "Sidekicks 1.0.0, macOS signed and notarized" && git push origin v1.0.0-macos. The Release workflow starts and stops at its check (the tag is notv+ the version ofbuild/config.yml): this is expected. Create the release on Rickub from that tag and attach the.dmg, the.zipandSHA256SUMS. You can also simply replace the macOS files of thev1.0.0release. - Notarization is refused (
status: Invalid):xcrun notarytool log <id> --keychain-profile "$SIDEKICKS_NOTARY_PROFILE"gives the reason. The usual ones: a signature without--options runtimeor without--timestamp, or an unsigned file in the app. - "The disk image is corrupted" when opening the
.dmg: it was changed after it was signed (synced folder) or truncated (upload, download).hdiutil verifyon the file andshasum -a 256compared withSHA256SUMStell which. Do B2 to B7 again with syncing paused (or with$Win/tmp). codesignrefuses ("resource fork, Finder information, or similar detritus"): the app comes from a synced folder, which adds metadata.xattr -cr "$W/sidekicks.app"and do B3 again.- The signed app does not start, or a feature no longer works: the hardened runtime blocks something. Read Console.app at launch; if one entitlement is missing, add an entitlements file (
--entitlements build/darwin/entitlements.plistin B2) with that entitlement only, and note it in issue #57. - Check an installed app:
spctl -a -vvv /Applications/sidekicks.appandxcrun stapler validate /Applications/sidekicks.app. - Another Mac to build on: import the
.p12(double-click), then do A6 and A7 again. - The
.dmgto make again, the app already notarized: if$W/sidekicks.appis still there andxcrun stapler validate "$W/sidekicks.app"succeeds, go straight to B5.
See also
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
|