Featured image of post Publish your key where the platform can't touch it

Publish your key where the platform can't touch it

If the platform hosting your code also hands out the key that verifies it, the signature proves nothing. Where to publish a public key instead.

Signing your releases proves someone with the private key signed them. It says nothing about whether that key is actually mine, and that second part is the bit that protects anybody. Because a signature you can’t trace back to the right public key proves nothing at all. It just tells you something was signed by someone.

So ask the obvious follow-up. When a verifier wants to check one of my signatures, where do they get my public key? For most projects the answer is some flavour of “the platform.” The key’s in the repo, or attached to the host’s notion of my identity, or sitting on a keyserver I pushed it to once. And in the common case, the place that hands out the verifying key is the very same place that hosts the code being verified.

Sit with that for a second, because it’s a circle. The whole reason you sign is so that someone can check your release independently of the host, in case the host is compromised, in case a release gets tampered with in transit, in case you simply don’t want “trust the platform” to be the entire security story. But if the key they verify against is also served by the platform, then an attacker who owns the platform for an afternoon doesn’t need to forge your signature. They just swap your public key for one of theirs, re-sign the tampered artifact, and every check comes back green. The thing meant to keep the platform honest was handed to you by the platform. You’ve checked the host’s work using the host’s answer key.

The fix is to publish the verifying key somewhere the code host doesn’t control, and OpenPGP has had the mechanism for this for years: Web Key Directory. WKD is a convention for “given an email address, here is exactly where to fetch that person’s public key, on their own domain, over HTTPS.” gtb keys wkd builds that directory for you:

gtb keys wkd \
    --domain phpboyscout.uk \
    --output ./wkd-staging \
    signing-key-v1.asc

What comes out is a small tree you upload to any static host: .well-known/openpgpkey/<domain>/hu/<hash>, where the hash is a z-base-32-encoded SHA-1 of the local-part of the key’s email. (The SHA-1 there isn’t doing security work, before anyone winces. It’s a bucket id, the IETF-mandated way to turn matt into a fixed directory name a client can compute and go fetch. It’s addressing, not a signature.) Alongside it sits the zero-byte policy file the spec requires, and the tool emits both the advanced layout, the default, namespaced under the domain, and can fall back to the direct one that older clients expect. It’s all pure Go, with no shell-out to gpg-wks-client, which is the same instinct as the rest of the signing work: do the awkward thing in the binary, the same way every time, rather than at the mercy of whatever’s installed.

Here’s what that buys. Once my key lives at phpboyscout.uk/.well-known/openpgpkey/..., the trust root for that key is my domain. My DNS, my TLS certificate. A WKD-aware verifier, given my email, computes the path and fetches the key straight from there. The platform that hosts my source has no part in it, and can’t substitute a key it never served. To hand someone a fake key now, an attacker has to compromise my domain, my DNS or my certificate authority, which is a completely separate attack from compromising my git host or my repo. I’ve pulled “who vouches for my verifying key” apart from “who hosts my code,” and made them two different things that would both have to fall.

The piece that makes this usable rather than ceremonial is that go-tool-base also has the client side: a WKDResolver that fetches and verifies keys this way, so a tool built on the framework can both publish its key by WKD and check other people’s keys by WKD. The generator and the resolver are two ends of the same idea.

None of this replaces the signing itself, which has its own story about a signature the hosting platform can’t forge, nor the question of where the signing key physically lives. It’s the third leg of the same stool, and it’s the one people forget. You can keep your private key in a hardware module the cloud can’t read, sign with an identity the platform can’t fake, and still have the whole edifice defeated if everyone fetches your public key from a place the attacker controls. Where the verifying key lives is not a detail. Publish it somewhere that’s yours.

Built with Hugo · Theme Stack designed by Jimmy