first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
# `commands/keys/keys`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Non-chain key-lifecycle commands for `@suaveplan/server-keys`. These
functions are the library surface every key/view bin in the package
calls into: root keychain creation, server keychain derivation, server
keychain rotation, and the four read-only inspectors (`view-keys`,
`view-vault`, `view-public-key-file`, `view-chain-public`). The
sibling `keys-chain.ts` covers the hierarchical chain variants and
`verify-key-chain`; `keys-io.ts` holds the shared filesystem and
decryption helpers.
Every command writes through `@suaveplan/storage-file`'s
`NodeFsStorage`, so vault and public-key persistence flows through the
same gated I/O as the rest of the storage stack — no direct
`fs.writeFile` calls live in this module. The crypto-vault
orchestration (`createRootKeys`, `createServerKeys`, `rollServerKeys`,
`openVault`, `viewPublicKeys`) is the format owner.
## Features
- Filename convention mirroring upstream: `root.vault`/`root.pub`,
`server.vault`/`server.pub`, `server.v<N>.vault`/`server.v<N>.pub`.
- Structured `ServerKeysError(SERVER_KEYS_*)` failures everywhere —
callers always see a typed code on `error.code`.
- Injectable `KeysLogger` (defaults to silent) — bins wire a console
shim; tests can capture every printed line as a string array.
- Cooperative abort via `options.signal`; each command checks before
expensive crypto or filesystem work.
## Usage: Basic
```ts
import {
createRootKeysCommand,
createServerKeysCommand,
} from "@suaveplan/server-keys/commands/keys/keys";
await createRootKeysCommand("hunter2hunter2", { output: "/tmp/keys" });
await createServerKeysCommand(
"/tmp/keys/root.vault",
"hunter2hunter2",
"newserverpw",
{ output: "/tmp/keys" },
);
```
## Usage: Advanced
Drive the viewers from a higher-level orchestrator with a capturing
logger:
```ts
import {
viewKeysCommand,
viewVaultCommand,
viewPublicKeyFileCommand,
} from "@suaveplan/server-keys/commands/keys/keys";
const lines: string[] = [];
const logger = {
info: (m: string) => lines.push(`I:${m}`),
error: (m: string) => lines.push(`E:${m}`),
};
const stat = await viewVaultCommand("/tmp/keys/root.vault", { logger });
const keychain = await viewKeysCommand(
"/tmp/keys/root.vault",
"hunter2hunter2",
{ logger },
);
const pubChain = await viewPublicKeyFileCommand("/tmp/keys/root.pub", {
logger,
});
expect(stat.bytes).toBeGreaterThan(0);
expect(Object.keys(pubChain).length).toBeGreaterThan(0);
// Rotate with an abort guard:
const ac = new AbortController();
setTimeout(() => ac.abort(), 100);
await rollServerKeysCommand(
"/tmp/keys/root.vault",
"hunter2hunter2",
"newserverpw",
1,
{ output: "/tmp/keys", signal: ac.signal },
);
```
## API Reference
- `createRootKeysCommand(password, options?) => Promise<void>`
- `createServerKeysCommand(rootVaultPath, rootPassword, serverPassword,
options?) => Promise<void>`
- `rollServerKeysCommand(rootVaultPath, rootPassword, serverPassword,
currentVersion, options?) => Promise<void>`
- `viewKeysCommand(vaultPath, password, options?) => Promise<Keychain>`
- `viewVaultCommand(vaultPath, options?) =>
Promise<{ path, bytes }>`
- `viewPublicKeyFileCommand(publicKeyPath, options?) =>
Promise<PublicKeychain>`
- `viewChainPublicCommand(publicKeyPath, signKeyPath?, options?) =>
Promise<PublicKeychain>`
All options are `KeyCommandOptions = { output?, logger?, signal? }`.
## Implementation Notes
`viewKeysCommand` returns the decoded keychain instead of `void` so test
harnesses can assert on the result without re-decrypting; the
information is already in memory by the time the function logs, and
exposing it avoids a redundant decrypt in callers. Slot iteration goes
through `Object.entries(keychain as Record<string, unknown>)` because
`Keychain` is `Readonly<Record<PurposeTag, KeySlotPrivate>>` where
`PurposeTag` is a branded `string & { __brand }` — TS cannot iterate
branded keys directly, so a typed-string view is the correct narrowing.
The roll-server output uses `server.v<N>` (dot-separated) instead of
upstream's `server-v<N>` to keep version segments visually distinct in
operator listings.