sign¶
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 — they are public, non-sensitive 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 — for AWS KMS, 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 — custody is fully preserved. 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: 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, and 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 | — | Signing backend name. Valid values are the backends compiled into the binary (see Compile in signing backends); --help lists them. |
--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 — e.g. the aws-kms backend adds
--kms-region (default eu-west-2). These appear in --help only when a backend
that declares them is compiled in.
--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
# 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¶
On success sign writes the armored .sig file and logs a structured 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.
See also¶
keys mint— produce the--public-keyfile.- Compile in signing backends — 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.