Wails in Practice #5 Signing and Notarization — How a Shipped App Earns Trust

4 min read

Through #4, the notes app is feature-complete. Now it is time to make it run on other people’s machines — and here many solo developers get stuck. An unsigned app makes macOS and Windows put up warnings and block it outright. This post covers the code signing and notarization that make a shipped app trusted.

This is the fifth of ten posts (two parts).

Why signing is needed #

Send the binary built in intro #6 straight to someone and the recipient’s OS steps in.

  • macOS Gatekeeper: refuses to open an app that is not signed and notarized, calling it from an “unidentified developer.” A user can force it with a right-click, but walking an ordinary user through that is a distribution failure.
  • Windows SmartScreen: shows the blue “Windows protected your PC” warning on an executable that is unsigned or has not built reputation.

Signing vouches for who made the app, and notarization is Apple’s confirmation that it scanned for malware. Pass both and the warnings disappear, with the added guarantee that the file was not tampered with after distribution.

macOS: sign, then notarize #

macOS distribution is two steps. First sign with an Apple Developer certificate, then upload to Apple’s servers for notarization. It presupposes a paid Apple Developer Program membership ($99/year).

1) code signing — with a Developer ID Application certificate
codesign --deep --force --options runtime \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  build/bin/Notes.app

--options runtime enables the hardened runtime, a prerequisite for notarization. Zip or dmg the signed app, submit it to Apple, and wait for notarization. The current tool is notarytool (the altool of older material is retired).

2) notarization — submit and staple with notarytool
# zip the app and submit (wait until finished)
xcrun notarytool submit Notes.zip \
  --apple-id "you@example.com" --team-id TEAMID \
  --password "app-specific-password" --wait

# attach the notarization result to the app so it verifies offline too
xcrun stapler staple build/bin/Notes.app

--wait blocks until notarization finishes. The final stapler staple embeds the notarization ticket into the app so it verifies even when the user runs it with no internet. Skip this step and offline users may see the warning again.

Note
In the past, tools like mitchellh/gon bundled signing and notarization, but gon is no longer maintained. The current standard is using codesign and notarytool directly as above. The signing guide in the official Wails docs follows this flow too.

Windows: the certificate type shapes the experience #

Windows code signing signs the .exe or installer with a certificate. Signing with signtool is simple in itself, but the certificate type shapes the user experience significantly.

CertificateRough costSmartScreen warning
OV (Organization Validation)tens to low hundreds of dollars a yearappears initially, disappears as downloads and reputation accrue
EV (Extended Validation)more expensive, requires HSM/tokentrusted immediately, no reputation wait

For individuals and small teams, starting with OV and waiting for reputation to build is realistic. If you must remove the warning immediately, EV is needed but the cost and hardware requirements are steep.

sign with signtool (timestamp included)
signtool sign /fd SHA256 /a `
  /tr http://timestamp.digicert.com /td SHA256 `
  build\bin\Notes.exe

Always include the /tr timestamp. With a timestamp, the signature stays valid even after the certificate later expires, because it is proven the certificate was valid at signing time.

Do not keep secrets locally #

Signing needs sensitive values — the certificate, passwords, an app-specific password. Put these in code or the repository and they leak as-is. When signing manually on your machine, keep them in environment variables or the OS keychain, and when handing off to CI in #6, store them encrypted in GitHub Secrets. The standard is to base64-encode the certificate file itself into a Secret and restore it in the workflow. Only with this separation in place is the next post’s automation safe.

Summary #

  • An unsigned app is blocked by warnings from macOS Gatekeeper and Windows SmartScreen. Signing vouches for the maker, and notarization is Apple’s malware-scan confirmation.
  • On macOS, sign with codesign (hardened runtime on), notarize with notarytool, and embed the ticket with stapler staple. This is the current flow, replacing the unmaintained gon.
  • On Windows, sign with signtool and always include a timestamp. OV loses the warning as reputation builds; EV is trusted immediately but costs more.
  • Do not keep certificates and passwords locally — put them in environment variables, the keychain, and GitHub Secrets. This separation is the premise for the next post’s CI automation.
  • Next post puts this signing and notarization into GitHub Actions so a single tag generates a three-platform release automatically.
X