Concepts¶
Why a separate, standalone CLI¶
The signing logic has been a reusable module family for a while
(go/signing and its backends). The signing commands, however, lived inside
gtb — a framework that is deliberately both a library and a CLI. That was fine
while gtb was the only consumer, but it meant you could only get a sign /
keys command surface by building a whole gtb application.
sigillum exists to remove that constraint. Its entire reason to be is signing,
so it makes sign and keys the whole tool, exposed as top-level commands
rather than sub-commands of a larger app.
Signing, note, and not verifying: there is no sigillum verify. The
verification half of go/signing is linked in — sigillum's own update command
uses it to check the release it downloads — but it is not exposed as a command.
What sigillum does not do covers why, and
what to use instead.
The practical payoff is for non-Go and non-gtb release pipelines. A Rust
project, a shell-driven release, or any CI job that just needs to produce and
publish OpenPGP signatures can go install (or download) one small binary and
sign — with no framework build, no gtb dependency, and no bespoke signing
script. It is a purpose-built utility, sized to one job.
Keeping sigillum thin is what makes this honest: it holds no signing logic of
its own. All of that stays in go/signing, shared with gtb, so the two tools
can never drift in how they sign. See
Components & architecture for the module stack.
The build-time backend model¶
A signing backend (AWS KMS, local PEM, or a third party) implements
go/signing's crypto.Signer-based contract and registers itself in that
module's registry. The command layer never hard-codes a backend; --backend
simply selects a registered one, and sigillum sign --help lists whichever
backends are present.
Which backends a given binary carries is decided at build time by blank
imports in sigillum's main package:
// cmd/sigillum/signing.go
import (
_ "gitlab.com/phpboyscout/go/signing-aws-kms"
_ "gitlab.com/phpboyscout/go/signing/local"
)
Each blank import runs the backend package's init, which registers it. sigillum
ships the full set: AWS KMS + local PEM.
The encryption side has its own registry¶
decrypt and certificate resolve --backend against a separate registry
in go/encryption, populated by a separate file:
// cmd/sigillum/encryption.go
import (
_ "gitlab.com/phpboyscout/go/encryption-aws-kms"
_ "gitlab.com/phpboyscout/go/encryption/local"
)
Two registries rather than one, because the two sides want different things from
a backend. A signing backend supplies a crypto.Signer. An encryption backend
must derive a shared secret from a sender's ephemeral point and hand back its
own public half so a certificate can be published — neither of which a signing
backend has any reason to implement. Merging them would let --backend offer a
name that cannot serve the command it was passed to.
The same build-time property holds on both sides, and independently: a build can carry AWS KMS for signing and local PEM only for encryption, or drop either side entirely.
This model has a security dividend. A regulated or hardened build can drop a
blank import — say, remove the AWS KMS line and rebuild. The Go linker's
dead-code elimination then keeps that backend's SDK (and its transitive
dependencies) entirely out of the linked binary. The backend set is thus a
compile-time policy decision, not a runtime flag that could be toggled by
config. (sigillum uses the same pattern for OS-keychain support via
cmd/sigillum/keychain.go.)