fix(admin): make create_new_key idempotent — never clobber an existing key (#39) #40

Merged
padreug merged 1 commit from fix-create-new-key-idempotent into dev 2026-06-21 14:01:48 +00:00

1 commit

Author SHA1 Message Date
2cf3e55bf4 fix(admin): make create_new_key idempotent — never clobber an existing key
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
`create_new_key` unconditionally generated a fresh keypair and let
`saveEncrypted` overwrite `config.keys[keyName]` on disk. So calling it
with a name that already exists SILENTLY DESTROYED the in-use signing
key: the old encrypted nsec was overwritten, surviving only in the
running process's in-memory Backend until the next restart, then gone.

This breaks the contract callers already rely on. spirekeeper's
`pair_spire` re-pairs a machine through the same `spire-<id>` keyName and
documents the assumption verbatim — "create_new_key is idempotent —
returns the existing key if the name is taken" (pairing.py). It wasn't:
a re-pair rotated the machine's spire identity and orphaned everything
bound to the old key (tokens, beacons, wallet routing), unrecoverably.

Guard at the top of the command: if a key with this name already exists,
recover and return it instead of generating + overwriting.
  - no `_nsec`: decrypt the existing entry with the supplied passphrase
    and return its npub (the idempotent re-pair path).
  - explicit `_nsec`, or an unrecognized/!iv/!data entry, or a passphrase
    that doesn't decrypt the existing key: throw rather than overwrite —
    destroying a key must never be a silent side effect of "create".

Functionally verified on the dev bunker: a repeat `/pair` for an
already-paired machine now returns the existing spire pubkey with the
on-disk key entry unchanged, where before it minted a new identity and
clobbered the old blob.
2026-06-21 15:51:13 +02:00