Why Ed25519
Small keys (32 bytes), small signatures (64 bytes), fast constant-time verification, and no parameter choices to get wrong. OTA artifacts are also Ed25519-signed (minisign), so the algorithm is already in use on the agent.Generate a key pair
keys/:
acme-2026-A.pem, the public key (mode 0644). Operators install this at/etc/ados/plugin-keys/acme-2026-A.pem.acme-2026-A.priv.pem, the private key (mode 0600). Your signing rig keeps it. Never commit it or copy it across hosts without encrypted transport.
--force overwrites existing files. keygen prints a SHA-256 fingerprint of the public key so an operator can cross-check the key they install.
keygen is a developer aid. Production publisher keys deserve a hardware-token or air-gapped workflow. See Key storage.openssl produces an equivalent pair:
Pick a signer id
The signer id is a short, stable label: the first line of the archive’sSIGNATURE file and the basename of the public key on the agent. Operators see it in the install review. Use a namespace prefix and a generation suffix, for example acme-2026-A. It is not a secret.
Sign an archive
ados plugin sign packs a plugin directory and signs it in one command:
manifest.yaml), --key, --signer-id and --output.
In the ADOSExtensions monorepo, sign the archive pack.sh produced with sign.sh:
sign.sh writes com.example.my-plugin-1.0.0.signed.adosplug next to the input. ADOS_SIGNING_KEY is the .priv.pem from keygen (or a raw 32-byte seed); a public key or an encrypted key is refused. ADOS_SIGNING_KEY_ID is required and has no default. Set ADOS_SIGNING_PUBKEY to your .pem to verify the signature right after writing it.
Both paths compute the same canonical payload hash (SHA-256 over the sorted <path>\n<sha256-hex>\n lines of every entry except SIGNATURE), sign that digest, and write a two-line SIGNATURE file: the signer id, then the base64 signature. The hash covers every entry, so the signature protects the manifest and every asset.
Signing in CI
Hold the base64-encoded private key in a repository secret and pass it inline:ADOS_SIGNING_KEY_INLINE=1 makes sign.sh decode the key to a temp file, sign, and delete it. Put a high-value key behind a protected environment with required reviewers.
Enrol the key on the agent
The agent trusts a directory of PEM public keys, one file per signer id:SIGNATURE file’s first line. A signer with no matching file is refused with exit code 3. No restart is needed: keys are read at every install.
First-party status is pinned in agent code by signer id and exact key bytes, so a key file planted under a first-party name verifies only as a third party. First-party status unlocks inline GCS isolation, resources.class: heavy, and installs on a node without a sandboxing service backend. No plugin runs in-process.
Bench install and signing
On a bench, use one of two paths. Signed (recommended). Run the full chain once and every later build installs like production:ados plugin keygen acme-2026-A --output-dir keys- Copy
keys/acme-2026-A.pemto/etc/ados/plugin-keys/acme-2026-A.pemon the node. - Sign with
ados plugin signorsign.sh, as above. ados plugin install ./com.example.my-plugin-1.0.0.signed.adosplug
0, false, no and off all relax enforcement, and the agent logs a warning while it is off. A revoked signer is refused even in this mode. Remove the line and restart both services when you are done.
Key storage
For iteration, a file on disk is fine. Tighten its permissions:sign.sh.
Self-signed closed fleets
A fleet can sign and trust end to end with no Altnautica involvement:- Generate a key pair.
- Install the public key at
/etc/ados/plugin-keys/<your-id>.pemon every agent. - Sign your plugins with the private key.
- Publish on a GitHub release, or copy the file to each node and run
ados plugin install <path>. Self-hosted download hosts are not supported for URL install; see URL install.
Next steps
URL install
Install from a GitHub release link.
Revocation and incidents
Retire a compromised key.