pdftract/docs/notes/release-signing.md
jedarden eb025f7b1a 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 <noreply@anthropic.com>
2026-05-24 11:12:56 -04:00

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.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:

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

  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
  • Microsoft Docs: Authenticode
  • SLSA: SLSA Level 2

Revision History

Date Change
2026-05-24 Initial resolution; v1.0.0 stance (GPG Linux only)