From eb025f7b1a81f4b40bf663db2e9af31102fae6b9 Mon Sep 17 00:00:00 2001 From: jedarden Date: Sun, 24 May 2026 11:12:56 -0400 Subject: [PATCH] docs(pdftract-3wrx): add release signing strategy note 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 --- docs/notes/release-signing.md | 258 ++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 docs/notes/release-signing.md diff --git a/docs/notes/release-signing.md b/docs/notes/release-signing.md new file mode 100644 index 0000000..02d2d28 --- /dev/null +++ b/docs/notes/release-signing.md @@ -0,0 +1,258 @@ +# 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`: + +```bash +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.toml` includes `metadata.signing` key 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-sign` is a single command; key lives in OpenBao as a Kubernetes Secret + +**Key Management:** +- **Private key:** Stored in OpenBao, synced to Kubernetes Secret `signing-gpg-private` in the `iad-ci` namespace +- **Passphrase:** Separate OpenBao entry, injected as env var `GPG_PASSPHRASE` during 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:** +```yaml +# 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:** +```bash +# 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:** +1. **Apple Developer Program account** ($99/year) +2. **Developer ID Application certificate** (issued by Apple) +3. **Notarization** (post-sign submission to Apple's notarization service) +4. **Stapling** (attach notarization ticket to binary) + +**Implementation cost:** +- **Financial:** $99/year recurring (Apple Developer Program) +- **Tooling:** `codesign` CLI (built into Xcode), `xcrun notarytool submit` (requires Apple ID auth) +- **CI complexity:** + - Certificates stored as `.p12` in OpenBao + - Notarization requires Apple ID app-specific password (separate secret) + - Two-step signing+notarization workflow + +**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-ci` is Linux-only; cannot run `codesign` natively. 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:** +1. **Code signing certificate** from a Windows-trusted CA (DigiCert, Sectigo, GlobalSign) +2. **Certificate cost:** $200-400/year (standard), ~$1000/year (EV certificate) +3. **Signing tool:** `signtool` (Windows SDK) or `osslsigncode` (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) +- **CI complexity:** `iad-ci` is Linux-only per ADR-009. `signtool` doesn't run on Linux. Options: + 1. **Cross-compile + remote signing:** Build on Linux, transfer binary to Windows VM for signing (complex) + 2. **osslsigncode:** Open-source alternative, but unclear if Windows SmartScreen trusts it equally + 3. **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-release` workflow 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: + +1. **Download release artifacts** from GitHub Releases +2. **Verify GPG signature** (Linux): `gpg --verify *.asc` +3. **Verify SLSA provenance** (all platforms): `slsa-verifier verify-artifact` +4. **Run smoke test** (binary execution, extraction sanity check) +5. **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](https://developer.apple.com/support/code-signing/) +- Microsoft Docs: [Authenticode](https://docs.microsoft.com/en-us/windows/win32/seccrypto/cryptography-tools) +- SLSA: [SLSA Level 2](https://slsa.dev/spec/v0.1/levels#level-2) + +## Revision History + +| Date | Change | +|------|--------| +| 2026-05-24 | Initial resolution; v1.0.0 stance (GPG Linux only) |