signing

package module
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 8 Imported by: 4

README

signing

OpenPGP/WKD release signing & verification for Go, light and framework-free

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit: small, framework-free Go modules extracted from go-tool-base. Docs: signing.go.phpboyscout.uk


gitlab.com/phpboyscout/go/signing is OpenPGP/WKD release signing and verification as a light, dependency-inverted Go library.

It is the estate's signing and verification model (the same verify package that gtb update checks releases with), extracted so a project can verify signed releases or sign its own without pulling in the go-tool-base framework.

Design

  • Light core. Three non-test dependencies: ProtonMail/go-crypto, the toolkit's own dependency-free go/errors, and golang.org/x/crypto for the BLAKE2b hash the minisign package needs. A guard test (depfootprint_test.go) fails if go-tool-base, the AWS SDK, OpenTelemetry or a CLI library (Cobra, Viper, pflag, charmbracelet) ever enters the build graph.
  • Dependency-inverted backends. The library defines a Backend interface that produces a crypto.Signer. Heavy or remote backends (AWS KMS, an HSM) live in their own modules and are injected by the consumer. A light local (PEM) backend ships here as the default and the reference implementation.
  • Stdlib seams. crypto.Signer for keys, *slog.Logger for logging, *http.Client for WKD fetches. Inject your own; BuildKeyResolver falls back to a 30-second-timeout client, and a nil logger disables logging.
  • Verification. A detached OpenPGP signature over a checksums manifest, checked against keys embedded in your binary and, optionally, cross-checked against the same key fetched over WKD.
  • Two signature formats. OpenPGP over a checksums manifest for consumers that speak GPG; prehashed minisign over individual artefacts for Rust consumers (cargo-binstall, rtb-update) that do not.

Packages

Package Purpose
gitlab.com/phpboyscout/go/signing The Backend contract, the process-global backend registry, and named instances (RegisterFactory, NewInstance)
gitlab.com/phpboyscout/go/signing/verify Trust sets, key resolvers (embedded, WKD, composite), detached-signature verification
gitlab.com/phpboyscout/go/signing/openpgpkey Mints OpenPGP public keys (RSA only), produces detached signatures, writes a WKD tree
gitlab.com/phpboyscout/go/signing/minisign Produces and verifies prehashed minisign signatures over artefacts, for Rust consumers (Ed25519 only)
gitlab.com/phpboyscout/go/signing/local The local backend: an unencrypted PEM key on disk (RSA or Ed25519)

Generated testify mocks for Backend and verify.KeyResolver ship under mocks/; see the reference overview.

Backends

Keys live behind a Backend (a crypto.Signer producer). The light local (PEM) backend ships here; other providers are separate modules activated by a blank import, so you pull only the SDK(s) you use:

Backend Module Keys keyID is
local gitlab.com/phpboyscout/go/signing/local Unencrypted PEM: RSA (PKCS#1/PKCS#8) or Ed25519 (PKCS#8) a filesystem path
aws-kms gitlab.com/phpboyscout/go/signing-aws-kms RSA_4096 SIGN_VERIFY, or ECC_NIST_EDWARDS25519 a key ARN or alias

Both register a factory as well, so a binary can run several named instances of one type (local:staging, aws-kms:prod) through signing.NewInstance. No other provider backend exists yet; GCP KMS, Azure Key Vault or HashiCorp Vault would follow the same pattern. See Backends and the per-provider module pattern, the backend registry and Implement a custom backend.

Quick start (verify a signed release)

trust, err := verify.LoadTrustSet(publisherArmoredPubKey)
if err != nil {
    return err
}
if err := trust.VerifyManifestSignature(manifest, signature); err != nil {
    return err // tampered or untrusted — reject
}
// verified

See the runnable Example tests on pkg.go.dev for the full sign→verify round-trip and the local backend.

Documentation

Licence

MIT. See LICENSE.

Documentation

Overview

Package signing exposes a registry of signing-key backends. Each backend knows how to talk to a specific HSM/KMS/keyring and produce a crypto.Signer that callers (typically package openpgpkey) use to mint an OpenPGP-armored public key.

Backends register themselves at process start via init() calling Register. Downstream binaries opt into a backend by blank-importing its package — this is the same activate-by-side-effect pattern used in net/http/pprof, image/* decoders, and go/credentials/keychain. The signing-key minter (gtb keys mint) reads the registered names via Names() and dispatches based on the --backend flag the user passes.

Built-in backends shipping with the standard `gtb` binary:

  • "aws-kms" (gitlab.com/phpboyscout/go/signing-aws-kms): RSA-4096 keys held in AWS KMS.
  • "local" (gitlab.com/phpboyscout/go/signing/local): RSA keys loaded from an unencrypted PKCS#1 or PKCS#8 PEM file on disk. Use for the onboarding tutorial / local signing flows; never in CI.

Third parties add backends by implementing Backend and calling Register from their package's init(). No upstream change is needed. See docs/how-to/implement-a-custom-backend.md for the contract details.

API stability: Beta. The interface is small and stable; the only known evolution path is additive (e.g. ECDSA support, capability- discovery methods).

Index

Constants

This section is empty.

Variables

View Source
var ErrUnknownBackend = errors.NewSentinel("signing.unknown_backend", "unknown signing backend")

ErrUnknownBackend is returned by Get when the named backend is not registered. The error message lists the available backends so users can see what their binary actually ships with.

View Source
var ErrUnknownBackendType = errors.NewSentinel("signing.unknown_backend_type", "unknown signing backend type")

ErrUnknownBackendType is returned by NewInstance when no factory is registered for the requested type — i.e. the binary was not built with that backend, or the backend does not offer named instances.

Functions

func Names

func Names() []string

Names returns the names of all registered backends, sorted alphabetically. Used by `gtb keys mint --help` to enumerate the accepted --backend values for the current binary.

func Register

func Register(b Backend)

Register adds b to the global backend registry. Called from each backend package's init() so that blank-importing the package activates the backend.

Panics on a duplicate name — duplicates indicate a programming error at process init (two backend packages claiming the same identifier) and failing fast is preferable to silent override.

Panics on a nil Backend or one whose Name() is empty. Both are programmer errors that should never reach a deployed binary.

func RegisterFactory added in v0.7.0

func RegisterFactory(typeName string, construct func(canonicalName string) (Backend, error))

RegisterFactory records how to construct a NAMED instance of a backend type. A backend package calls it from init(), alongside Register, so that blank-importing the package activates both the default instance and the ability to mint named instances of it.

construct is given the canonical instance name "<type>:<instance>" and must return a Backend whose Name() equals it (NewInstance enforces this). construct runs OUTSIDE the registry lock, so it may itself touch the registry without deadlock.

Panics — all programmer errors that should never reach a deployed binary — on:

  • a typeName that is not lowercase kebab-case and colon-free ([a-z0-9][a-z0-9-]*), which the unambiguous selector split relies on;
  • a nil construct;
  • a duplicate type (one factory per type, mirroring Register's duplicate panic).

A type that offers named instances should also Register a same-key default, so a bare selection of the type resolves and the type appears in Names(); the factory adds the named capability, it does not replace the default.

func ResetForTesting

func ResetForTesting()

ResetForTesting clears the registry — both the registered backends and the instance factories (RegisterFactory). The "ForTesting" suffix is the Go-standard signal that this is a test helper — production code must not call it. Exported (rather than hidden behind a _test.go-only build) so that tests in other packages (notably go/signing-cli's command tests) can drive registry-mutating scenarios against fake backends.

Types

type Backend

type Backend interface {
	Name() string
	NewSigner(ctx context.Context, keyID string) (crypto.Signer, error)
}

Backend constructs a crypto.Signer for an HSM/KMS-held signing key. Implementations are registered globally via Register and selected at runtime by Name (matching, e.g., a CLI front-end's --backend flag).

The contract is deliberately minimal and CLI-agnostic so a backend is trivial to implement and the module stays dependency-light (no flag library in the core):

  • Name returns a stable identifier, unique across the process (duplicate Register calls panic). A registered default is lowercase kebab-case; an instance minted via NewInstance carries the instance-qualified "<type>:<instance>" form (e.g. "aws-kms:prod").
  • NewSigner returns a crypto.Signer wrapping the remote key. The backend interprets keyID's format — AWS uses ARNs/aliases, a PEM backend uses a file path, GCP uses resource names, etc. The signer's Public() must return a type the caller can consume; openpgpkey currently requires *rsa.PublicKey.

Backends do not own credential resolution; callers configure the SDK / agent / environment before invoking the signer.

A backend that needs to declare its own CLI flags can implement an optional flag-registration interface defined by the CLI front-end (e.g. gtb), which type-asserts for it. The core intentionally does not define or depend on such an interface, keeping any flag library out of this module.

func Get

func Get(name string) (Backend, error)

Get returns the registered backend with the given name. If no such backend is registered, returns ErrUnknownBackend wrapped with a listing of the available names so the user can immediately see what their binary actually supports.

func NewInstance added in v0.7.0

func NewInstance(typeName, instance string) (Backend, error)

NewInstance mints (constructs) a named instance of typeName via its registered factory. It does NOT register the result — the caller owns its lifetime. The canonical name handed to the factory, and required back from it, is "<typeName>:<instance>".

Returns an error on: an empty instance; a type with no factory (ErrUnknownBackendType); a factory error (wrapped); a nil or typed-nil result; or a result whose Name() is not the canonical "<type>:<instance>".

NewInstance validates only these structural invariants. The *grammar* of a selector — the instance character set, the settings shape — belongs to the CLI front-end that parses "<type>:<instance>?…", not here.

Directories

Path Synopsis
Package local is the on-disk PEM private-key backend for package signing.
Package local is the on-disk PEM private-key backend for package signing.
Package minisign produces and verifies minisign-format detached signatures over release artefacts.
Package minisign produces and verifies minisign-format detached signatures over release artefacts.
Package openpgpkey mints an ASCII-armored OpenPGP public key from a crypto.Signer.
Package openpgpkey mints an ASCII-armored OpenPGP public key from a crypto.Signer.
Package verify holds the self-update signing primitives.
Package verify holds the self-update signing primitives.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL