Skip to content

Release signing

Every ffmpeg-wasi release is signed, so a consumer can be sure the module they load is the one this project published, not a substitute. The mechanism is deliberately small and auditable.

What is signed

The release publishes checksums.txt (the SHA-256 of every asset (the .wasm modules and the native drivers alike, including provenance.json) and a detached signature over it, checksums.txt.sig. Because the signature covers checksums.txt, and checksums.txt covers everything else, one signature certifies the whole release: verify the signature, then check any asset against its line in checksums.txt.

checksums.txt.sig is an ASCII-armored OpenPGP detached signature, the same format the rest of the org uses (go-tool-base's gtb update), so any OpenPGP tool (gpg --verify, or afmpeg via the gitlab.com/phpboyscout/signing module) can verify it. The signature self-identifies the signing key by its fingerprint.

How it is signed, and why only the tag pipeline can

The signing key is an asymmetric AWS KMS key (RSA-4096, SIGN_VERIFY). Its private half never leaves KMS. There is no key file, and no human ever holds it. The tag-gated sign CI job assumes an IAM role via GitLab OIDC, then runs sigillum (the org signing CLI): sigillum keys mint derives the OpenPGP public key from the KMS key, and sigillum sign produces the detached signature; KMS performs every private-key operation. The role's trust policy is pinned to this project's release tags (project_path:phpboyscout/ffmpeg-wasi:ref_type:tag:ref:n*). So:

  • a leaked credential cannot sign, because there is no static credential;
  • the infra apply pipeline cannot sign; it can manage the key resource but has no kms:Sign;
  • branch, MR, and non-n*-tag pipelines cannot sign, because they fail the OIDC subject filter.

The key is provisioned in phpboyscout/infra (src/main.signing-kms.tf) and is dedicated to ffmpeg-wasi, not shared with any other project, so no other project's pipeline can ever produce an ffmpeg-wasi signature.

The keys

Two OpenPGP keys back the chain (the go-tool-base model):

  • the signing key ([email protected]), minted from the KMS key alias/ffmpeg-wasi-release-signing-v2. OpenPGP fingerprint 4C96ECB35C7446619FF78EB1ED1344E576B7BBBF. Signs every release since 2026-07-24. Its predecessor ([email protected], 710881C1DDAEABD138E53004A2166E59EB6060E1) signed every release before n8.1.2-11 alone and shared the signature with v2 from n8.1.2-11 until September 2026; afmpeg still trusts it so those releases verify, see Rotation.
  • the shared org rotation-authority key ([email protected], 2B26658409047ED08B56CEBDCF5B8DBB5D9F19C2), an offline break-glass key that certifies the signing key and authorises rotation. One per org, never used in normal operation, and never a release signer.

The primary consumer, afmpeg, embeds and pins the signing key (and cross-checks it against WKD), so its WithModuleRelease verifies releases automatically: the trust root ships inside the verifying binary, which is stronger than any key fetched at runtime. The rotation-authority key stays offline org infrastructure: it backs key rotation, not afmpeg's runtime trust set (afmpeg pins the signing key directly).

Deliberately, the public key is not published in this repository: a key you fetch from the same platform that hosts the releases is not an independent anchor, since a compromise of that platform would control both. Its authoritative public location for third-party verification is a Web Key Directory on phpboyscout.uk (a control plane independent of GitLab), which is the subject of afmpeg spec 0011 (a committed fast-follow). A by-hand verification guide ships with it.

What it defends, and what it does not

The signature defends against a swapped or tampered artifact: leaked credentials, a compromised apply runner, and non-release pipelines all cannot produce a valid signature.

It does not defend against a compromised GitLab account that can push a tag. That triggers the legitimate release pipeline, which would sign a malicious build with the real key. No signing scheme closes that "poisoned well"; it is the domain of GitLab account hardening (protected tags, 2FA, required approvals) and reproducible builds, out of scope here. What the WKD second anchor (afmpeg spec 0011) does add is key/registry-substitution defense: afmpeg cross-checks its embedded key against a copy served from the phpboyscout.uk domain (a control plane independent of GitLab), so an attacker would have to compromise both. Stating the limit plainly is part of the posture.

Rotation, and why a release may carry two signatures

The key alias is versioned (alias/ffmpeg-wasi-release-signing-v2 today). Rotation mints a new key, publishes its public half under a new WKD identity, and adds it to afmpeg's pinned set alongside the old one; releases are signed with both for an overlap window, then the old key stops signing. There is no flag-day, and a compromised key can be dropped promptly.

The first rotation ran from 2026-07-24 to September 2026, when the AWS account holding the v1 key was closed. Releases cut inside that window carry two detached signatures in the same armored checksums.txt.sig, because gtb sign --append merges the second one:

  • It is still one file over the same checksums.txt.
  • Verifying with either key succeeds. An OpenPGP verifier skips signature packets from an issuer it does not know, so an afmpeg build that pins only v1 verifies those releases exactly as before, and one that pins both verifies with whichever it holds.
  • Neither key is preferred. They certify identical bytes.

Releases since the window carry the v2 signature alone. The v1 public key stays in afmpeg's trust set for as long as the releases it signed should verify, and the WKD entry for [email protected] is never edited: an afmpeg build cross-checks its embedded set against the WKD set for its compiled-in identity and requires them to agree exactly, so a new trust set always gets a new identity.

If you verify by hand a release from the window, expect gpg --verify to report an unknown-key signature alongside the one it can check. That is the overlap, not a tampered manifest.

The tooling is MIT

build/sign-release.sh only orchestrates: it shells gtb keys mint / gtb sign. Like the rest of build/ it is MIT, and it links nothing: the signature is over text, the key is in KMS.