What signing-cli does not do¶
Everything on this page is a deliberate boundary of the current release
(v0.4.1), not a bug. If you are designing around these commands, read this
before you design.
There is no signing-cli binary¶
This module is a library of Cobra command builders. go install on it
produces nothing runnable. You get sign and keys by attaching the builders to
a CLI you build or already own — see
Attach the commands to a CLI, or the
tutorial for a CLI built from scratch
in one file.
No signing backend ships with this module¶
signing-cli imports no concrete backend, on purpose. A host binary that
blank-imports none has sign and keys mint present but unusable: --backend
has no valid value and --help lists none. Backends come from go/signing and
its sibling modules, activated in the host's main package —
Compile in signing backends.
OpenPGP signing is RSA-only¶
keys mint and sign --format openpgp both go through go/signing's
openpgpkey package, which handles RSA keys only. Pointing either at an
Ed25519 key fails:
The restriction is upstream and structural: go-crypto's OpenPGP EdDSA signing
branch needs a concrete *eddsa.PrivateKey — its own type, from
go-crypto/openpgp/eddsa — rather than a crypto.Signer, so a
KMS-held Ed25519 key could not drive it even if the type check were relaxed.
ECDSA keys are refused by the local backend before they reach OpenPGP at all.
keys generate --algorithm ed25519 is the one exception, and only because it
generates the key inside go-crypto rather than wrapping an external signer. The
armored public half it writes is a valid OpenPGP key; you still cannot sign
OpenPGP with the matching private half through the local backend.
minisign signing is Ed25519-only¶
The mirror of the above. sign --format minisign and keys minisign require an
Ed25519 key — for AWS KMS, an ECC_NIST_EDWARDS25519 key. An RSA key is refused
before the backend is asked to sign anything:
minisign requires an Ed25519 signing key, but --key-id resolved to *rsa.PublicKey; RSA keys sign the OpenPGP manifest path (--format openpgp) instead
One key cannot serve both signature formats¶
Taken together, the two rules above mean a project signing both a checksum manifest and release artefacts needs two keys: an RSA key for the OpenPGP path and an Ed25519 key for the minisign path. There is no key type that satisfies both, and no flag combination that avoids the split.
Nothing here verifies a signature¶
sign and keys produce signatures and keys. There is no verify command in
this module, and none is planned here — verification belongs to whoever consumes
the artefact:
- OpenPGP detached signatures verify with
gpg --verify, or programmatically viago/signing'sverifypackage. - minisign signatures verify with the
minisigntool, cargo-binstall, or rtb-update.
Private keys are never written encrypted¶
keys generate writes the private half unencrypted, whichever format you choose:
an armored OpenPGP secret block, a PKCS#1 PEM, or a PKCS#8 PEM. There is no
passphrase flag, and the CLI's own help text still describes this as a v0.1
limitation.
Correspondingly, the local backend cannot read an encrypted PEM. A file
whose PEM header is ENCRYPTED PRIVATE KEY is refused outright:
constructing signer: encrypted PEM private keys are not supported in v0.1; decrypt out-of-band first or use the aws-kms backend
Use filesystem-level encryption (LUKS, FileVault, age) and move the file offline, or use a KMS-held key so no private half exists on disk at all.
Minted keys carry no expiry, and mint makes no subkey¶
A key produced by keys mint or keys generate --algorithm rsa is a single
primary key with sign and certify capability, one User ID, and no expiry date.
There is no flag to set one, no revocation certificate is emitted, and no
encryption subkey is created. Rotation is handled by minting a new key and
publishing it alongside the old one, not by expiring the old one.
keys generate --algorithm ed25519 differs, because go-crypto's NewEntity
builds the whole entity: that path emits a primary Ed25519 key plus a Curve25519
encryption subkey you did not ask for and cannot suppress.
sign overwrites its output without asking¶
sign has no --force flag and no prompt. If --output (or the default
<input>.sig / <input>.minisig) already exists, it is truncated and rewritten.
The only path it refuses is one that resolves to the input file itself.
The keys subcommands behave the opposite way: mint, generate and
minisign refuse to clobber an existing file and require --force. Do not
generalise either behaviour to the other.
A failed keys generate can still leave a private key behind¶
keys generate writes the private half first, then the public half. If the
public-half write is the one that fails — most often because the file already
exists and --force was not passed — the command exits non-zero with the private
half already on disk:
$ ls
pub.asc # pre-existing, empty
$ mytool keys generate --algorithm rsa --name A --email a@example.com --output pub.asc
ERR: writing public-half output: "pub.asc" (pass --force to overwrite): output file already exists
$ ls -l
-rw-rw-r-- 1 user user 0 pub.asc
-rw------- 1 user user 1675 pub.pem # written before the failure
Check for a stray private half after any failed keys generate.
keys publish stages, it does not deploy¶
keys publish writes into a local directory. It never uploads, never talks to a
CDN or object store, and needs no credentials — that is the point of it reading a
file rather than a backend. Deployment is a separate step you run yourself
(wrangler pages deploy ./keys-staging, rsync, or whatever hosts the site).
The same is true of keys wkd.
keys publish rewrites the untrusted comment¶
The .pub file keys publish writes is not a copy of its input. Only the key
material is carried across; the untrusted comment line is regenerated as:
The phpboyscout prefix is fixed in the code and not configurable, so a key
published by a tool outside this estate still carries it. Nothing signs an
untrusted comment, so this affects presentation only — but it means the file you
publish differs byte-for-byte from the file keys minisign --output produced.
A published key's bytes can never change¶
keys publish is add-only. Re-running with identical content is a no-op; pointing
the same project and generation at a different key is refused, both on disk and
in the manifest:
site/minisign/demo/v1.pub already holds a different key — published keys are add-only; publish a new generation instead
Everything else in the entry — status, purpose, valid_from — can be updated
by re-running with the same key. Rotation means a new --generation, never a
replacement.
keys wkd overwrites and never prunes¶
keys wkd writes its tree with plain file writes and no no-clobber guard: an
existing hu/<hash> bucket, policy, or submission-address is overwritten
silently. It also never deletes anything, so a bucket for an email you have
stopped publishing stays in the staging directory and will be deployed again.
Build the tree in a fresh directory if you need it to be exact.
There is no config file and no environment binding for flags¶
Every value comes from a flag or a positional argument. This module reads no config file, defines no environment-variable prefix, and does not bind flags to environment variables — including when it is embedded in a go-tool-base host, whose own config store these commands do not consult. See Configuration and environment for the two environment variables that do affect behaviour, both of which belong to something else.
Flags are format-specific and refused rather than ignored¶
Three flags on sign apply to exactly one format, and passing one to the other
format is an error, not a silent no-op:
| Flag | Valid for | Refused for |
|---|---|---|
--public-key |
--format openpgp (required) |
--format minisign |
--append |
--format openpgp |
--format minisign |
--project |
--format minisign |
--format openpgp |
--append in particular has no minisign equivalent: it merges OpenPGP signature
packets into one armored block, and a minisign signature file holds exactly one
signature.
See also¶
signandkeys— the flags themselves.- Errors and what they mean — the message you get when you hit one of these boundaries.
- Backends are the consumer's responsibility — why the backend gap is a design choice rather than an omission.