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:
Steve Yegge
2025-12-23 23:54:34 -08:00
parent edc6eae82c
commit 3c786f2333
5 changed files with 214 additions and 9 deletions
+50
View File
@@ -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
Additional maintenance scripts may be added here as needed.
+122
View 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"