Skip to main content
Version: Next Version 🚧

Code Signing

This is a guide on how you can sign your binaries generated with Wails on MacOS and Windows. The guide will target CI environments, more specifically GitHub Actions.

Windows

First off you need a code signing certificate. If you do not already have one, Microsoft's info page lists some providers here. Please note that an EV certificate is not required unless you need to write kernel-level software such as device drivers. For signing your Wails app, a standard code signing certificate will do just fine.

It may be a good idea to check with your certificate provider how to sign your binaries on your local machine before targeting automated build systems, just so you know if there are any special requirements. For instance, here is SSL.com's code signing guide for Windows. If you know how to sign locally, it will be easier to troubleshoot any potential issues in a CI environment. For instance, SSL.com code signing certificates require the /tr flag for SignTool.exe while other providers may only need the /t flag for providing the timestamping server. Popular GitHub Actions for signing Windows binaries like this one does not support the /tr flag on SignTool.exe. Therefore this guide will focus on signing our app manually with PowerShell commands, but you can use actions like the code-sign-action Action if you prefer.

First off, let's make sure we are able to build our Wails app in our GitHub CI. Here is a small workflow template:

name: "example"
on:
workflow_dispatch:
# This Action only starts when you go to Actions and manually run the workflow.

jobs:
package:
strategy:
matrix:
platform: [windows-latest, macos-latest]
go-version: [1.18]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: setup node
uses: actions/setup-node@v2
with:
node-version: 14
# You may need to manually build you frontend manually here, unless you have configured frontend build and install commands in wails.json.
- name: Get Wails
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- name: Build Wails app
run: |
wails build
- name: upload artifacts macOS
if: matrix.platform == 'macos-latest'
uses: actions/upload-artifact@v2
with:
name: wails-binaries-macos
path: build/bin/*
- name: upload artifacts windows
if: matrix.platform == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: wails-binaries-windows
path: build/bin/*

Next we need to give the GitHub workflow access to our signing certificate. This is done by encoding your .pfx or .p12 certificate into a base64 string. To do this in PowerShell, you can use the following command assuming your certificate is called 'my-cert.p12':

certutil -encode .\my-cert.p12 my-cert-base64.txt

You should now have your .txt file with the base64 encoded certificate. It should start with -----BEGIN CERTIFICATE----- and end with -----END CERTIFICATE-----. Now you need to make two action secrets on GitHub. Navigate to Settings -> Secrets -> Actions and create the two following secrets:

  • WIN_SIGNING_CERT with the contents of your base64 encoded certificate text.
  • WIN_SIGNING_CERT_PASSWORD with the contents of your certificate password.

Now we're ready to implement the signing in our workflow using one of the two methods:

Method 1: signing with commands

This method uses PowerShell commands to sign our app, and leaves you control over the entire signing process.

After the "Build Wails app" step, we can add the following step to our workflow:

- name: Sign Windows binaries
if: matrix.platform == 'windows-latest'
run: |
echo "Creating certificate file"
New-Item -ItemType directory -Path certificate
Set-Content -Path certificate\certificate.txt -Value '${{ secrets.WIN_SIGNING_CERT }}'
certutil -decode certificate\certificate.txt certificate\certificate.pfx
echo "Signing our binaries"
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe' sign /fd <signing algorithm> /t <timestamping server> /f certificate\certificate.pfx /p '${{ secrets.WIN_SIGNING_CERT_PASSWORD }}' <path to binary>

This script creates a new directory for your certificate file, creates the certificate file from our base64 secret, converts it to a .pfx file, and finally signs the binary. The following variables needs to be replaced in the last line:

  • signing algorithm: usually sha256.
  • timestamping server: URL to the timestamping server to use with your certificate.
  • path to binary: path to the binary you want to sign.

Given that our Wails config has outputfilename set to "app.exe" and that we have a certificate from SSL.com, this would be our workflow:

name: "example"
on:
workflow_dispatch:
# This Action only starts when you go to Actions and manually run the workflow.

jobs:
package:
strategy:
matrix:
platform: [windows-latest, macos-latest]
go-version: [1.18]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: setup node
uses: actions/setup-node@v2
with:
node-version: 14
# You may need to manually build you frontend here, unless you have configured frontend build and install commands in wails.json.
- name: Get Wails
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- name: Build Wails app
run: |
wails build
- name: Sign Windows binaries
if: matrix.platform == 'windows-latest'
run: |
echo "Creating certificate file"
New-Item -ItemType directory -Path certificate
Set-Content -Path certificate\certificate.txt -Value '${{ secrets.WIN_SIGNING_CERT }}'
certutil -decode certificate\certificate.txt certificate\certificate.pfx
echo "Signing our binaries"
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe' sign /fd sha256 /tr http://ts.ssl.com /f certificate\certificate.pfx /p '${{ secrets.WIN_SIGNING_CERT_PASSWORD }}' .\build\bin\app.exe

- name: upload artifacts macOS
if: matrix.platform == 'macos-latest'
uses: actions/upload-artifact@v2
with:
name: wails-binaries-macos
path: build/bin/*
- name: upload artifacts windows
if: matrix.platform == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: wails-binaries-windows
path: build/bin/*

Method 2: automatically signing with Action

It is possible to use a Windows code signing Action like this one, but note it requires a SHA1 hash for the certificate and a certificate name. View an example of how to configure it on the Action's marketplace.


MacOS

First off you need your code signing certificate from Apple. If you do not have one, a simple Google search will help you acquire one. Once you have your certificate, you need to export it and encode it to base64. This tutorial shows you how to do that in an easy manner. Once you have exported your .p12 certificate file, you can encode it to base64 as seen in the tutorial with the following command:

base64 Certificates.p12 | pbcopy

Now you're ready to create some GitHub project secrets, just as with Windows:

  • APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 with the contents of your newly copied base64 certificate.
  • APPLE_DEVELOPER_CERTIFICATE_PASSWORD with the contents of your certificate password.
  • APPLE_PASSWORD with the contents of an App-Specific password to your Apple-ID account which you can generate here.

Let's make sure we are able to build our Wails app in our GitHub Action workflow. Here is a small template:

name: "example"
on:
workflow_dispatch:
# This Action only starts when you go to Actions and manually run the workflow.

jobs:
package:
strategy:
matrix:
platform: [windows-latest, macos-latest]
go-version: [1.18]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: setup node
uses: actions/setup-node@v2
with:
node-version: 14
# You may need to manually build you frontend here, unless you have configured frontend build and install commands in wails.json.
- name: Get Wails
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- name: Build Wails app
run: |
wails build
- name: upload artifacts macOS
if: matrix.platform == 'macos-latest'
uses: actions/upload-artifact@v2
with:
name: wails-binaries-macos
path: build/bin/*
- name: upload artifacts windows
if: matrix.platform == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: wails-binaries-windows
path: build/bin/*

For code signing on macOS, gon is a very handy tool for code signing and communicating with Apple servers, also written in Go, and will be used in this guide.

After the Build Wails app step, add the following to the workflow:

- name: MacOS download gon for code signing and app notarization
if: matrix.platform == 'macos-latest'
run: |
brew install Bearer/tap/gon

Now we need to configure some gon config files in our build/darwin directory:

  1. gon-sign.json:
{
"source": ["./build/bin/app.app"],
"bundle_id": "app.myapp",
"apple_id": {
"username": "[email protected]",
"password": "your-app-specific-password",
"provider": "ABCDE12345"
},
"sign": {
"application_identity": "Developer ID Application: Human User"
}
}

Here is a brief break down of the above fields:

  • source: The location of your wails binary to be signed
  • apple_id:
    • username: Your Apple ID email address
    • password: Your app-specific password
    • provider: Your team ID for your App Store Connect account
  • sign:
    • application_identity: Your Apple developer identity

The (https://developer.apple.com/documentation/technotes/tn3147-migrating-to-the-latest-notarization-tool)[deprecated Apple's altool]'s syntax supporting @env: is no longer available since Apple has migrated to the new notarytool.

Your developer identity and team ID can both by found on macOS by running the following command:

$ security find-identity -v -p codesigning
1) 00000000000000000000000000000000000000000 "Developer ID Application: Human User (ABCDE12345)"
  1. entitlements.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
</dict>
</plist>

In this file you configure the entitlements you need for you app, e.g. camera permissions if your app uses the camera. Read more about entitlements here.

Make sure you have updated your Info.plist file with the same bundle ID as you entered in gon-sign.json. Here's an example Info.plist file:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleName</key><string>MyApp</string>
<key>CFBundleExecutable</key><string>app</string>
<key>CFBundleIdentifier</key><string>app.myapp</string>
<key>CFBundleVersion</key><string>0.1.0</string>
<key>CFBundleGetInfoString</key><string>My app is cool and nice and chill and</string>
<key>CFBundleShortVersionString</key><string>0.1.0</string>
<key>CFBundleIconFile</key><string>iconfile</string>
<key>LSMinimumSystemVersion</key><string>10.13.0</string>
<key>NSHighResolutionCapable</key><string>true</string>
<key>LSApplicationCategoryType</key><string>public.app-category.utilities</string>
<key>NSHumanReadableCopyright</key><string>© Me</string>
</dict></plist>

Now we're ready to add the signing step in our workflow after building the Wails app:

- name: Import Code-Signing Certificates for macOS
if: matrix.platform == 'macos-latest'
uses: Apple-Actions/import-codesign-certs@v1
with:
# The certificates in a PKCS12 file encoded as a base64 string
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
# The password used to import the PKCS12 file.
p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
- name: Sign our macOS binary
if: matrix.platform == 'macos-latest'
run: |
echo "Signing Package"
gon -log-level=info ./build/darwin/gon-sign.json

Please note that signing binaries with Apple could take anywhere from minutes to hours.

Combined workflow file:

Here is our GitHub workflow file with Windows + macOS combined:

name: "example combined"
on:
workflow_dispatch:
# This Action only starts when you go to Actions and manually run the workflow.

jobs:
package:
strategy:
matrix:
platform: [windows-latest, macos-latest]
go-version: [1.18]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: setup node
uses: actions/setup-node@v2
with:
node-version: 14
# You may need to manually build you frontend here, unless you have configured frontend build and install commands in wails.json.
- name: Get Wails
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- name: Build Wails app
run: |
wails build
- name: MacOS download gon for code signing and app notarization
if: matrix.platform == 'macos-latest'
run: |
brew install Bearer/tap/gon
- name: Import Code-Signing Certificates for macOS
if: matrix.platform == 'macos-latest'
uses: Apple-Actions/import-codesign-certs@v1
with:
# The certificates in a PKCS12 file encoded as a base64 string
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
# The password used to import the PKCS12 file.
p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
- name: Sign our macOS binary
if: matrix.platform == 'macos-latest'
run: |
echo "Signing Package"
gon -log-level=info ./build/darwin/gon-sign.json
- name: Sign Windows binaries
if: matrix.platform == 'windows-latest'
run: |
echo "Creating certificate file"
New-Item -ItemType directory -Path certificate
Set-Content -Path certificate\certificate.txt -Value '${{ secrets.WIN_SIGNING_CERT }}'
certutil -decode certificate\certificate.txt certificate\certificate.pfx
echo "Signing our binaries"
& 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe' sign /fd sha256 /tr http://ts.ssl.com /f certificate\certificate.pfx /p '${{ secrets.WIN_SIGNING_CERT_PASSWORD }}' .\build\bin\Monitor.exe
- name: upload artifacts macOS
if: matrix.platform == 'macos-latest'
uses: actions/upload-artifact@v2
with:
name: wails-binaries-macos
path: build/bin/*
- name: upload artifacts windows
if: matrix.platform == 'windows-latest'
uses: actions/upload-artifact@v2
with:
name: wails-binaries-windows
path: build/bin/*

End notes

This guide inspired by the RiftShare project and its workflow, which is highly recommended to check out here.