Skip to main content
Every plugin a production agent installs must be signed by an Ed25519 key the agent trusts. This page owns the publisher-side lifecycle: generating a key, signing, enrolling the public key on the agent, and the bench mode for unsigned archives. Revocation is on Revocation and incidents.

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

Add keys/ and *.priv.pem to your .gitignore before generating keys. In your own repository nothing ignores them for you.
This writes two files under 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.
The positional argument is the signer id. --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’s SIGNATURE 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:
All four are required: the plugin directory (with a 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:
The agent verifies an archive against the key named by the 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:
  1. ados plugin keygen acme-2026-A --output-dir keys
  2. Copy keys/acme-2026-A.pem to /etc/ados/plugin-keys/acme-2026-A.pem on the node.
  3. Sign with ados plugin sign or sign.sh, as above.
  4. ados plugin install ./com.example.my-plugin-1.0.0.signed.adosplug
Unsigned bench mode. Unsigned archives install only while signature enforcement is relaxed:
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:
For production keys, keep the private half off general-purpose disks: a hardware security module, a smartcard with an Ed25519 applet, or a TPM-sealed key. The signing input is a 32-byte digest, so any tool that can produce an Ed25519 signature over it can replace sign.sh.

Self-signed closed fleets

A fleet can sign and trust end to end with no Altnautica involvement:
  1. Generate a key pair.
  2. Install the public key at /etc/ados/plugin-keys/<your-id>.pem on every agent.
  3. Sign your plugins with the private key.
  4. 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.
If a key leaks, follow Revocation and incidents.

Next steps

URL install

Install from a GitHub release link.

Revocation and incidents

Retire a compromised key.
Last modified on October 11, 2026