Resolves OQ-10: document v1.0.0 stance on binary signing. - Linux: GPG-signed (implemented) - macOS: Deferred to v1.1+ ($99/yr Apple Developer Program) - Windows: Deferred to v1.1+ ($200-400/yr Authenticode cert) - All platforms: SLSA Level 2 attestation (already committed) Closes: pdftract-3wrx Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
11 KiB
Release Binary Signing Strategy
Status: RESOLVED (OQ-10) Date: 2026-05-24 Bead: pdftract-3wrx
Open Question OQ-10
What is the v1.0.0 stance on signed binaries — code-signed macOS releases, Authenticode-signed Windows binaries, GPG-signed Linux releases? Each adds CI complexity.
Resolution Decision
v1.0.0 Stance: GPG-sign Linux artifacts only. Defer macOS and Windows code-signing to v1.1+.
| Platform | Code Signing | v1.0.0 | Rationale |
|---|---|---|---|
| Linux | GPG signature | ✅ Yes | Low cost; strong trust chain; distro compatibility |
| macOS | Developer ID + notarization | ❌ Defer | $99/yr Apple Developer Program cost |
| Windows | Authenticode | ❌ Defer | $200-400/yr cert cost; cross-platform CI complexity |
Background: Code-Signing vs Attestation
Code-signing and SLSA provenance answer different questions:
- Code-signing (macOS Developer ID, Windows Authenticode): Who built this? Binds a binary to an identity certificate. End-of-chain trust anchors are Apple/Microsoft root CAs.
- SLSA provenance (
provenance.intoto.jsonl): How was this built? Cryptographic attestation of the build process (materials, inputs, builder identity). Trust anchor is the build infrastructure's signing key.
Both are complementary. SLSA Level 2 (already committed per plan line 895) provides supply-chain transparency. Code-signing provides platform-native trust integration (Gatekeeper, SmartScreen).
SLSA Level 2 Attestation (Already Implemented)
Per plan line 895, every GitHub Release includes provenance.intoto.jsonl generated by the Argo runner on iad-ci. This attestation contains:
- Builder identity: Argo Workflow execution on
iad-ci - Materials: Git commit SHA, dependency crate versions (from
Cargo.lock) - Recipe: Build command invocation
- Metadata: Timestamp, workflow template
The attestation is signed by the CI fleet's root key. Consumers verify using slsa-verifier:
slsa-verifier verify-artifact \
--provenance-path provenance.intoto.jsonl \
--source-uri github.com/jedarden/pdftract \
--source-tag v1.0.0
This provides supply-chain integrity without platform-specific code-signing.
Platform-Specific Analysis
Linux: GPG Signing
Status: ✅ Implemented in v1.0.0
Implementation:
- Release tarball (
pdftract-$VERSION-x86_64-unknown-linux-gnu.tar.gz) and cargo registry crate are signed with a maintainer-controlled GPG key - Public key is checked into the repo at
docs/signing/pubkey.asc - Signature files (
*.tar.gz.asc) are uploaded alongside artifacts on GitHub Releases Cargo.tomlincludesmetadata.signingkey for cargo verification
Rationale for v1.0.0:
- Zero cost: GPG is free; key generation is local
- Strong trust: GPG web-of-trust is well-understood by Linux users
- Distro compatibility: Many Linux distributions require GPG signatures for third-party packages
- Low CI complexity:
gpg --detach-signis a single command; key lives in OpenBao as a Kubernetes Secret
Key Management:
- Private key: Stored in OpenBao, synced to Kubernetes Secret
signing-gpg-privatein theiad-cinamespace - Passphrase: Separate OpenBao entry, injected as env var
GPG_PASSPHRASEduring signing step - Public key: Checked into repo at
docs/signing/pubkey.asc(NOT secret) - Rotation: Annually, or immediately on suspected compromise. Rotate = generate new key, update
pubkey.asc, re-sign latest release, publish new fingerprint.
CI Integration:
# Argo WorkflowTemplate: pdftract-release
- name: sign-linux-artifacts
container:
image: debian:bookworm-slim
command:
- sh
- -c
- |
echo "$GPG_PRIVATE_KEY" | gpg --import --batch --passphrase "$GPG_PASSPHRASE"
for artifact in pdftract-*.tar.gz; do
gpg --detach-sign --armor --passphrase "$GPG_PASSPHRASE" "$artifact"
done
env:
- name: GPG_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: signing-gpg-private
key: key
- name: GPG_PASSPHRASE
valueFrom:
secretKeyRef:
name: signing-gpg-passphrase
key: passphrase
Verification:
# Download and verify release
wget https://github.com/jedarden/pdftract/releases/download/v1.0.0/pdftract-1.0.0-x86_64-unknown-linux-gnu.tar.gz{,.asc}
gpg --verify pdftract-1.0.0-x86_64-unknown-linux-gnu.tar.gz.asc
gpg --import docs/signing/pubkey.asc # First time only
macOS: Code Signing + Notarization
Status: ❌ Deferred to v1.1+
What would be required:
- Apple Developer Program account ($99/year)
- Developer ID Application certificate (issued by Apple)
- Notarization (post-sign submission to Apple's notarization service)
- Stapling (attach notarization ticket to binary)
Implementation cost:
- Financial: $99/year recurring (Apple Developer Program)
- Tooling:
codesignCLI (built into Xcode),xcrun notarytool submit(requires Apple ID auth) - CI complexity:
- Certificates stored as
.p12in OpenBao - Notarization requires Apple ID app-specific password (separate secret)
- Two-step signing+notarization workflow
- Certificates stored as
Consequences of NOT signing (current state):
- Gatekeeper quarantine: macOS quarantines unsigned downloads; users see "cannot be opened because the developer cannot be verified"
- User friction: Users must right-click → Open, or use
xattr -d com.apple.quarantine pdftract - No SmartScreen integration: macOS doesn't block execution, but warns on first run
Why defer to v1.1+:
- Funding question: $99/year is non-trivial for a v1.0.0 release with uncertain adoption
- ADR-009 constraint:
iad-ciis Linux-only; cannot runcodesignnatively. Would need macOS runner or cross-compilation with remote signing - Alternative: Community-sponsored Apple Developer Program account (e.g., "pdftract is signed by Ardenone LLC under their developer account")
v1.1+ trigger:
- Sustained macOS user base (>10% of downloads)
- Funding allocation for Apple Developer Program
- OR community contribution: "I will sign pdftract binaries under my Apple Developer account"
Windows: Authenticode Signing
Status: ❌ Deferred to v1.1+
What would be required:
- Code signing certificate from a Windows-trusted CA (DigiCert, Sectigo, GlobalSign)
- Certificate cost: $200-400/year (standard), ~$1000/year (EV certificate)
- Signing tool:
signtool(Windows SDK) orosslsigncode(cross-platform)
Implementation cost:
- Financial: $200-400/year minimum
- Tooling:
- Native:
signtool sign /f cert.pfx /p password ...(requires Windows) - Cross-platform:
osslsigncode(Linux-compatible, but less battle-tested)
- Native:
- CI complexity:
iad-ciis Linux-only per ADR-009.signtooldoesn't run on Linux. Options:- Cross-compile + remote signing: Build on Linux, transfer binary to Windows VM for signing (complex)
- osslsigncode: Open-source alternative, but unclear if Windows SmartScreen trusts it equally
- Azure Trusted Signing: Microsoft's cloud signing service (requires Azure AD tenancy, per-certificate cost)
Consequences of NOT signing (current state):
- SmartScreen warning: Users see "Windows protected your PC" warning on first run
- User friction: Users must click "More info" → "Run anyway"
- No Microsoft Store compatibility: Cannot publish to Microsoft Store without signing
Why defer to v1.1+:
- Funding question: $200-400/year is non-trivial
- CI-platform mismatch: ADR-009 mandates Linux-only CI; Authenticode signing is fundamentally Windows-centric
- osslsigncode uncertainty: Unclear if cross-platform signing produces identical SmartScreen behavior
v1.1+ trigger:
- Sustained Windows user base (>10% of downloads)
- Funding allocation for certificate
- OR community contribution: "I will sign pdftract binaries under my certificate"
Failure Modes and Mitigations
macOS Notarization Rejection
Failure: xcrun notarytool returns Status: Invalid
Mitigation:
- Do not ship unsigned binary with failed notarization
- CI step aborts release; diagnostic logged to Argo Workflow
- Maintainer must investigate (malware fingerprint, entitlement issue, Apple ID problem)
SmartScreen False Positive (Windows)
Failure: Unsigned binary triggers SmartScreen "unrecognized app" warning
Mitigation (v1.0.0, unsigned):
- Document in README: "Click 'More info' → 'Run anyway'"
- Encourage users to verify GPG signature on Linux subsystem or WSL
- Track SmartScreen false-positive rate via user feedback
Mitigation (v1.1+, signed):
- Timestamped Authenticode signature prevents post-signage tampering
- Certificate reputation improves over time (fewer warnings as adoption grows)
GPG Key Compromise
Failure: Maintainer's GPG private key is exfiltrated from OpenBao
Mitigation:
- Immediate: Rotate compromised key (generate new key, revoke old on keyserver)
- Re-sign: Re-sign latest release with new key
- Communicate: Publish security advisory with new fingerprint
- Postmortem: Audit OpenBao access logs; implement stricter RBAC
Prevention:
- OpenBao's transit encryption (key never leaves Vault in plaintext)
- Kubernetes Secret with strict RBAC (only
pdftract-releaseworkflow can read) - Quarterly key rotation (planned, not implemented)
Version Policy
v1.0.0 (Current Release)
- Linux: GPG-signed (
*.tar.gz.asc) - macOS: Unsigned (quarantine warning expected)
- Windows: Unsigned (SmartScreen warning expected)
- All platforms: SLSA Level 2 attestation (
provenance.intoto.jsonl)
v1.1.0 (Future Release, TBD)
- Linux: GPG-signed (no change)
- macOS: Developer ID signed + notarized (funding dependent)
- Windows: Authenticode-signed (funding dependent)
- All platforms: SLSA Level 2 attestation (no change)
Release Checklist (Per KU-12)
The docs/operations/manual-platform-smoke.md runbook (KU-12) includes signature verification:
- Download release artifacts from GitHub Releases
- Verify GPG signature (Linux):
gpg --verify *.asc - Verify SLSA provenance (all platforms):
slsa-verifier verify-artifact - Run smoke test (binary execution, extraction sanity check)
- Check platform-specific behavior:
- macOS: Does Gatekeeper quarantine the binary? (Expected v1.0.0, unexpected v1.1+)
- Windows: Does SmartScreen warn? (Expected v1.0.0, unexpected v1.1+)
- Linux: Does GPG verify successfully? (Expected always)
References
- Plan Open Question OQ-10 (line 521)
- Plan Supply Chain Considerations (line 895): SLSA Level 2 commitment
- Plan ADR-009 (line 495): Argo Workflows on
iad-ci(Linux-only CI) - Plan Known Unknown KU-12 (line 608): Manual quarterly smoke test
- Apple Developer Documentation: Code Signing
- Microsoft Docs: Authenticode
- SLSA: SLSA Level 2
Revision History
| Date | Change |
|---|---|
| 2026-05-24 | Initial resolution; v1.0.0 stance (GPG Linux only) |