Notarize macOS applications¶
GoReleaser has two ways to do notarization for macOS:
- Cross-platform using anchore/quill;
- Native using
codesign
andxcrun
(only on macOS);
The first can be used with binaries/universal binaries only. Note that putting a signed and notarized binary inside a non-notarized .app
does not work!
The second is the recommended way if you need to ship App Bundles inside DMGs.
Getting the keys¶
To use these features, you'll need:
- An Apple Developer Account ($99/year).
- A certificate from said account. It should be of "Developer ID Application" type. This will give you a
.cer
file. You'll need to import it intoKeyChain.app
, and then export it as a.p12
file. It'll will have a password. - An App Store Connect API key. This will give you a
.p8
file.
So you should end up with:
- a
Certificates.p12
file and the password to open it - a
ApiKey_AAABBBCCC.p8
file
If you plan to use them in GitHub Actions (or another CI), you'll need to base64
encode them as well.
base64 encoding
To base64 encode a file, you run this:
base64 -w0 < ./Certificates.p12
base64 -w0 < ./ApiKey_AAABBBCCC.p8
Cross-platform¶
If you only need to sign and notarize your binaries, this is probably the best alternative.
It has no external dependencies, and works on any operating system.
Do not use with App Bundles
Do not use this method if you create App Bundles. App Bundles in which only the binary is signed/notarized are deemed damaged by macOS. In that case, use the native signing and notarizing documented below.
Read the commented configuration excerpt below to learn how to use do it.
notarize:
macos:
- # Whether this configuration is enabled or not.
#
# Default: false.
# Templates: allowed.
enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}'
# IDs to use to filter the built binaries.
#
# Default: the project name.
ids:
- build1
- build2
# Before notarizing, we need to sign the binary.
# This block defines the configuration for doing so.
sign:
# The .p12 certificate file path or its base64'd contents.
#
# Templates: allowed.
certificate: "{{.Env.MACOS_SIGN_P12}}"
# The password to be used to open the certificate.
#
# Templates: allowed.
password: "{{.Env.MACOS_SIGN_PASSWORD}}"
# Allows to set the signature entitlements XML file.
#
# Templates: allowed.
# Since: v2.6.
entitlements: ./path/to/entitlements.xml
# Then, we notarize the binaries.
#
# You can leave this section empty if you only want
# to sign the binaries (Since: v2.1).
notarize:
# The issuer ID.
# Its the UUID you see when creating the App Store Connect key.
#
# Templates: allowed.
issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}"
# Key ID.
# You can see it in the list of App Store Connect Keys.
# It will also be in the ApiKey filename.
#
# Templates: allowed.
key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}"
# The .p8 key file path or its base64'd contents.
#
# Templates: allowed.
key: "{{.Env.MACOS_NOTARY_KEY}}"
# Whether to wait for the notarization to finish.
# Not recommended, as it could take a really long time.
wait: true
# Timeout for the notarization.
# Beware of the overall `--timeout` time.
# This only has any effect if `wait` is true.
#
# Default: 10m.
timeout: 20m
Template Language
Discover more about the name template engine.
GitHub Actions¶
In this case, signing and notarizing inside GitHub Actions is just a matter of adding the environment variables to the goreleaser-action
setup.
release.yml
name: goreleaser
# ...
jobs:
goreleaser:
runs-on: ubuntu-latest
env:
# The base64 of the contents of your '.p12' key.
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
# The password to open the '.p12' key.
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
# The base64 of the contents of your '.p8' key.
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
# The ID of the '.p8' key.
# You can find it in the filename, as well as the Apple Developer Portal
# website.
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
# The issuer UUID.
# You can find it in the Apple Developer Portal website.
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
steps:
# ...
- uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: "~> v2"
args: release --clean
Native¶
Since v2.8.
GoReleaser Pro
One or more features are exclusively available with GoReleaser Pro.
This method can sign and notarize App Bundles, but it depends on xcrun
and codesign
.
For now, it'll only works if you package your app as a DMG, but we might add more support in future releases.
See the configuration options below.
notarize:
macos_native:
- # Whether this configuration is enabled or not.
#
# Default: false.
# Templates: allowed.
enabled: "true"
# IDs to use to filter the built binaries.
#
# Default: the project name.
ids:
- build1
- build2
# Before notarizing, we need to sign the app bundle.
# This block defines the configuration for doing so.
sign:
# The path to the Keychain, if needed.
#
# Templates: allowed.
keychain: "{{ .Env.KEYCHAIN_PATH }}"
# The identity in Keychain.
#
# Templates: allowed.
identity: "Developer ID Application: Carlos Becker"
# Options to pass to 'codesign'.
# You will generally want to add 'runtime' here.
options: [runtime]
# Allows to set the signature entitlements XML file.
#
# Templates: allowed.
entitlements: ./path/to/entitlements.xml
# Then, we notarize the DMGs.
notarize:
# Profile name.
#
# Templates: allowed.
profile_name: "{{ .Env.MACOS_NOTARY_PROFILE_NAME }}"
# Whether to wait for the notarization to finish.
# Not recommended, as it could take a really long time.
wait: true
Template Language
Discover more about the name template engine.
Creating a profile
To use this, you'll need to create a profile with notarytool
. You can do so in your machine with:
xcrun notarytool store-credentials "$MACOS_NOTARY_PROFILE_NAME" \
--key "$KEY_PATH" \
--key-id "$MACOS_NOTARY_KEY_ID" \
--issuer "$MACOS_NOTARY_ISSUER_ID" \
--keychain $KEYCHAIN_PATH
GitHub Actions¶
This is only needed for native notarization.
Make sure to read the official GitHub Guide as well, but this is how we are doing it, in case you want to save some time.
You can also take a look at this live example.
release.yml
name: goreleaser
# ...
jobs:
goreleaser:
runs-on: macos-latest # only on macos
env:
# The base64 of the contents of your '.p12' key.
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
# The password to open the '.p12' key.
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
# A password for our temporary keychain
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
# The profile name to create and use for notarization.
MACOS_NOTARY_PROFILE_NAME: ${{ secrets.MACOS_NOTARY_PROFILE_NAME }}
# The base64 of the contents of your '.p8' key.
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
# The ID of the '.p8' key.
# You can find it in the filename, as well as the Apple Developer Portal
# website.
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
# The issuer UUID.
# You can find it in the Apple Developer Portal website.
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
steps:
# ...
- name: "setup-keychain"
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/goreleaser.p12
KEY_PATH=$RUNNER_TEMP/goreleaser.p8
KEYCHAIN_PATH=$RUNNER_TEMP/goreleaser.keychain-db
# import certificate and key from secrets
echo -n "$MACOS_SIGN_P12" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$MACOS_NOTARY_KEY" | base64 --decode -o $KEY_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$MACOS_SIGN_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# create notary profile
xcrun notarytool store-credentials "$MACOS_NOTARY_PROFILE_NAME" \
--key "$KEY_PATH" \
--key-id "$MACOS_NOTARY_KEY_ID" \
--issuer "$MACOS_NOTARY_ISSUER_ID" \
--keychain $KEYCHAIN_PATH
# export the keychain path
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >>$GITHUB_ENV
# ...
- uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: "~> v2"
args: release --clean
How it works¶
To make the behavior of this featur a bit clearer, this is the order in which the relevant steps are executed:
The cross-platform version uses quill under the hood. It is imported as a Go library and built into GoReleaser, so this just works.
graph LR
A[Create Binaries] --> B[Sign Binaries]
B --> C[Notarize Binaries]
Once the binaries are built, the notary step does everything in a single run. The signed binaries are then used from that point forward.
The native version runs codesign
and xcrun notarytool
. It only works on macOS and needs access to a Keychain.
graph LR
A[Create Binaries] --> B[Create App Bundles]
B --> C[Sign App Bundles]
C --> D[Create DMGs]
D --> E[Notarize DMGs]
Here things get a little bit more complicated. First, it only signs App Bundles, so they need to be created first. Once the App Bundle is signed, it needs to be packaged in a DMG. Finally, the DMG is notarized and used from that point on.