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