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

View File

@@ -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

View File

@@ -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

View File

@@ -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?

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 ## 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
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"