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.
signingprovides 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
localbackend 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¶
- The trust model — the composite anchor design and the RSA-3072 policy.
- Dependency inversion — why the dependency graph stays small, reducing supply-chain surface.
- Configure trust — choosing fail-open vs fail-closed in practice.