title: Verifying a release description: How afmpeg certifies an ffmpeg-wasi release: the OpenPGP-signed checksums, the embedded signing key, the WKD second anchor, and what each layer does and does not defend. date: 2026-06-30 tags: [explanation, security, releases, signing] authors: [Matt Cockayne matt@phpboyscout.uk]
Verifying a release¶
afmpeg loads executable WebAssembly. Where those bytes come from matters, so afmpeg offers two acquisition paths with deliberately different trust postures (spec 0010):
WithModuleURL: uncertified, for a module you host or build yourself. Integrity is the caller-suppliedWithSHA256. afmpeg can't vouch for bytes it didn't publish.WithModuleRelease: certified, for the project's own published releases. This page is about that path.
Verification reuses the org signing module, gitlab.com/phpboyscout/go/signing (the same
OpenPGP/WKD machinery go-tool-base uses) rather than any afmpeg-specific crypto.
The chain¶
Every ffmpeg-wasi release publishes checksums.txt (the SHA-256 of every asset, including
provenance.json) and checksums.txt.sig, an ASCII-armored OpenPGP detached signature over
checksums.txt, produced by gtb sign from a key held in AWS KMS. WithModuleRelease
verifies, in order:
- The signature, against the release-signing public key embedded in afmpeg
(
signing/verify'sVerifyManifestSignature). The private key lives in AWS KMS and can be wielded only by ffmpeg-wasi's tagged-release CI job (via GitLab OIDC). No human, no long-lived credential. OpenPGP identifies the signing key by fingerprint; afmpeg's embedded key is the trust anchor. - The module's checksum, read from the now-trusted
checksums.txt. provenance.json's checksum, binding it into the signed set.- Provenance agrees with the variant you requested (e.g.
VariantLGPL↔ffmpeg-wasi-lgpl.wasm).
Only then is the module compiled. Each failure is its own typed error:
signing/verify.ErrSignatureInvalid, ErrChecksumMismatch, ErrProvenanceMismatch. Because the
signature covers checksums.txt and checksums.txt covers everything else, one signature
certifies the whole release.
The second anchor: WKD (spec 0011)¶
The embedded key is pinned in the binary, but where did that key come from? On every online
fetch afmpeg adds a second, independent check: it fetches the signing key from the Web Key
Directory on openpgpkey.phpboyscout.uk and requires it to match the embedded key by
fingerprint (signing/verify's composite resolver). The domain is a control plane administered
separately from GitLab and AWS, so the key has an anchor that does not depend on the platform that
hosts the releases.
- A fingerprint mismatch (embedded vs WKD) is a hard failure, and a tamper signal.
- A WKD outage degrades gracefully to the pinned embedded key (which is already a strong anchor): a transient domain problem never blocks a legitimate load.
- The offline-bundle path (
WithReleaseBundleDir) skips WKD entirely, since it must not touch the network.WithReleaseWKDEmailoverrides the WKD identity (for a mirror) or disables it ("").
There are two kinds of OpenPGP key in the model, with distinct roles. The signing
identity ([email protected]) is what afmpeg embeds and cross-checks, and it
signs releases. More than one key can carry that identity at once: afmpeg currently pins two, and
a release is signed by both, which is how a rotation ships without invalidating anything.
The rotation-authority key ([email protected]) is a shared, offline break-glass key
that certifies and rotates signing keys; it never signs releases and is not in afmpeg's
runtime trust set (afmpeg pins the signing key directly, so the rotation key adds no runtime
verification; it is org infrastructure).
Why these choices¶
- The trust root ships in afmpeg. The signing key is pinned (embedded), so verification is non-circular: you never fetch only the key you're verifying against. Rotation ships a new embedded key in an afmpeg release (old + new can overlap), and the WKD bucket is republished.
- A dedicated key, not a shared one. ffmpeg-wasi signs with its own key, never go-tool-base's A shared key would let one project's pipeline forge the other's releases (spec 0010 D-0010-D).
- No skip. Verification is mandatory on this path; air-gapped use is served by
WithReleaseBundleDir(verify a local directory of pre-downloaded assets), which still verifies the OpenPGP signature fully. There is no "trust me" switch to misuse.
What this does, and does not, defend¶
The signature defends against a swapped or tampered artifact: leaked credentials, the apply runner, and non-tag pipelines all cannot produce a valid signature. The WKD cross-check adds key/registry-substitution defense (an attacker must compromise both the release platform and the independently-administered domain) and independent key distribution (third parties discover the key from the domain, not the repo).
What no signing scheme here closes is a compromised GitLab account that can push a tag: that triggers the legitimate release pipeline, which signs a genuine-but-malicious build with the real key. That is the domain of GitLab account hardening (protected tags, 2FA, required approvals) and reproducible builds are out of scope, and stated plainly rather than papered over. The WKD anchor narrows the attack surface; it does not eliminate that case.
Verifying by hand¶
You don't need afmpeg (or Go) to check a release. See
Verify a release by hand: fetch the keys from WKD,
verify the OpenPGP signature over checksums.txt, then sha256sum -c.
The pinned fingerprints, the exact asset and provenance names, and where verified bytes are cached are in engine releases.