Getting started¶
This tutorial walks you through verifying your first signed release with
signing, end to end. By the end you will have produced a signed checksums
manifest and confirmed that a TrustSet accepts it — the exact flow a consumer
like afmpeg uses to verify ffmpeg-wasi's release assets.
You will do everything in one self-contained Go program so you can watch each moving part. In real deployments the signing half happens on the publisher's machine and the verifying half happens in your tool; here we run both so the round-trip is visible.
Prerequisites¶
- Go 1.26 or newer.
- A new module to experiment in:
mkdir verify-tutorial && cd verify-tutorial
go mod init example.test/verify-tutorial
go get gitlab.com/phpboyscout/signing
Step 1 — A signing key¶
In production the publisher holds the private key (often in a KMS); you only
ever see the public key. For this tutorial we generate an RSA key so we can
play both roles. The key must be at least 3072 bits — signing rejects weaker
RSA keys when a trust set is built (see the strength policy).
priv, _ := rsa.GenerateKey(rand.Reader, 3072)
now := time.Unix(0, 0) // a fixed time keeps this example reproducible
Step 2 — Publish an armoured public key¶
The publisher exports an ASCII-armoured OpenPGP public key. This is what you embed in your verifier at build time.
pub, _ := openpgpkey.ArmoredPublicKey(priv, "Release", "[email protected]", now)
pub is a []byte containing a -----BEGIN PGP PUBLIC KEY BLOCK----- envelope.
Step 3 — Sign a checksums manifest¶
A release does not sign each asset individually. Instead the publisher writes a checksums manifest (one line per asset) and signs that single file. Here is a one-line manifest and its detached, armoured signature:
manifest := []byte("sha256 ffmpeg.wasm 0xc0ffee\n")
sig, _ := openpgpkey.DetachSign(priv, pub, bytes.NewReader(manifest), now)
DetachSign returns an ASCII-armoured detached signature — it does not modify
the manifest. The publisher ships manifest, sig, and pub alongside the
release assets.
Step 4 — Build a trust set and verify¶
Now switch hats: you are the consumer. Load the publisher's public key into a
TrustSet, then verify the manifest against the signature.
trust, _ := verify.LoadTrustSet(pub)
err := trust.VerifyManifestSignature(manifest, sig)
if err != nil {
log.Fatalf("verification failed: %v", err)
}
fmt.Println("verified")
The complete program¶
package main
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"fmt"
"log"
"time"
"gitlab.com/phpboyscout/signing/openpgpkey"
"gitlab.com/phpboyscout/signing/verify"
)
func main() {
priv, err := rsa.GenerateKey(rand.Reader, 3072)
if err != nil {
log.Fatal(err)
}
now := time.Unix(0, 0)
pub, err := openpgpkey.ArmoredPublicKey(priv, "Release", "[email protected]", now)
if err != nil {
log.Fatal(err)
}
manifest := []byte("sha256 ffmpeg.wasm 0xc0ffee\n")
sig, err := openpgpkey.DetachSign(priv, pub, bytes.NewReader(manifest), now)
if err != nil {
log.Fatal(err)
}
trust, err := verify.LoadTrustSet(pub)
if err != nil {
log.Fatal(err)
}
if err := trust.VerifyManifestSignature(manifest, sig); err != nil {
log.Fatalf("verification failed: %v", err)
}
fmt.Println("verified")
}
Run it:
Success criterion¶
The program prints:
That is your success signal: the manifest was signed by the key in your trust
set, and not altered since. To prove the check is real, change a byte in
manifest after signing (for example, edit the checksum) and re-run — the
program now exits with a verification error.
Next steps¶
- Recipe form, including reading the signer fingerprint and handling failures: Verify a release.
- Sign your own manifests: Sign with the local backend.
- Add a second, independent trust anchor via WKD: Configure trust.
- Why the composite cross-check matters: The trust model.