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/go/signing"
_ "gitlab.com/phpboyscout/go/signing/local" // registers "local"
"gitlab.com/phpboyscout/go/signing/openpgpkey"
)
Point at a PEM key¶
Generate (or reuse) an unencrypted RSA private key of at least 3072 bits:
The keyID you pass to the local backend is simply the path to this PEM
file.
Supported formats:
- PKCS#1 —
-----BEGIN RSA PRIVATE KEY-----(RSA) - PKCS#8 —
-----BEGIN PRIVATE KEY-----(RSA or Ed25519)
Two key types, matching the two signing paths:
| Key type | Path |
|---|---|
| RSA | OpenPGP signing — checksum manifests |
| Ed25519 | minisign artefact signing — what the Rust consumers verify |
Ed25519 here is what makes the artefact path usable without an HSM: try it, test it, or run it in a project that has no cloud KMS at all.
Limitations (all surface as typed sentinels from the local package):
- Encrypted PEM is unsupported —
local.ErrEncryptedPEMUnsupported. Decrypt out of band first, or use a KMS/HSM backend. - Only RSA and Ed25519 are supported — anything else (ECDSA, for
instance) returns
local.ErrUnsupportedKeyType. Nothing in this estate verifies ECDSA, so accepting it would be a trap rather than a feature. - A file with no PEM block — or a PEM block of an unrecognised type — yields
local.ErrMissingPEMBlock. - The path is read as given: symlinks are followed and nothing is canonicalised. Fine for an operator-supplied path, worth knowing if it could come from anywhere less trusted.
A key on disk is a key that can be copied. This backend is for tutorials, tests, and projects without an HSM; where custody matters, use
aws-kms.
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", "release@example.test", 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¶
- Sign artefacts with minisign — what the Ed25519 half of this backend is for.
- Implement a custom backend — for KMS/HSM signing in production.
- The backend registry —
Get,Names, and whatkeyIDmeans per backend. - Dependency inversion — why backends are injected rather than bundled.
- The full API and runnable
Exampletests: pkg.go.dev/gitlab.com/phpboyscout/go/signing/local.