Why Ed25519
Small keys (32 bytes), small signatures (64 bytes), constant-time verification, no parameter choices to misconfigure. The same algorithm and verifier sign OTA payloads, so the agent’s verification code already exists and is well-tested.Generate a key pair
The agent CLI mints a keypair in one step:keys/:
acme-2026-A.pemis the public key (mode 0644). This is what operators install at/etc/ados/plugin-keys/<signer-id>.pem.acme-2026-A.priv.pemis the private key (mode 0600). The signing rig keeps this. Never check it in, never copy it across hosts without an encrypted transport.
--force to
overwrite existing files at the target paths. keygen prints a SHA-256
fingerprint of the public key so an operator can cross-check the
installed key against what you generated.
The reference repo template at altnautica/ADOSExtensions carries a
.gitignore that covers keys/*.pem.
Alternative: openssl
openssl produces an equivalent PEM keypair:
acme-2026-A.pem) is what operators add to their trust
list; its basename is the signer id. Keep the private key out of git.
Pick a signer key id
The signer key id is a short, stable label. It is the first line of the archive’sSIGNATURE file and the basename of the public key in the
trust list. Operators see it on the install dialog. Use a namespace
prefix and a generation suffix, for example acme-2026-A.
The id is not a secret. It is a stable identifier for trust management.
Pack and sign
ados plugin sign packs a plugin directory and signs it in one command:
manifest.yaml), --key (the Ed25519 private key in PEM), --signer-id
(written to the SIGNATURE file, and it must match the public-key
filename on the agent), and --output (the path for the signed archive).
The command computes the canonical payload hash (SHA-256 over the sorted
<path>\n<sha256-hex>\n lines of every entry except SIGNATURE), signs
that 32-byte digest with the Ed25519 private key, and writes a
SIGNATURE file (the signer id on the first line, then the base64
signature) into the archive. Because the hash covers every entry, the
signature protects the manifest and every asset at once.
Alternative: pack.sh and sign.sh (monorepo CI)
Thealtnautica/ADOSExtensions repo ships two scripts for its tagged
release pipeline. pack.sh builds the GCS bundle and zips the archive;
sign.sh adds the signature.
sign.sh writes a *.signed.adosplug archive next to the input.
ADOS_SIGNING_KEY is a path to the private key (PEM or raw 32-byte). The
signer id defaults to altnautica-2026-A when ADOS_SIGNING_KEY_ID is
unset. The payload hash and SIGNATURE format are identical to the CLI,
so an archive from either path verifies the same way on the agent.
Trust list on the agent
The agent trusts a directory of PEM public keys:SIGNATURE file’s first line. A signature from a signer id with no
matching file is rejected. After adding or removing a key, restart the
supervisor:
altnautica-2026-A, altnautica-2026-B) is maintained in agent code,
not by filename, so a planted key file cannot impersonate first-party
status. First-party status is what unlocks the in-process agent
isolation and inline GCS isolation modes; third-party plugins always run
subprocess and iframe.
Revocation
Revoked signer ids live in a JSON file on the agent:CI signing
A tagged release can sign in CI with the key held in a repository secret. Pass the base64-encoded private key inline:ADOS_SIGNING_KEY_INLINE=1 tells sign.sh to treat ADOS_SIGNING_KEY
as a base64 key body rather than a file path; it writes it to a tempfile,
signs, and wipes it. Hold the secret in a protected environment with
required reviewers if the key is high-value.
Key storage
For developer iteration, a file on disk is fine. Pin tight permissions and never commit it:sign.sh in a
hardened pipeline.
Self-signed for closed deployments
A fleet that never pulls from a public registry signs and trusts end to end without Altnautica involvement:- Generate a key pair locally.
- Place the public key at
/etc/ados/plugin-keys/<your-id>.pemon every agent and restart the supervisor. - Sign your plugins with the matching private key.
- Distribute via internal HTTPS, USB, or your existing fleet channel,
then
ados plugin install <path>against the local archive file.