Writ

verify an openpgp message

2020-01-02

I’m not hugely knowledgeable about the intricacies of openpgp / gpg. I think most understand the concept of signing, encrypting, hashing, and the operations needed for those. But when it comes to the web of trust, ascii armor, and all the errata that gpg/pgp pile on top; it’s doubtful that many muddle through it.

If anything, most probably rely on window/cli tools that prompt them for passwords to keyrings, and just make a setup that "just works" on their machine.

I wanted to build a warrant canary checker, for rsync.net, where I keep some data. Their warrant canary is kept up to date each week, but I don’t check it each week, so I built a checker. I initially thought about doing it as a shell script, but scrapped it once I outlined even the functions i’d need. So it’s in Go.

The goal was to use a specific public key to verify the signature on a specific message.

Openpgp

Differences between ascii armor, clearsign.

openpgp package

Go naturally has an openpgp package, with quite complete support. Unfortunately, its examples are very limited, and googling gives very few details on how to do much of anything.

I found a couple gists detailing similar applications. And they worked great to get the public key loaded into a standalone keyring with openpgp.ReadArmoredKeyRing. But nothing about actually verifying the signatures worked for me; I kept getting openpgp: invalid data: tag byte does not have MSB set. Which is infuriating, because it’s plain text, how could it have a most significant byte problem?

A walk down this post helped clarify the isue.

Naturally, I was assuming the wrong formats for things. GPG/PGP has a notion of "clearsign", which is clear-text with signatures. That’s the data that rsync.net uses, and what i was trying to verify. Using clearsign.Decode, whose docs have a note about using openpgp.CheckDetachedSignature afterwards, worked great. You even get back a armor.Block representing the signature, which is all that’s needed.

I also knew the keyring didn’t need to prompt for input, so a quick no-op openpgp.PromptFunction takes care of that;

func dontPrompt(keys []openpgp.Key, symmetric bool) ([]byte, error) {
	return []byte{}, errors.New("Key required prompting, this should never happen")
}
All site content protected by CC-BY-4.0 license