Skip to content

Commit

Permalink
feat: sign Windows binary
Browse files Browse the repository at this point in the history
  • Loading branch information
agateau-gg committed Jun 13, 2024
1 parent 1a4df83 commit 4753f7d
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 12 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/build_release_assets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,43 @@ jobs:
MACOS_P12_PASSWORD: ${{ secrets.MACOS_P12_PASSWORD }}
MACOS_API_KEY_FILE: ${{ secrets.MACOS_API_KEY_FILE }}

- name: Setup Windows environment
if: startsWith(matrix.os, 'windows-') && inputs.release_mode
shell: bash
run: |
signtool_install_dir="/c/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64"
smctl_install_dir="/c/Program Files/DigiCert/DigiCert Keylocker Tools"
# Add signtool dir to $PATH
if [ ! -x "$signtool_install_dir/signtool.exe" ] ; then
echo "signtool.exe is not in '$signtool_install_dir'"
exit 1
fi
echo "$signtool_install_dir" >> $GITHUB_PATH
# Add smctl dir to $PATH
# Don't test if smctl is there: it is installed by the next step
echo "$smctl_install_dir" >> $GITHUB_PATH
# Create our certificate file
cert_file="$TMPDIR/cert.p12"
echo "${{ secrets.SM_CLIENT_CERT_FILE }}" | base64 --decode > "$cert_file"
# Add secrets to env
cat >> $GITHUB_ENV <<EOF
WINDOWS_CERT_FINGERPRINT=${{ secrets.WINDOWS_CERT_FINGERPRINT }}
SM_API_KEY=${{ secrets.SM_API_KEY }}
SM_HOST=${{ secrets.SM_HOST }}
SM_CLIENT_CERT_FILE=$cert_file
SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}
EOF
- name: Install Windows dependencies
if: startsWith(matrix.os, 'windows-') && inputs.release_mode
shell: bash
run: |
scripts/build-os-packages/install-keylockertools
- name: Build
shell: bash
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,4 @@ test-secret-files.json

# cache
.cache_ggshield
*.msi
48 changes: 36 additions & 12 deletions doc/dev/os-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ flowchart TD

We use [PyInstaller](https://pyinstaller.org) to generate `ggshield` standalone executable.

## Signing

MacOS and Windows archives are signed, but `build-os-packages` must be called with the `--sign` option to sign binaries because: signing requires access to secrets not available for PR from forks.

## macOS-specific information

For macOS, we produce a .pkg archive. The advantage of this file is that it can be installed by double-clicking on it or by using `sudo installer -pkg path/to/ggshield.pkg -target /`, and `ggshield` is immediately usable after install, without the need to alter `$PATH`.
Expand All @@ -40,18 +44,9 @@ For macOS, we produce a .pkg archive. The advantage of this file is that it can

The .pkg itself installs `ggshield` files in `/opt/gitguardian/ggshield-$version` and a `ggshield` symbolic link in `/usr/local/bin/ggshield`.

### Signing & notarizing

The .pkg archive used for releases is signed. Signing the archive is required to ensure macOS Gatekeeper security system does not block `ggshield` when users try to run it.

#### Setting up signing

`build-standalone-exe` won't sign binaries unless it's called with `--sign`. This is because:
### Setting up signing

- signing requires access to secrets not available for PR from forks.
- signing (and especially notarizing) can take a long time.

When called with `--sign`, `build-standalone-exe` expects the following environment variables to be set:
When called with `--sign`, `build-os-packages` expects the following environment variables to be set:

- `$MACOS_P12_FILE`: Path to a signing certificate. You can export one from Xcode by following [Apple documentation][apple-signing-certificate].
- `$MACOS_P12_PASSWORD_FILE`: Path containing the password protecting the signing certificate. Xcode will ask for it when exporting it.
Expand All @@ -62,7 +57,7 @@ Attention: these 3 files should be treated as secrets (even if `$MACOS_P12_FILE`
[apple-signing-certificate]: https://help.apple.com/xcode/mac/current/#/dev154b28f09
[rcodesign-api-key]: https://gregoryszorc.com/docs/apple-codesign/0.27.0/apple_codesign_getting_started.html#obtaining-an-app-store-connect-api-key

#### Signing implementation details
### Signing implementation details

Although PyInstaller supports signing, it did not work at the time we tried it, so we use [rcodesign][] to do so.

Expand All @@ -71,3 +66,32 @@ Although PyInstaller supports signing, it did not work at the time we tried it,
For Gatekeeper to accept the app, the executable and all the dynamic libraries must be signed, as well as the .pkg archive itself. Signing the executable and the libraries is done by the `sign` step, whereas signing the .pkg archive is done by the `create_archive` step.

[rcodesign]: https://gregoryszorc.com/docs/apple-codesign/

## Windows specific information

### Signing

We use [DigiCert](https://www.digicert.com) to sign `ggshield` Windows binaries.

#### Environment variables

On Windows, `build-os-packages` expects a number of environment variables to be set:

- `$WINDOWS_CERT_FINGERPRINT`: the thumbprint of the certificate.
- `$SM_API_KEY`: the API key to download DigiCert tools.
- `$SM_HOST`: the host to use for DigiCert.
- `$SM_CLIENT_CERT_FILE`: the path to the signing user authentication certificate.
- `$SM_CLIENT_CERT_PASSWORD`: the password protecting the signing user authentication certificate.
- `$PATH`: `$PATH` must contain the path to:
- the `signtool.exe` tool, from Microsoft
- the `smctl.exe` tool, provided by DigiCert. Its default installation path is `C:\Program Files\DigiCert\DigiCert Keylocker Tools`.

#### Installing DigiCert tools

Downloading and installing DigiCert tools can be done with `scripts/build-os-packages/install-keylocker-tools`.

Note: `install-keylocker-tools` expects `$PATH` to already contain the installation path of `smctl.exe`.

#### Building signed binaries

Once all environment variables are set and DigiCert tools are installed, one can build signed Windows binaries using `build-os-packages --sign`.
12 changes: 12 additions & 0 deletions scripts/build-os-packages/build-os-packages
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ load_os_specific_code() {
macOS)
. "$SCRIPT_DIR/macos-functions.bash"
;;
Windows)
. "$SCRIPT_DIR/windows-functions.bash"
;;
*)
;;
esac
Expand All @@ -142,6 +145,9 @@ add_os_specific_sign_requirements() {
macOS)
macos_add_sign_dependencies
;;
Windows)
windows_add_sign_dependencies
;;
*)
;;
esac
Expand Down Expand Up @@ -233,6 +239,9 @@ step_sign() {
macOS)
macos_sign
;;
Windows)
windows_sign
;;
*)
info "Signing not supported on $HUMAN_OS, skipping step"
;;
Expand Down Expand Up @@ -324,6 +333,9 @@ cd "$ROOT_DIR"
read_version
init_system_vars
load_os_specific_code
if [ "$DO_SIGN" -eq 1 ] ; then
add_os_specific_sign_requirements
fi

if [ -z "$steps" ] ; then
steps=$DEFAULT_STEPS
Expand Down
33 changes: 33 additions & 0 deletions scripts/build-os-packages/install-keylockertools
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail

DOWNLOAD_URL=https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download
KEYLOCKER_TOOLS_MSI_PATH=Keylockertools-windows-x64.msi

if command -v smctl.exe > /dev/null ; then
echo "Skipping installation of Keylockertools, smctl is already there"
else
curl \
-H "x-api-key:$SM_API_KEY" \
-o "$KEYLOCKER_TOOLS_MSI_PATH" \
--continue-at - \
"$DOWNLOAD_URL"

# double '/' so that Git Bash does not turn them into paths
msiexec //passive //i "$KEYLOCKER_TOOLS_MSI_PATH"
fi

if ! command -v smctl.exe > /dev/null ; then
echo "smctl.exe not found after installation. Make sure its installation dir is in \$PATH"
exit 1
fi

set -x # Log commands before running them
smksp_registrar list
smctl keypair ls
certutil.exe -csp "DigiCert Signing Manager KSP" -key -user

# Synchronize certificates with Windows certificate store
smctl windows certsync

smctl healthcheck --tools
27 changes: 27 additions & 0 deletions scripts/build-os-packages/windows-functions.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
WINDOWS_CERT_FINGERPRINT=${WINDOWS_CERT_FINGERPRINT:-}

windows_add_sign_dependencies() {
REQUIREMENTS="$REQUIREMENTS smctl signtool"
}

windows_sign() {
check_var WINDOWS_CERT_FINGERPRINT

# All the SM_* vars are required by smctl
check_var SM_API_KEY
check_var SM_HOST
check_var SM_CLIENT_CERT_FILE
check_var SM_CLIENT_CERT_PASSWORD

if [ ! -f "$SM_CLIENT_CERT_FILE" ] ; then
die "$SM_CLIENT_CERT_FILE does not exist"
fi

local archive_dir="$PACKAGES_DIR/$ARCHIVE_DIR_NAME"
smctl sign \
--verbose \
--fingerprint "$WINDOWS_CERT_FINGERPRINT" \
--tool signtool \
--input "$archive_dir/$INSTALL_PREFIX/ggshield.exe"
}

0 comments on commit 4753f7d

Please sign in to comment.