TacidOS
DevelopersGitHub

Decision 0041

The owner's key is made at installation, and the recovery key is shown once

Architecture decision record 0041: The owner's key is made at installation, and the recovery key is shown once

Status
accepted
Date
2026-09-05

Context

ADR-0007 made the owner a key and ADR-0012 promised a recovery key shown once. Neither had been built. tacid install demanded --owner-key and refused to go on without it — an installer that teaches people to paste something in, and a machine whose owner is whatever was pasted — and an encrypted root had exactly two ways in: the TPM behind its PIN, and the passphrase. A person who forgets the passphrase on a machine whose board was replaced has a disk and no machine.

Both are things a person is given once, at the console, and both are secrets that must exist on the machine in one place or in none.

Prior decisions

ADR-0033 makes the installation the stem’s typed operations, with a person present; ADR-0039 puts a key slot’s secrets in the tool’s environment and never an argument, and has the keeper scrub every secret before a word is written down; ADR-0004 keeps the keeper between a person’s request and the root that carries it out.

Decision

A person who brings no key is given one; the recovery key is printed by the tool that made it, handed back once, and kept nowhere.

The owner’s key: tacid install without --owner-key makes an Ed25519 pair. The public half goes into the genome as tacid.owner.key, where the keeper reads whose signature makes a policy the owner’s. The secret half goes into the person’s own home on the machine, at ~/.config/tacid/owner.key, mode 0600, theirs — never the genome, which is the agent’s; never the store, which is everybody’s; never the journal, where the keeper scrubs it like a passphrase. The stem writes it through its own guarded operation, owner-key-write, which refuses anything outside the install mount, any name that is not a user’s, and any uid below 1000: an owner is a person. The genome declares the first person as uid 1000, so that the stem can make their home theirs before the machine has ever run. A genome written to a directory with --write-genome needs the key given: one made there would have nowhere to keep its secret half. The plan says the key was made, and tacid install says where it went.

The recovery key: after the passphrase’s slot and the TPM’s, the keeper asks the stem for a third, systemd-cryptenroll --recovery-key, with the passphrase in the tool’s environment as ADR-0039 has it. The tool prints the key it made to standard output when that is not a terminal, and to nothing else; the stem holds the pipe, and hands the key back as the one thing an operation can produce — a Secret, never described. The keeper registers it with its scrubber before it writes the step down, so the journal says “the recovery key” and never the key, and returns it once, in the installation’s result, to the person at the console. tacid install prints it with what it is for: it is typed at the passphrase prompt, and opens the root when the TPM, the PIN and the passphrase cannot. It exists on paper, or nowhere.

Consequences

  • A fresh installation makes six guarded requests of the stem where it made four: the disk, the key, the recovery key, the genome, the owner’s key, the system.
  • Response::Done carries what an operation produced, and the keeper is the only reader. A Step that produces is the only kind whose output is kept; every other program’s output is discarded as it was.
  • The installation test enrols the recovery key, reads it from what tacid install printed, finds it in no journal and no state, and boots the machine with it once the TPM is gone — and then with the passphrase, as before. It installs with a key given, because the system it compares to the host’s is built from the genome and the genome carries the key; the stem’s writing of a made key into the home is held by its own tests and the keeper’s, and the plan the live system prints for an installation without a key says a key was made.
  • The initrd’s prompt changes with the slot: a root with a recovery key enrolled asks for “the passphrase or recovery key”, and the channel test’s seventh full run waited for the old words until its time ran out, with the follower installed and standing at the prompt. Both tests now wait for either wording.
  • An owner who loses owner.key has a machine with no owner key until they give it another, by editing owner.nix. That is the right failure: the key is theirs, and Tacid holding a copy would make it not.

Alternatives rejected

  • The secret half in the genome’s secrets/. The genome is the agent’s, group-readable by design, and copied into the store at every evaluation. A key there is not the owner’s.
  • The recovery key in the journal, redacted later. A secret written down and then scrubbed is a secret that was written down. The scrubber is registered before the first line.
  • Deriving the recovery key from the passphrase. It exists to be independent of it.
  • Making the key on the host and passing it in. A test could, and a person at a console cannot; the installer is built for the person.