Skip to content

Sign with the local backend

Sign a checksums manifest using a private key held in a PEM file on disk, via the reference local backend. This is the library path; in production, release signing is normally driven by a CLI such as gtb sign, which wraps these same calls.

The local backend is the lightweight default. It is ideal for onboarding, local development, and reproducible examples — but a private key on disk is a weaker posture than a KMS or HSM. For production you typically implement a custom backend (AWS KMS, GCP, Azure, HSM) instead.

Activate the backend

Backends register themselves on import. Blank-import the local package so its init() registers the name "local":

import (
    "context"

    "gitlab.com/phpboyscout/signing"
    _ "gitlab.com/phpboyscout/signing/local" // registers "local"
    "gitlab.com/phpboyscout/signing/openpgpkey"
)

Point at a PEM key

Generate (or reuse) an unencrypted RSA private key of at least 3072 bits:

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out release.pem

The keyID you pass to the local backend is simply the path to this PEM file.

Supported formats:

  • PKCS#1 — -----BEGIN RSA PRIVATE KEY-----
  • PKCS#8 — -----BEGIN PRIVATE KEY-----

Limitations (all surface as typed sentinels from the local package):

  • Encrypted PEM is unsupportedlocal.ErrEncryptedPEMUnsupported. Decrypt out of band first, or use a KMS/HSM backend.
  • Non-RSA keys are unsupportedlocal.ErrUnsupportedKeyType.
  • A file with no PEM block yields local.ErrMissingPEMBlock.

Resolve, sign, publish

Resolve the backend through the registry, build a crypto.Signer from the PEM path, then mint the public key and sign the manifest:

backend, err := signing.Get("local") // requires the blank import above
if err != nil {
    return err // e.g. signing.ErrUnknownBackend if not imported
}

signer, err := backend.NewSigner(context.Background(), "release.pem") // keyID = PEM path
if err != nil {
    return err // e.g. local.ErrEncryptedPEMUnsupported
}

now := time.Now()
pub, err := openpgpkey.ArmoredPublicKey(signer, "Release", "[email protected]", now)
if err != nil {
    return err
}

manifest := []byte("sha256  ffmpeg.wasm  0xc0ffee\n")
sig, err := openpgpkey.DetachSign(signer, pub, bytes.NewReader(manifest), now)
if err != nil {
    return err
}
// publish: manifest, sig (ASCII-armoured detached signature), and pub

Ship manifest, sig, and pub with your release assets. Consumers embed pub and run the verify recipe.

Note that ArmoredPublicKey and DetachSign take any crypto.Signer — the exact same code signs whether the key lives in a PEM file or a remote KMS. Only the backend differs.

See also