Adds macOS code signing and notarization infrastructure (FN-5609), introducing hardened runtime entitlements, electron-builder config for signed builds, and signing-enabled release workflows, with tests validating the configuration and docs covering the signing flow. Fusion-Task-Id: FN-5609 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> Fusion-Task-Id: FN-5609
237 lines
9.2 KiB
Markdown
237 lines
9.2 KiB
Markdown
# Code Signing Setup Guide
|
|
|
|
This document explains how to configure code signing for `kb` release binaries so they don't trigger OS security warnings on macOS (Gatekeeper) or Windows (SmartScreen), and how Linux desktop artifact signatures are published for authenticity verification.
|
|
|
|
## Overview
|
|
|
|
The release workflow automatically signs binaries when the appropriate secrets are configured:
|
|
|
|
- **macOS**: Codesign with hardened runtime + Apple notarization
|
|
- **Windows**: Authenticode signing with timestamp
|
|
- **Linux**: GPG detached-signature signing of `.AppImage` / `.deb` / `.tar.gz` desktop artifacts
|
|
|
|
Signing is **optional** — if secrets are not configured, the build succeeds and signing steps are skipped.
|
|
|
|
## Required GitHub Secrets
|
|
|
|
### macOS Signing
|
|
|
|
| Secret | Description |
|
|
|--------|-------------|
|
|
| `APPLE_CERTIFICATE_BASE64` | Base64-encoded `.p12` Developer ID Application certificate |
|
|
| `APPLE_CERTIFICATE_PASSWORD` | Password used when exporting the `.p12` certificate |
|
|
| `APPLE_IDENTITY` | Signing identity string (e.g., `Developer ID Application: Your Name (TEAMID)`) |
|
|
| `APPLE_ID` | Apple ID email address used for notarization |
|
|
| `APPLE_TEAM_ID` | Apple Developer Team ID (10-character alphanumeric) |
|
|
| `APPLE_APP_PASSWORD` | App-specific password for notarization |
|
|
|
|
### Windows Signing
|
|
|
|
| Secret | Description |
|
|
|--------|-------------|
|
|
| `WINDOWS_CERTIFICATE_BASE64` | Base64-encoded `.pfx` Authenticode code signing certificate |
|
|
| `WINDOWS_CERTIFICATE_PASSWORD` | Password for the `.pfx` certificate |
|
|
|
|
### Linux Signing
|
|
|
|
| Secret | Description |
|
|
|--------|-------------|
|
|
| `LINUX_GPG_PRIVATE_KEY` | Base64 of an ASCII-armored exported private key (`gpg --armor --export-secret-keys`) |
|
|
| `LINUX_GPG_PASSPHRASE` | Passphrase that unlocks the exported private key |
|
|
| `LINUX_GPG_KEY_ID` | Long key ID or full fingerprint used with `gpg --local-user` |
|
|
|
|
## macOS Setup Instructions
|
|
|
|
### 1. Obtain a Developer ID Application Certificate
|
|
|
|
1. Enroll in the [Apple Developer Program](https://developer.apple.com/programs/)
|
|
2. In Xcode or the Apple Developer portal, create a **Developer ID Application** certificate
|
|
3. Export the certificate from Keychain Access as a `.p12` file with a password
|
|
|
|
### 2. Encode the Certificate as Base64
|
|
|
|
```bash
|
|
base64 -i certificate.p12 | pbcopy
|
|
```
|
|
|
|
Paste the result as the `APPLE_CERTIFICATE_BASE64` secret.
|
|
|
|
### 3. Find Your Team ID
|
|
|
|
Your Team ID is visible at [developer.apple.com/account](https://developer.apple.com/account) under Membership Details. It's a 10-character alphanumeric string (e.g., `ABC1234DEF`).
|
|
|
|
### 4. Create an App-Specific Password
|
|
|
|
1. Go to [appleid.apple.com](https://appleid.apple.com/)
|
|
2. Sign in and navigate to **Sign-In and Security** → **App-Specific Passwords**
|
|
3. Generate a new password and label it (e.g., "kb notarization")
|
|
4. Use this as the `APPLE_APP_PASSWORD` secret
|
|
|
|
### 5. Determine Your Signing Identity
|
|
|
|
The signing identity looks like:
|
|
```
|
|
Developer ID Application: Your Name (TEAMID)
|
|
```
|
|
|
|
You can find it by running:
|
|
```bash
|
|
security find-identity -v -p codesigning
|
|
```
|
|
|
|
## Linux Signing
|
|
|
|
### 1. Generate a release signing key
|
|
|
|
```bash
|
|
gpg --full-generate-key
|
|
```
|
|
|
|
Recommended: RSA 4096, with either no expiration or a 2-year expiration.
|
|
|
|
### 2. Export and base64-encode the private key for GitHub Actions secret storage
|
|
|
|
```bash
|
|
gpg --armor --export-secret-keys <key-id> | base64 -w0 | pbcopy
|
|
```
|
|
|
|
Linux clipboard alternative:
|
|
|
|
```bash
|
|
gpg --armor --export-secret-keys <key-id> | base64 -w0 | xclip -selection clipboard
|
|
```
|
|
|
|
Store this output as `LINUX_GPG_PRIVATE_KEY`.
|
|
|
|
### 3. Set Linux signing secrets
|
|
|
|
Set repository-level Actions secrets:
|
|
- `LINUX_GPG_PRIVATE_KEY`
|
|
- `LINUX_GPG_PASSPHRASE`
|
|
- `LINUX_GPG_KEY_ID`
|
|
|
|
### 4. Publish the public key for users
|
|
|
|
Export and publish the public key in release notes and/or a repository `KEYS` file:
|
|
|
|
```bash
|
|
gpg --armor --export <key-id>
|
|
```
|
|
|
|
### 5. User verification flow
|
|
|
|
```bash
|
|
gpg --import KEYS && gpg --verify Fusion-<version>-linux-x64.AppImage.asc Fusion-<version>-linux-x64.AppImage
|
|
```
|
|
|
|
If `LINUX_GPG_PRIVATE_KEY` is absent (forks/initial setup), CI continues and ships unsigned Linux artifacts without `.asc` sidecars.
|
|
|
|
## Windows Setup Instructions
|
|
|
|
### 1. Obtain an Authenticode Code Signing Certificate
|
|
|
|
Purchase a code signing certificate from a trusted Certificate Authority:
|
|
- DigiCert
|
|
- Sectigo (Comodo)
|
|
- GlobalSign
|
|
- SSL.com
|
|
|
|
### 2. Export as `.pfx`
|
|
|
|
Export the certificate with its private key as a `.pfx` (PKCS#12) file. Set a strong password.
|
|
|
|
### 3. Encode the Certificate as Base64
|
|
|
|
```powershell
|
|
[Convert]::ToBase64String([IO.File]::ReadAllBytes("certificate.pfx")) | Set-Clipboard
|
|
```
|
|
|
|
Or on Linux/macOS:
|
|
```bash
|
|
base64 -i certificate.pfx
|
|
```
|
|
|
|
Paste the result as the `WINDOWS_CERTIFICATE_BASE64` secret.
|
|
|
|
## How Signing Works in the Release Flow
|
|
|
|
1. A tag push (`v*`) triggers the release workflow
|
|
2. Each platform job builds the standalone binary
|
|
3. **macOS CLI jobs**: `scripts/sign-macos.sh` runs codesign + notarization for standalone CLI binaries
|
|
4. **macOS desktop jobs**: electron-builder signs `.dmg` / `.zip` outputs using `CSC_LINK` / `CSC_KEY_PASSWORD`, notarizes with `APPLE_ID` / `APPLE_APP_SPECIFIC_PASSWORD` / `APPLE_TEAM_ID`, and validates signatures/stapling in CI
|
|
5. **Windows jobs**: `scripts/sign-windows.ps1` runs Authenticode signing
|
|
6. **Linux desktop jobs**: `scripts/sign-linux.sh` runs `gpg --detach-sign --armor` for each `.AppImage` / `.deb` / `.tar.gz` artifact and verifies each signature with `gpg --verify`
|
|
7. Checksums are generated **after** signing (so they match the signed binaries)
|
|
8. Signed binaries and checksums are uploaded to the GitHub Release
|
|
|
|
The test-release workflow (`workflow_dispatch`) includes the same signing steps but guards them with secret-availability checks — signing is skipped if secrets are not configured.
|
|
|
|
Desktop Windows packaging (`.github/workflows/desktop-windows.yml`) now uses the same `WINDOWS_CERTIFICATE_BASE64` / `WINDOWS_CERTIFICATE_PASSWORD` secrets to sign NSIS and portable EXE artifacts via electron-builder (`CSC_LINK` / `CSC_KEY_PASSWORD`) and validates signatures with `Get-AuthenticodeSignature` when signing is enabled.
|
|
|
|
## Troubleshooting
|
|
|
|
### macOS: "The signature of the binary is invalid"
|
|
|
|
- Ensure the certificate is a **Developer ID Application** certificate (not Developer ID Installer or Mac App Distribution)
|
|
- Check that the certificate hasn't expired
|
|
- Verify the base64 encoding is correct: `echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode | file -`
|
|
|
|
### macOS: Notarization fails with "Invalid credentials"
|
|
|
|
- Verify `APPLE_ID` is your Apple ID email
|
|
- Verify `APPLE_APP_PASSWORD` is an app-specific password (not your Apple ID password)
|
|
- Verify `APPLE_TEAM_ID` matches the team that issued the certificate
|
|
|
|
### macOS: Notarization fails with "The software is not signed"
|
|
|
|
- Ensure the `--options runtime` flag is used during codesign (hardened runtime is required for notarization)
|
|
- The `sign-macos.sh` script handles this automatically
|
|
|
|
### Windows: "signtool not found"
|
|
|
|
- `signtool.exe` is included in the Windows SDK, which is pre-installed on GitHub Actions Windows runners
|
|
- For local testing, install the Windows SDK or Visual Studio Build Tools
|
|
|
|
### Windows: "The specified PFX password is not correct"
|
|
|
|
- Double-check the `WINDOWS_CERTIFICATE_PASSWORD` secret matches the password used when exporting the `.pfx`
|
|
|
|
### Linux signing skipped
|
|
|
|
- Expected when `LINUX_GPG_PRIVATE_KEY` is not set (forks, dry runs, first-time setup)
|
|
- In this path, Linux artifacts upload without `.asc` sidecars by design
|
|
|
|
### Linux signing fails with "No secret key"
|
|
|
|
- `LINUX_GPG_KEY_ID` does not match the imported private key fingerprint/key ID
|
|
- Re-export and re-check key ID via `gpg --list-secret-keys --keyid-format=long`
|
|
|
|
### Linux verification fails with "BAD signature"
|
|
|
|
- Artifact changed between sign and verify; investigate runner storage path and artifact staging
|
|
- Ensure signatures are generated from the exact file that is later uploaded
|
|
|
|
### Signing step skipped
|
|
|
|
- In the test-release workflow, signing is intentionally skipped when secrets are not configured
|
|
- Verify the secrets are set at the repository level in **Settings → Secrets and variables → Actions**
|
|
|
|
### Desktop EXE signing skipped
|
|
|
|
- The desktop Windows workflow intentionally falls back to unsigned artifacts when `WINDOWS_CERTIFICATE_BASE64` is empty
|
|
- In that unsigned path, `Verify signed artifacts` is skipped by design
|
|
- Set both `WINDOWS_CERTIFICATE_BASE64` and `WINDOWS_CERTIFICATE_PASSWORD` at the repository level to enable signed desktop EXE output
|
|
|
|
### Desktop DMG/ZIP signing skipped
|
|
|
|
- The macOS desktop release jobs intentionally fall back to unsigned artifacts when `APPLE_CERTIFICATE_BASE64` is empty
|
|
- In that unsigned path, packaging runs with `-c.mac.notarize=false` so builds do not block on missing Apple notarization credentials
|
|
- The `Verify signed and notarized macOS artifacts` step is skipped by design for unsigned runs
|
|
|
|
### Notarization fails for desktop bundle
|
|
|
|
- Confirm `APPLE_APP_PASSWORD` is configured in GitHub secrets and mapped to `APPLE_APP_SPECIFIC_PASSWORD` in workflow env
|
|
- Confirm `APPLE_TEAM_ID` matches the team that issued the Developer ID certificate
|
|
- Confirm the Developer ID certificate in `APPLE_CERTIFICATE_BASE64` is valid and not expired
|
|
- Confirm hardened runtime entitlements are present via `packages/desktop/build/entitlements.mac.plist`
|