# 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](https://docs.microsoft.com/en-us/windows-hardware/drivers/dashboard/get-a-code-signing-certificate). 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](https://www.ssl.com/how-to/using-your-code-signing-certificate/) 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](https://docs.microsoft.com/en-us/windows/win32/seccrypto/signtool) while other providers may only need the `/t` flag for providing the timestamping server. Popular GitHub Actions for signing Windows binaries like [this one](https://github.com/Dana-Prajea/code-sign-action) 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](https://github.com/Dana-Prajea/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:

```yaml
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':

```PowerShell
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:

```yaml
- 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:

```yaml
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](https://github.com/marketplace/actions/code-sign-a-file-with-pfx-certificate) 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](https://github.com/marketplace/actions/code-sign-a-file-with-pfx-certificate).

---

## 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](https://localazy.com/blog/how-to-automatically-sign-macos-apps-using-github-actions) 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:

```bash
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](https://appleid.apple.com/account/manage).

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

```yaml
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](https://github.com/mitchellh/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:

```yaml
- 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:

1. gon-sign.json:

```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:

```bash
security find-identity -v -p codesigning
```

2. entitlements.plist:

```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](https://developer.apple.com/documentation/bundleresources/entitlements).

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:

```plist
<!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:

```yaml
- 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:

```yaml
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 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](https://github.com/achhabra2/riftshare/blob/main/.github/workflows/build.yaml).
