Skip to content

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:

go run .

Success criterion

The program prints:

verified

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