first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user