feat: add Windows code signing infrastructure (bd-14v0)
Implements Authenticode signing for Windows binaries to reduce AV false positives. Changes: - Add scripts/sign-windows.sh for osslsigncode-based signing - Update .goreleaser.yml with post-build signing hook - Update release.yml to install osslsigncode and pass secrets - Update docs/ANTIVIRUS.md with signing verification instructions - Update scripts/README.md with signing script documentation The signing is gracefully degraded - releases continue without signing if the certificate secrets are not configured. Required secrets for signing: - WINDOWS_SIGNING_CERT_PFX_BASE64: base64-encoded PFX certificate - WINDOWS_SIGNING_CERT_PASSWORD: certificate password 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -26,10 +26,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.23'
|
||||||
|
|
||||||
- name: Install cross-compilation toolchains
|
- name: Install cross-compilation toolchains and signing tools
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y gcc-mingw-w64-x86-64 gcc-aarch64-linux-gnu
|
sudo apt-get install -y gcc-mingw-w64-x86-64 gcc-aarch64-linux-gnu osslsigncode
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
@@ -39,6 +39,9 @@ jobs:
|
|||||||
args: release --clean
|
args: release --clean
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# Windows code signing (optional - signing is skipped if not set)
|
||||||
|
WINDOWS_SIGNING_CERT_PFX_BASE64: ${{ secrets.WINDOWS_SIGNING_CERT_PFX_BASE64 }}
|
||||||
|
WINDOWS_SIGNING_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERT_PASSWORD }}
|
||||||
|
|
||||||
publish-pypi:
|
publish-pypi:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -93,6 +93,11 @@ builds:
|
|||||||
- -X main.Commit={{.Commit}}
|
- -X main.Commit={{.Commit}}
|
||||||
- -X main.Branch={{.Branch}}
|
- -X main.Branch={{.Branch}}
|
||||||
- -buildmode=exe
|
- -buildmode=exe
|
||||||
|
hooks:
|
||||||
|
post:
|
||||||
|
# Sign Windows executable with Authenticode certificate
|
||||||
|
# Requires WINDOWS_SIGNING_CERT_PFX_BASE64 and WINDOWS_SIGNING_CERT_PASSWORD secrets
|
||||||
|
- ./scripts/sign-windows.sh "{{ .Path }}"
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: bd-archive
|
- id: bd-archive
|
||||||
|
|||||||
@@ -101,13 +101,34 @@ ldflags:
|
|||||||
|
|
||||||
These flags are already applied in the official builds.
|
These flags are already applied in the official builds.
|
||||||
|
|
||||||
### Code Signing (Future)
|
### Code Signing
|
||||||
|
|
||||||
Future releases may include Windows code signing to improve trust scores with antivirus vendors. Code signing:
|
Windows releases are signed with an Authenticode certificate when available. Code signing:
|
||||||
- Reduces false positive rates over time
|
- Reduces false positive rates over time
|
||||||
- Builds reputation with SmartScreen/antivirus vendors
|
- Builds reputation with SmartScreen/antivirus vendors
|
||||||
- Provides tamper verification
|
- Provides tamper verification
|
||||||
|
|
||||||
|
**Verify a signed binary (Windows PowerShell):**
|
||||||
|
```powershell
|
||||||
|
# Check if the binary is signed
|
||||||
|
Get-AuthenticodeSignature .\bd.exe
|
||||||
|
|
||||||
|
# Expected output for signed binary:
|
||||||
|
# SignerCertificate: [Certificate details]
|
||||||
|
# Status: Valid
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify a signed binary (Linux/macOS with osslsigncode):**
|
||||||
|
```bash
|
||||||
|
# Install osslsigncode if not available
|
||||||
|
# Ubuntu/Debian: apt-get install osslsigncode
|
||||||
|
# macOS: brew install osslsigncode
|
||||||
|
|
||||||
|
osslsigncode verify -in bd.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Code signing requires an EV (Extended Validation) certificate, which involves a verification process. If a release is not signed, it means the certificate was not available at build time. Follow the checksum verification steps above to verify authenticity.
|
||||||
|
|
||||||
### Alternative Build Methods
|
### Alternative Build Methods
|
||||||
|
|
||||||
Some users report success with:
|
Some users report success with:
|
||||||
@@ -136,12 +157,16 @@ The issue isn't specific to beads' code - it's a characteristic of Go binaries i
|
|||||||
|
|
||||||
### Will this be fixed in future releases?
|
### Will this be fixed in future releases?
|
||||||
|
|
||||||
We're working on:
|
We've implemented:
|
||||||
- Submitting beads to antivirus vendor whitelists
|
- **Code signing infrastructure** for Windows releases (requires EV certificate)
|
||||||
- Adding code signing for Windows releases
|
- **Build optimizations** to reduce heuristic triggers
|
||||||
- Continuing to use build optimizations
|
- **Documentation** for users to add exclusions and report false positives
|
||||||
|
|
||||||
However, false positives may still occur with new releases until reputation is established.
|
Still in progress:
|
||||||
|
- Acquiring an EV code signing certificate
|
||||||
|
- Submitting beads to antivirus vendor whitelists
|
||||||
|
|
||||||
|
False positives may still occur with new releases until the certificate builds reputation with antivirus vendors. This typically takes several months of consistent signed releases.
|
||||||
|
|
||||||
### Should I disable my antivirus?
|
### Should I disable my antivirus?
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,56 @@ This script fixes all those issues and is now used by `release.sh`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## sign-windows.sh
|
||||||
|
|
||||||
|
Signs Windows executables with an Authenticode certificate using osslsigncode.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sign a Windows executable
|
||||||
|
./scripts/sign-windows.sh path/to/bd.exe
|
||||||
|
|
||||||
|
# Environment variables required for signing:
|
||||||
|
export WINDOWS_SIGNING_CERT_PFX_BASE64="<base64-encoded-pfx>"
|
||||||
|
export WINDOWS_SIGNING_CERT_PASSWORD="<certificate-password>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### What It Does
|
||||||
|
|
||||||
|
This script is called automatically by GoReleaser during the release process:
|
||||||
|
|
||||||
|
1. **Decodes** the PFX certificate from base64
|
||||||
|
2. **Signs** the Windows executable using osslsigncode
|
||||||
|
3. **Timestamps** the signature using DigiCert's RFC3161 server
|
||||||
|
4. **Replaces** the original binary with the signed version
|
||||||
|
5. **Verifies** the signature was applied correctly
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- `osslsigncode` installed (`apt install osslsigncode` or `brew install osslsigncode`)
|
||||||
|
- EV code signing certificate exported as PFX file
|
||||||
|
- GitHub secrets configured:
|
||||||
|
- `WINDOWS_SIGNING_CERT_PFX_BASE64` - base64-encoded PFX file
|
||||||
|
- `WINDOWS_SIGNING_CERT_PASSWORD` - certificate password
|
||||||
|
|
||||||
|
### Graceful Degradation
|
||||||
|
|
||||||
|
If the signing secrets are not configured:
|
||||||
|
- The script prints a warning and exits successfully
|
||||||
|
- GoReleaser continues without signing
|
||||||
|
- The release proceeds with unsigned Windows binaries
|
||||||
|
|
||||||
|
This allows releases to work before a certificate is acquired.
|
||||||
|
|
||||||
|
### Why This Script Exists
|
||||||
|
|
||||||
|
Windows code signing helps reduce antivirus false positives that affect Go binaries.
|
||||||
|
Kaspersky and other AV software commonly flag unsigned Go executables as potentially
|
||||||
|
malicious due to heuristic detection. See `docs/ANTIVIRUS.md` for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Future Scripts
|
## Future Scripts
|
||||||
|
|
||||||
Additional maintenance scripts may be added here as needed.
|
Additional maintenance scripts may be added here as needed.
|
||||||
|
|||||||
122
scripts/sign-windows.sh
Executable file
122
scripts/sign-windows.sh
Executable file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# sign-windows.sh - Sign Windows executables using osslsigncode
|
||||||
|
#
|
||||||
|
# This script signs Windows binaries with an Authenticode certificate.
|
||||||
|
# It's designed to be called from GoReleaser hooks or CI/CD pipelines.
|
||||||
|
#
|
||||||
|
# Required environment variables:
|
||||||
|
# WINDOWS_SIGNING_CERT_PFX_BASE64 - Base64-encoded PFX certificate file
|
||||||
|
# WINDOWS_SIGNING_CERT_PASSWORD - Password for the PFX certificate
|
||||||
|
#
|
||||||
|
# Optional environment variables:
|
||||||
|
# TIMESTAMP_SERVER - RFC3161 timestamp server URL (default: DigiCert)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./sign-windows.sh <path-to-exe>
|
||||||
|
# ./sign-windows.sh dist/bd-windows-amd64_windows_amd64_v1/bd.exe
|
||||||
|
#
|
||||||
|
# For GoReleaser integration, add to .goreleaser.yml:
|
||||||
|
# builds:
|
||||||
|
# - id: bd-windows-amd64
|
||||||
|
# hooks:
|
||||||
|
# post:
|
||||||
|
# - ./scripts/sign-windows.sh "{{ .Path }}"
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
TIMESTAMP_SERVER="${TIMESTAMP_SERVER:-http://timestamp.digicert.com}"
|
||||||
|
CERT_FILE="/tmp/signing-cert.pfx"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
# Remove temporary certificate file
|
||||||
|
if [[ -f "$CERT_FILE" ]]; then
|
||||||
|
rm -f "$CERT_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Check for required argument
|
||||||
|
if [[ $# -lt 1 ]]; then
|
||||||
|
log_error "Usage: $0 <path-to-exe>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXE_PATH="$1"
|
||||||
|
|
||||||
|
# Verify file exists
|
||||||
|
if [[ ! -f "$EXE_PATH" ]]; then
|
||||||
|
log_error "File not found: $EXE_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for required environment variables
|
||||||
|
if [[ -z "${WINDOWS_SIGNING_CERT_PFX_BASE64:-}" ]]; then
|
||||||
|
log_warn "WINDOWS_SIGNING_CERT_PFX_BASE64 not set - skipping code signing"
|
||||||
|
log_info "To enable Windows code signing:"
|
||||||
|
log_info " 1. Obtain an EV code signing certificate"
|
||||||
|
log_info " 2. Export it as a PFX file"
|
||||||
|
log_info " 3. Base64 encode: base64 -i cert.pfx"
|
||||||
|
log_info " 4. Add as GitHub secret: WINDOWS_SIGNING_CERT_PFX_BASE64"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${WINDOWS_SIGNING_CERT_PASSWORD:-}" ]]; then
|
||||||
|
log_error "WINDOWS_SIGNING_CERT_PASSWORD not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for osslsigncode
|
||||||
|
if ! command -v osslsigncode &> /dev/null; then
|
||||||
|
log_error "osslsigncode not found. Install it with:"
|
||||||
|
log_error " Ubuntu/Debian: apt-get install osslsigncode"
|
||||||
|
log_error " macOS: brew install osslsigncode"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Signing Windows executable: $EXE_PATH"
|
||||||
|
|
||||||
|
# Decode certificate from base64
|
||||||
|
echo "$WINDOWS_SIGNING_CERT_PFX_BASE64" | base64 -d > "$CERT_FILE"
|
||||||
|
|
||||||
|
# Sign the executable
|
||||||
|
osslsigncode sign \
|
||||||
|
-pkcs12 "$CERT_FILE" \
|
||||||
|
-pass "$WINDOWS_SIGNING_CERT_PASSWORD" \
|
||||||
|
-n "beads - AI-supervised issue tracker" \
|
||||||
|
-i "https://github.com/steveyegge/beads" \
|
||||||
|
-t "$TIMESTAMP_SERVER" \
|
||||||
|
-in "$EXE_PATH" \
|
||||||
|
-out "${EXE_PATH}.signed"
|
||||||
|
|
||||||
|
# Replace original with signed version
|
||||||
|
mv "${EXE_PATH}.signed" "$EXE_PATH"
|
||||||
|
|
||||||
|
log_info "Successfully signed: $EXE_PATH"
|
||||||
|
|
||||||
|
# Verify the signature
|
||||||
|
log_info "Verifying signature..."
|
||||||
|
osslsigncode verify -in "$EXE_PATH" || {
|
||||||
|
log_warn "Signature verification returned non-zero (may still be valid)"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info "Windows code signing complete"
|
||||||
Reference in New Issue
Block a user