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,116 @@
# `commands/keys/keys-io`
## Purpose
Private I/O and orchestration-layer helpers shared by every public keys
command. Extracted from `keys.ts` to keep each file under the 300-line
hard limit while still concentrating filesystem access, vault decryption,
signal checking, and logger defaulting in a single auditable module.
Every public failure surfaces as a `ServerKeysError` with the matching
`SERVER_KEYS_*` code: `FILE_NOT_FOUND`, `VAULT_DECRYPT_FAILED`,
`CHAIN_VERIFY_FAILED`, or `ABORTED`.
All filesystem I/O goes through `node:fs/promises` directly, NOT
through `@suaveplan/storage-file`. The storage-file driver appends
`.data` / `.meta` extensions to every key on write, which would force
operator scripts and downstream consumers to address artefacts by
`root.vault.data` instead of the conventional `root.vault`. The CLI
contract is byte-name-stable across upstream and suaveplan, so the
helpers stay on plain Node fs.
## Features
- `pickLogger(options)` — silent default; honours an injected `KeysLogger`.
- `checkSignal(signal, command)` — cooperative abort check that throws
`ServerKeysError(SERVER_KEYS_ABORTED)`.
- `writeVaultPair(dir, vaultName, publicName, vaultBytes, publicBytes)`
— single transaction that persists both vault and public-key file via
storage-file.
- `loadFile(path)` — bytes loader with FILE_NOT_FOUND translation.
- `decryptVault(bytes, password, path)` — VAULT_DECRYPT_FAILED on any
failure, preserving the `cause` chain.
- `loadRootKeysResult(rootVaultPath, password)` — reconstructs the
`RootKeysResult` shape the crypto-vault orchestration expects, by
re-reading the sibling `.pub` file.
- `extractSignPublicKey(bytes, sourcePath)` — pulls the `sign` slot's
public key out of a public-keychain blob; CHAIN_VERIFY_FAILED on
missing slot.
## Usage: Basic
```ts
import {
loadFile,
decryptVault,
writeVaultPair,
pickLogger,
} from "@suaveplan/server-keys/commands/keys/keys-io";
const bytes = await loadFile("./keys/root.vault");
const keychain = await decryptVault(bytes, password, "./keys/root.vault");
await writeVaultPair(
"./keys",
"server.vault",
"server.pub",
serverVaultBytes,
serverPubBytes,
);
pickLogger(undefined).info("done");
```
## Usage: Advanced
Compose with `checkSignal` for an abort-aware command body, and use
`loadRootKeysResult` to feed the crypto-vault `createServerKeys`
orchestration:
```ts
import { createServerKeys } from "@suaveplan/crypto-vault";
import {
checkSignal,
loadRootKeysResult,
writeVaultPair,
} from "@suaveplan/server-keys/commands/keys/keys-io";
export async function deriveServer(
rootVaultPath: string,
rootPassword: string,
serverPassword: string,
signal: AbortSignal | undefined,
) {
checkSignal(signal, "derive-server");
const rootResult = await loadRootKeysResult(rootVaultPath, rootPassword);
checkSignal(signal, "derive-server");
const server = await createServerKeys(rootResult, serverPassword);
return writeVaultPair(
"./keys",
"server.vault",
"server.pub",
server.vaultBytes,
server.publicKeyBytes,
);
}
```
## API Reference
- `pickLogger(options) => KeysLogger`
- `checkSignal(signal, command) => void` (throws on abort)
- `writeVaultPair(dir, vaultName, publicName, vaultBytes, publicBytes) =>
Promise<{ vaultPath, publicPath }>`
- `loadFile(path) => Promise<Uint8Array>`
- `decryptVault(bytes, password, path) => Promise<Keychain>`
- `loadRootKeysResult(path, password) => Promise<RootKeysResult>`
- `extractSignPublicKey(bytes, sourcePath) => Promise<Uint8Array>`
## Implementation Notes
`storageFor` constructs a fresh `NodeFsStorage` per call instead of
caching: each call's `rootDir` is independent (the keys CLI talks to
arbitrary user paths), so a cache would conflate unrelated paths and
introduce subtle leakage between commands. `loadRootKeysResult` derives
the sibling `.pub` path with a `.vault → .pub` substitution because the
upstream layout convention is mirrored verbatim — any deviation here
breaks `create-server-keys` against an existing `create-root-keys`
output. The error translations preserve `cause` so the SuaveplanError
chain stays walkable in operator log forensics.