Skip to content

signing

OpenPGP/WKD release signing and verification as a light, dependency-inverted Go library.

signing is the same trust model that backs gtb update, extracted so any project can verify signed releases (or sign its own) without pulling in the go-tool-base framework.

import "gitlab.com/phpboyscout/go/signing/verify"

The light-footprint promise

The module graph is deliberately tiny. Outside its tests, go.mod declares three dependencies: go-crypto, the toolkit's own dependency-free go/errors, and golang.org/x/crypto for the BLAKE2b hash the minisign package needs. No cloud SDK, no CLI library, no go-tool-base: a guard test fails if go-tool-base, the AWS SDK, OpenTelemetry, Cobra, Viper, pflag or charmbracelet appears in the build graph.

A verify-only consumer imports verify, and reaches all three. The direct x/crypto requirement is minisign's BLAKE2b, but go-crypto's own OpenPGP code pulls argon2, cast5, hkdf and sha3 out of the same module whether minisign is imported or not.

Heavy or remote signing backends (AWS KMS, GCP, Azure, an HSM) are defined as an interface and injected by the consumer, so they never enter your dependency graph unless you opt in. See Why backends are injected.

The library leans on the standard library at every seam:

  • crypto.Signer for keys (an in-memory RSA key and a remote KMS handle work alike),
  • *slog.Logger for logging (nil disables it),
  • *http.Client for WKD fetches (BuildKeyResolver substitutes a stdlib client with a 30-second timeout for nil; NewWKDResolver requires one).

Who it is for

  • Consumers verifying signed releases, such as afmpeg checking the signed wasm release assets that ffmpeg-wasi publishes.
  • Any non-framework Go project that wants release-asset integrity without adopting a CLI framework.
  • Publishers signing their own release manifests via the reference local backend or a custom KMS/HSM backend.
  • Publishers whose consumers are Rust tools. cargo-binstall and rtb-update verify minisign signatures over individual artefacts, which the minisign package produces.

Sign and verify in ten lines

priv, _ := rsa.GenerateKey(rand.Reader, 3072)
now := time.Unix(0, 0)
pub, _ := openpgpkey.ArmoredPublicKey(priv, "Release", "release@example.test", now)
manifest := []byte("sha256  ffmpeg.wasm  0xc0ffee\n")
sig, _ := openpgpkey.DetachSign(priv, pub, bytes.NewReader(manifest), now)

trust, _ := verify.LoadTrustSet(pub)
err := trust.VerifyManifestSignature(manifest, sig) // nil == verified

The first five lines play the publisher so the example is self-contained. In production you embed the publisher's armoured public key at build time rather than generating one, and the last two lines are unchanged.

Where to go next

The documentation follows the Diátaxis framework:

The generated Go API (signatures, doc comments and runnable Example tests) lives on pkg.go.dev. Each backend module has its own there too, for instance signing-aws-kms.

Further reading

The blog carries a curated route through this subject: Signing your releases collects everything written about it, ordered so you can start at the beginning rather than newest-first.

Ask phpbotscout

phpbotscout

He answers questions about the projects over on the Discord, citing the docs where they already cover it, and offering to raise an issue where they don't. Bring a bug, an idea, or a questionable engineering decision.

Join the Discord