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.
- 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/*
jobs:
package:
strategy:
matrix:
platform: [ windows-latest, macos-latest ]
go-version: [1.18]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- 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.
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. Now you need to make two action secrets on GitHub. It should start with -----BEGIN CERTIFICATE----- and end with -----END CERTIFICATE-----. 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@v2
- 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@v2
- 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.
jobs:
package:
strategy:
matrix:
platform: [ windows-latest, macos-latest ]
go-version: [1.18]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- 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.
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 mitchellh/gon/gon
Now we need to configure some gon config files in our build/darwin
directory:
- gon-sign.json:
{
"source" : ["./build/bin/app.app"],
"bundle_id" : "app.myapp",
"apple_id": {
"username": "my-appleid@email.com",
"password": "@env:APPLE_PASSWORD"
},
"sign" :{
"application_identity" : "Developer ID Application: My Name"
}
}
Where source
is your Wails binary, bundle_id
is your bundle ID, apple_id
contains your Apple ID username and App-Specific password which you created earlier, and sign.application_identity
is your identity which you can find by running the following command:
security find-identity -v -p codesigning
- 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@v2
- 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 mitchellh/gon/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.
- 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 mitchellh/gon/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.
End notes
This guide inspired by the RiftShare project and its workflow, which is highly recommended to check out here.