Skip to content

Threat model

This page states the security boundaries of signing: what it protects, what it assumes, and what is out of scope. It complements The trust model, which explains the design; here the focus is on guarantees and their limits.

What it protects

  • Tamper detection of release assets. A detached OpenPGP signature over a checksums manifest, verified against a trusted key, detects any modification to the manifest. Comparing each asset's hash to its manifest line extends that to the assets themselves. An attacker who alters an asset or the manifest after signing cannot produce a manifest that verifies, without the signing key.
  • Signer authenticity, anchored in agreement. Trust is granted only to keys the configured anchors agree on. With the composite embedded + WKD model, a forged release must be signed by a key present and consistent across both anchors. A single compromised source is detected as a disagreement (ErrKeyResolverMismatch) and rejected.
  • A strength floor. Keys below the policy (RSA < 3072 bits) are refused at trust-set construction (ErrWeakKey), so weak keys cannot quietly weaken the guarantee in production.

What it assumes (trust assumptions)

  • Key distribution is the root of trust. Verification proves a manifest was signed by a key in your trust set; it cannot tell you that key deserves trust. That judgement comes from how the key reached you:
    • Embedded keys are only as trustworthy as the build that embedded them. A compromised build pipeline can embed an attacker's key. Protecting the build is the consumer's responsibility.
    • WKD keys are only as trustworthy as the publisher's domain, DNS, web server, and the TLS protecting the fetch. The WKD leg relies on HTTPS transport security; a compromised CA, domain, or hosting can serve an attacker's key.
  • The composite model exists precisely because each anchor's assumption can fail. Cross-checking two independent anchors means a single failed assumption is caught as a mismatch rather than silently trusted.

Compromise of one anchor vs both

  • One anchor compromised. If an attacker controls the embedded key or the WKD key but not both consistently, the anchors disagree and resolution aborts with ErrKeyResolverMismatch. The release is rejected. This is the intended, designed-for case.
  • Both anchors compromised, consistently, within one window. If an attacker controls both the build that embeds the key and the publisher's WKD infrastructure, and presents the same forged key through each, the cross-check passes and a forged release is accepted. This is the residual risk the model is explicitly trying to make expensive — it requires compromising two independent systems at once — but it is not eliminated.
  • Availability degradation. A consumer running fail-open (RequireAll: false) accepts that a WKD outage drops back to the surviving anchor (e.g. embedded only), logging a warning. That narrows the cross-check to a single anchor for the duration of the outage. Fail-closed (RequireAll: true) trades availability for keeping the cross-check mandatory. Choosing between them is a deployment-level risk decision.

Out of scope

  • Confidentiality. signing provides integrity and authenticity, not secrecy. Manifests, signatures, and public keys are not secret.
  • Private-key custody. Protecting the signing key is the backend's and operator's job. The reference local backend reads an unencrypted PEM from disk and is intended for development and onboarding, not hardened production custody — a KMS/HSM backend exists for that.
  • Revocation and rotation policy. The library verifies against the keys in the trust set; deciding when to rotate or distrust a key, and propagating that decision, is the operator's responsibility.
  • Endpoint and supply-chain security of the consumer. Compromise of the machine running verification, or of the consumer's own build, is outside what signature verification can address.

The library's own distribution integrity

A subtle but important separation: the integrity signing provides (verifying someone else's release) is distinct from the integrity of signing itself as a dependency you pull in.

signing is distributed as a Go module. Its own integrity is established by the Go toolchain — go.sum pins the cryptographic hash of every module version, and the public checksum database independently corroborates those hashes on first download. That is what assures you the signing code you build against has not been tampered with in transit or substituted.

This is a separate mechanism from the OpenPGP/WKD trust model the library implements for your releases. Go module checksums secure the library's supply chain to you; the trust model secures your releases to your users. Conflating the two would be a mistake: a valid go.sum entry says nothing about whether a release manifest verifies, and a verified manifest says nothing about how the verifying code was obtained.

See also