Skip to content

Verify a release

Verify a detached OpenPGP signature over a checksums manifest against a public key you embed at build time. This is the surface a consumer such as afmpeg uses.

For a step-by-step introduction, see Sign and verify a release; this guide is the task-focused recipe.

Embed the publisher's key

Bake the publisher's armoured public key into your binary so verification needs no network access and no external trust store:

//go:embed keys/release.asc
var releaseKey []byte

Verify the manifest

Load the key into a TrustSet and verify. A nil error means the manifest was signed by a key in the set and has not been altered.

trust, err := verify.LoadTrustSet(releaseKey)
if err != nil {
    return err // e.g. verify.ErrWeakKey — see below
}

if err := trust.VerifyManifestSignature(manifest, signature); err != nil {
    return err // verification failed — do not trust the assets
}
// verified: now check each asset's checksum against the manifest

manifest and signature are the bytes you downloaded alongside the release (signature is the ASCII-armoured detached signature produced by openpgpkey.DetachSign).

Verifying the signature establishes that the manifest is authentic. You still hash each downloaded asset and compare it to the manifest line to confirm the asset itself is intact.

Get the signer fingerprint

When you need to know which key signed — for logging, audit, or pinning to a specific rotation — use the Signer variant. It returns the signer's fingerprint on success:

fingerprint, err := trust.VerifyManifestSignatureSigner(manifest, signature)
if err != nil {
    return err
}
log.Printf("release signed by %s", fingerprint)

On failure the returned fingerprint is empty. To enumerate every key the trust set will accept (for example, to display pinned anchors at startup), call:

for _, fp := range trust.Fingerprints() {
    log.Printf("trusted signer: %s", fp)
}

Fingerprints are returned as sorted upper-case hex, 40 characters each.

The fingerprint you get back is always the primary key's, even when a signing subkey produced the signature. Pinning it therefore pins the identity, not the particular subkey that signed — which is usually what you want, since subkeys rotate under a stable primary.

Handle a weak key

LoadTrustSet enforces the minimum-strength policy at construction time. An RSA key below 3072 bits fails fast with verify.ErrWeakKey:

trust, err := verify.LoadTrustSet(releaseKey)
if errors.Is(err, verify.ErrWeakKey) {
    return fmt.Errorf("embedded release key is too weak to trust: %w", err)
}

This is a build-time fact about your embedded key, so it should surface during development, not in the field. See the strength policy for the rationale.

Handle verification failure

VerifyManifestSignature returns a non-nil error whenever trust cannot be established — a tampered manifest, a signature from a key outside the set, or a malformed/empty signature. Treat any error as "do not trust" and refuse the release:

if err := trust.VerifyManifestSignature(manifest, signature); err != nil {
    return fmt.Errorf("refusing release, signature did not verify: %w", err)
}

Never proceed to install or use the assets when verification fails — fail closed.

What a single error value is hiding

ErrSignatureInvalid is deliberately broad. It covers a forged signature, a malformed or empty one, a signature from a key outside the set — and also everything go-crypto decides about the key material itself: a revoked primary key, a revoked identity, a revoked or expired signing subkey, an expired key, an expired signature, and an RSA key below the strength floor. Expiry is judged against the current clock at the moment you verify.

The underlying reason is folded into the message text, not the error chain, so errors.Is against a go-crypto sentinel will not match. If you need to tell "your key expired last week" from "someone forged this", read the message — and refuse the release either way.

Nothing here names which key rejected the signature. That is deliberate: a caller logging only the sentinel leaks nothing about the trust set.

See also