Featured image of post Release trust without the framework

Release trust without the framework

A few days ago I shipped afmpeg and ffmpeg-wasi, a way to run FFmpeg as a WebAssembly module straight from Go with nothing installed on the host. afmpeg fetches that wasm module at runtime and then… runs it. A lovely trick, right up until you ask the obvious question: how does afmpeg know the wasm it just pulled off the internet is the one I built, and not something swapped in on the way down?

It has to check a signature. And I already had the machinery to do exactly that… it was just bolted to the inside of go-tool-base.

So I pulled it out. Two new modules, both public: signing and signing-aws-kms.

The part I lifted out

signing is the OpenPGP/WKD signing-and-verification model the rest of my tools already lean on. It’s the same code behind gtb update when it checks its own releases, lifted out into a standalone module you can drop into any project. Verify a signed release, or sign your own, and you do it without dragging the whole go-tool-base framework (and its dependency tree, which is not small) in behind it.

That last bit is the whole reason it’s a separate module and not just a package. Go has a rule here that trips people up: when your code imports a package, you inherit that package’s entire module dependency list… the full go.mod, not just the corner you actually touched. So lifting one tidy little package out of go-tool-base into a sub-package would still have handed every consumer viper, OpenTelemetry, the whole charm stack, the lot. Only a separate module, with its own minimal go.mod, keeps that weight off your build. The core here leans on ProtonMail’s go-crypto and cockroachdb/errors, and nothing else.

The backend you inject

Signing needs a key, and keys live in awkward places: a PEM file on disk, a YubiKey, AWS KMS, GCP, Azure, a Vault. The remote ones drag in heavy SDKs. The AWS KMS client alone pulls in 57 modules, and I really didn’t fancy that sitting in the core where everyone pays for it whether they ever touch KMS or not.

So signing doesn’t implement those backends at all. It defines an interface (a thing that hands back a crypto.Signer) and lets you inject whichever backend you actually use. A light local backend (a PEM key on disk) ships in the box, as a sensible default and a worked example of the shape. The heavy ones are separate modules you opt into, one at a time.

signing-aws-kms is the first of them. It wraps a KMS-held key as a signer, so the private half never leaves AWS and every signature is a round-trip to the cloud. Blank-import it and it registers itself. GCP, Azure and Vault will follow the same pattern, and because each is its own module, your binary only ever carries the one you reached for. And if none of them fit? The interface is right there for you to write your own.

The proof, already running

This isn’t a library out looking for a user. ffmpeg-wasi signs its release assets in CI right now, with a KMS key driven through the go-tool-base CLI, and afmpeg verifies that signature against an embedded key before it hands a single byte to the runtime.

No valid signature, no run.

The thing that needed the trust and the thing that provides it are two separate projects, talking to each other through a signature.

The verifying key isn’t only baked into afmpeg, either. It’s published to WKD on my own domain, so the anchor you check against lives somewhere my git host has no say over. A signature is only ever worth as much as the key you check it against, and pinning that to my own domain rather than the platform that hosts my code is a deliberate bit of the posture, not an accident of wherever a file happened to be convenient to drop.

Where it leaves things

Two more modules out in the world, both public, both documented over at signing.phpboyscout.uk. The selfish win is that afmpeg got to trust its own downloads without me reinventing a single thing to do it. The broader one is that the signing model I keep banging on about is no longer something you have to swallow my entire framework to use. Take the part you want, and leave the rest on the shelf.

Built with Hugo
Theme Stack designed by Jimmy