sign¶
sign <input-file> --backend <type>[:<instance>[?<settings>]] --key-id <id> [--format openpgp|minisign] [flags]
Constructor: signingcli.NewCmdSign(log Logger) *cobra.Command.
Produce a detached signature over a single input file using a crypto.Signer
resolved from a compiled-in go/signing backend. Signatures are written with
mode 0644, because they are public artefacts distributed alongside the signed
file.
The private key never leaves the backend. Signing is one round-trip (two for
minisign) through the crypto.Signer interface.
sign overwrites an existing output file without asking. There is no
--force flag and no prompt; the only path it refuses is one that resolves to the
input file itself.
Two formats, two audiences¶
--format |
Output | Signs | For |
|---|---|---|---|
openpgp (default) |
<input>.sig |
ASCII-armored OpenPGP detached signature | checksum manifests, Go self-update |
minisign |
<input>.minisig |
prehashed ED minisign signature |
release artefacts, verified by cargo-binstall and rtb-update |
The key type decides the format, not the flag¶
The two formats need two different key types, and neither will accept the other's:
--format openpgprequires an RSA key. An Ed25519 key fails withDetachSign: ed25519.PublicKey: unsupported key type: only RSA is supported. The restriction comes fromgo/signing'sopenpgpkeypackage and is structural: go-crypto's OpenPGP Ed25519 path cannot be driven through acrypto.Signer, so a KMS-held Ed25519 key could not work here regardless.--format minisignrequires an Ed25519 key, which for AWS KMS means anECC_NIST_EDWARDS25519key. An RSA key is refused before any signing happens.
A project signing both a checksum manifest and release artefacts therefore needs two keys. See What signing-cli does not do.
How the minisign signature is produced¶
--format minisign hashes the artefact with BLAKE2b-512 and signs the 64-byte
digest, so an HSM-held key can sign an artefact of any size despite the KMS
4096-byte message cap, and the key never leaves the HSM to do it. It takes no
--public-key: a minisign signature carries its own key identifier, so the
identity comes from the signer itself. Publish the matching public key with
keys minisign.
The .minisig extension matters, because rtb-update selects its parser by
extension. cargo-binstall's signature URL is configurable, so one file serves both.
Recording project identity¶
--project appends project:<name> and key:<KEYID> to the trusted comment,
after minisign's conventional timestamp/file/hashed fields so anything
parsing the standard format still finds what it expects:
timestamp:1785326400 file:tool_1.2.3_linux_amd64.tar.gz hashed project:rtb-cli-bin key:56475AA75463474C
The trusted comment is covered by the signature's global signature, so these
fields are tamper-evident: altering the project name makes verification fail.
The key: field duplicates the key ID already in the signature body. The body
is authoritative; the copy exists so a signature file is greppable and
self-describing under review.
Comment lengths are validated against the reference minisign implementation's
limits (1024 bytes untrusted, 8192 trusted). No Rust verifier enforces these, so
an over-long comment would verify in cargo-binstall and then fail against
upstream minisign -V. sign refuses to emit one.
Arguments¶
| Argument | Required | Description |
|---|---|---|
<input-file> |
yes (exactly one) | Path to the file to sign. |
Flags¶
| Flag | Required | Default | Description |
|---|---|---|---|
--backend |
yes | — | Backend selector: a backend type compiled into the binary, or a named instance of one with inline settings. See Selecting a backend. --help lists the compiled-in types. |
--key-id |
yes | — | Backend-specific key identifier. For aws-kms: a KMS key ID, ARN, or alias. For local: a PEM file path. |
--format |
no | openpgp |
openpgp or minisign. Any other value is an error listing the valid ones. |
--project |
minisign only | — | Project this artefact belongs to, recorded in the signature's signed trusted comment as project:<name> alongside key:<KEYID>. Omit to emit minisign's exact default comment. Rejected for --format openpgp, which has no trusted comment to record it in. |
--public-key |
openpgp only | — | Path to the armored OpenPGP public-key file (.asc, produced by keys mint). Must contain the public half of the key resolved by --backend / --key-id. Rejected for --format minisign. |
--output |
no | <input>.sig, or <input>.minisig |
Output file path for the detached signature. Refuses to equal the input path. |
--created |
no | now | Signature creation time (RFC3339), truncated to whole seconds. Pin it to produce byte-identical signatures across re-runs. Must not be earlier than the signing key's creation time (see below). For --format minisign this sets the trusted comment's timestamp, and SOURCE_DATE_EPOCH is honoured when the flag is unset. --format openpgp ignores SOURCE_DATE_EPOCH. |
--append |
openpgp only | false |
Merge the new signature into an existing armored signature at --output instead of overwriting, producing one armored block with multiple signature packets. No-op when --output does not exist yet. Rejected for --format minisign, which has no equivalent. |
Backends may register additional flags. The aws-kms backend, for example,
adds --kms-region (default eu-west-2). These appear in --help only when a
backend that declares them is compiled in, and they configure that backend's
registered default and nothing else (see the next section).
Selecting a backend¶
--backend takes one of three forms. The same grammar applies to
keys mint and keys minisign,
which share the resolver.
| Form | Example | Resolves to |
|---|---|---|
<type> |
aws-kms |
The backend's registered default, configured by its top-level flags such as --kms-region. |
<type>:<instance> |
aws-kms:prod |
A fresh instance minted through signing.NewInstance, with every backend flag at its default. |
<type>:<instance>?<settings> |
'aws-kms:prod?kms-region=eu-west-1' |
The same, with the settings applied to that instance only. |
A named instance lets one binary sign through two differently configured copies of
the same backend, say a production and a staging KMS account, without the two
sharing flag values. The canonical name (aws-kms:prod) is what appears in the
backend field of the log line. The settings never do.
sign --backend 'aws-kms:prod?kms-region=eu-west-1' \
--key-id alias/release-signing-v1 --public-key release.asc checksums.txt
The rules, each enforced before anything is read or signed:
- Instance names match
^[a-z0-9][a-z0-9-]*$: lowercase letters, digits and hyphens, starting with a letter or digit.aws-kms:Prodandaws-kms:are both refused. - Settings are only valid on a named instance.
local?x=1is refused; give the instance a name first. - Settings are url-encoded
key=valuepairs joined by&. Percent-encode a value that contains&,=,+or%. - Each key must be a flag the backend declares, written without the leading
--. Foraws-kmsthat iskms-region. An unknown key is refused, and so is any setting at all for a backend that declares no flags. - No duplicates, no empty keys or values, no stray
&.kms-region=a&kms-region=b,kms-region=,=eu-west-1, a barekms-region, and a leading, trailing or doubled&are each refused. - A top-level backend flag cannot be combined with a named instance.
--backend aws-kms:prod --kms-region eu-west-1fails, because--kms-regionis bound to the registered default and would otherwise be silently ignored. Put the value in the selector instead. - The type must offer named instances. A backend opts in by registering a
factory with
signing.RegisterFactory.localdoes fromgo/signingv0.8.0, which this module requires, andaws-kmsdoes fromsigning-aws-kmsv0.6.0. A type compiled in without a factory gets an error telling you to use the bare<type>form or update the backend module.
Quote the whole value in a shell. An unquoted & ends the command there and
runs it in the background, and ? is a glob character that zsh, by default,
refuses to pass through when nothing matches it.
Validation runs in two phases. Every setting is checked before any is applied, so
a bad selector never leaves an instance half-configured. The errors name the
setting key rather than its value. A setting with an empty key (?=value), no
= at all (?value) or a malformed %-escape in its key is named by its
position instead, counting from 1. The exact messages are listed under
A named instance was refused.
--created must not predate the key¶
--created is used as go-crypto's notion of "now" while the signature is built,
so a timestamp from before the signing key was created makes the key look
not-yet-valid and the run fails:
Error: computing signature: computing detached signature: openpgp: invalid argument: no valid signing keys
The message does not mention the timestamp. If you see it while pinning
--created, compare the value against the key's creation date:
gpg --list-packets release.asc prints it as created <unix-seconds>.
Pinning --created to a valid instant does give byte-identical output: signing
the same file twice with the same key and the same --created produces two
identical .sig files.
--append and key rotation¶
--append is the dual-sign primitive for a key-rotation overlap window. Sign
once with the old key, then --append the new key's signature into the same
armored file:
sign --backend aws-kms --key-id alias/release-signing-v1 \
--public-key v1.asc --output checksums.txt.sig checksums.txt
sign --backend aws-kms --key-id alias/release-signing-v2 \
--public-key v2.asc --output checksums.txt.sig --append checksums.txt
Verifiers skip signature packets from issuers they do not know, so one armored file carrying both packets verifies for binaries that trust either key.
Examples¶
# Sign checksums.txt with a production KMS key
sign \
--backend aws-kms \
--kms-region eu-west-2 \
--key-id alias/gtb-release-signing-v1 \
--public-key release.asc \
checksums.txt
# The same key through a named instance, configured inline
sign \
--backend 'aws-kms:prod?kms-region=eu-west-2' \
--key-id alias/gtb-release-signing-v1 \
--public-key release.asc \
checksums.txt
# Reproducible signature: pin --created to a fixed instant
sign \
--backend local \
--key-id ./release.pem \
--public-key ./release.asc \
--created 2026-01-01T00:00:00Z \
--output build.txt.sig \
build.txt
Output¶
With --format openpgp, sign writes the armored .sig file and logs a
Signed file INFO line carrying backend, key_id, public_key, input,
output, sig_creation_time and the primary-key fingerprint, so operators can
confirm the signing identity without a follow-up gpg --verify.
With --format minisign it writes the .minisig file and logs a
Signed artefact INFO line carrying format, backend, key_id,
minisign_key_id, input, output, project and trusted_comment_time.
See also¶
keys mint, which produces the--public-keyfile.- Compile in signing backends, to make
--backendresolve. - What signing-cli does not do, including why there is no
verifycommand here. - Errors and what they mean: every message
signcan print. - Configuration and environment:
SOURCE_DATE_EPOCH, default output paths, file modes. - Verifying a signature:
go/signing.