first commit
This commit is contained in:
147
docs/security-stack/crypto/crypto-keys/src/factory/factory.md
Normal file
147
docs/security-stack/crypto/crypto-keys/src/factory/factory.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# factory
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Hierarchical-keychain factory helpers that compose `generateKeychain`
|
||||
from `@suaveplan/crypto` with `ChainedKeySigner.signWithSlot` to
|
||||
produce signed chains in one call. Three functions cover the common
|
||||
pattern: bootstrap a root keychain, derive a server keychain bound to
|
||||
the root, and roll a server keychain forward to the next version
|
||||
without changing the root.
|
||||
|
||||
These functions are Ed25519-only in v0.0.0 because the crypto
|
||||
keychain v1 grammar only generates Ed25519 sign-slots. Callers that
|
||||
need a different curve at any chain level should reach for
|
||||
`ChainedKeySigner.signRaw` directly instead.
|
||||
|
||||
## Features
|
||||
|
||||
- `createRootKeysWithChain` — fresh keychain + self-signed chain
|
||||
- `createServerKeysWithChain` — derived keychain + chain bound to root
|
||||
- `rollServerKeysWithChain` — re-derive at next slot version, re-sign
|
||||
- All three accept an injected `signer?` for custom hasher/codec choices
|
||||
- Strict `INVALID_KEYCHAIN` errors when root slots are missing or empty
|
||||
- Sensible `LayoutV1Fields` defaults (`masterVersion: 1, userId: 0, depth: 0, slotVersion: 1`)
|
||||
- Default `Clock` is `Date.now` — caller can override via `generate.clock`
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
ChainedKeySigner,
|
||||
createRootKeysWithChain,
|
||||
createServerKeysWithChain,
|
||||
} from "@suaveplan/crypto-keys";
|
||||
|
||||
// Bootstrap a root keychain + self-signed chain
|
||||
const { keys: rootKeys, chain: rootChain } = await createRootKeysWithChain();
|
||||
|
||||
// Derive a server keychain whose chain anchors to the root
|
||||
const { keys: serverKeys, chain: serverChain } = await createServerKeysWithChain({
|
||||
rootKeys,
|
||||
rootChain,
|
||||
});
|
||||
|
||||
// Verify end-to-end
|
||||
const verifier = new ChainedKeySigner();
|
||||
const result = await verifier.verify(serverChain, {
|
||||
algorithm: "ed25519",
|
||||
publicKey: (rootKeys as Record<string, { publicKey: Uint8Array }>).sign.publicKey,
|
||||
});
|
||||
console.log(result.valid, result.depth); // true 2
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
ChainedKeySigner,
|
||||
DomainHasher,
|
||||
createRootKeysWithChain,
|
||||
createServerKeysWithChain,
|
||||
rollServerKeysWithChain,
|
||||
} from "@suaveplan/crypto-keys";
|
||||
|
||||
const signer = new ChainedKeySigner({
|
||||
hasher: new DomainHasher({ domainTag: "tenant.acme.v1" }),
|
||||
});
|
||||
|
||||
// Tagged root
|
||||
const { keys: rootKeys, chain: rootChain } = await createRootKeysWithChain({
|
||||
signer,
|
||||
generate: { userId: 42 as never }, // custom layout fields per app
|
||||
});
|
||||
|
||||
// First server keychain (slot version 1)
|
||||
const v1 = await createServerKeysWithChain({ rootKeys, rootChain, signer });
|
||||
|
||||
// Six months later: roll to slot version 2, same root binding
|
||||
const v2 = await rollServerKeysWithChain({
|
||||
rootKeys,
|
||||
rootChain,
|
||||
currentVersion: 1,
|
||||
signer,
|
||||
});
|
||||
|
||||
// Issuance chain: root -> v1 -> v2 -> leaf user
|
||||
const leaf = await createServerKeysWithChain({
|
||||
rootKeys,
|
||||
rootChain: v2.chain,
|
||||
signer,
|
||||
});
|
||||
|
||||
const verifier = new ChainedKeySigner({
|
||||
hasher: new DomainHasher({ domainTag: "tenant.acme.v1" }),
|
||||
});
|
||||
const result = await verifier.verify(leaf.chain, {
|
||||
algorithm: "ed25519",
|
||||
publicKey: (rootKeys as Record<string, { publicKey: Uint8Array }>).sign.publicKey,
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `async function createRootKeysWithChain(options?)`
|
||||
|
||||
| Field | Type | Default |
|
||||
| ----------------- | ------------------------------------- | ----------------------- |
|
||||
| `signer?` | `ChainedKeySigner` | `new ChainedKeySigner()` |
|
||||
| `generate?` | `Partial<GenerateKeychainOptions>` | sensible defaults |
|
||||
|
||||
Returns `{ keys: Keychain<LayoutV1Fields>; chain: Uint8Array }`.
|
||||
|
||||
### `async function createServerKeysWithChain(options)`
|
||||
|
||||
| Field | Type | Required |
|
||||
| ------------- | ------------------------------------- | -------- |
|
||||
| `rootKeys` | `Keychain<LayoutV1Fields>` | yes |
|
||||
| `rootChain` | `Uint8Array` | yes |
|
||||
| `signer?` | `ChainedKeySigner` | no |
|
||||
| `generate?` | `Partial<GenerateKeychainOptions>` | no |
|
||||
|
||||
Raises `CHAIN_KEYS.INVALID_KEYCHAIN` if `rootKeys.master` or `rootKeys.sign` is missing, has an empty `secretKey`, or has a non-Ed25519 `algorithm`.
|
||||
|
||||
### `async function rollServerKeysWithChain(options)`
|
||||
|
||||
| Field | Type | Required |
|
||||
| ----------------- | -------------------------- | -------- |
|
||||
| `rootKeys` | `Keychain<LayoutV1Fields>` | yes |
|
||||
| `rootChain` | `Uint8Array` | yes |
|
||||
| `currentVersion` | `number` (≥ 0, integer) | yes |
|
||||
| `signer?` | `ChainedKeySigner` | no |
|
||||
|
||||
Generates a new keychain with `slotVersion: currentVersion + 1` and re-signs against the same root. Raises `CHAIN_KEYS.INVALID_KEYCHAIN` when `currentVersion` is negative or non-integer, or when the root slots are missing/invalid.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
crypto's `Keychain` is `Record<PurposeTag, KeySlotPrivate>` where `PurposeTag` is a branded string type. The factory module uses a small `getSlot(keys, tag)` helper that casts through `Record<string, KeySlotPrivate>` to access individual slots; this keeps the TypeScript narrowing clean without pulling in the `PurposeTag` brand machinery.
|
||||
|
||||
`LayoutV1Fields` is re-declared locally because it's not currently re-exported from `@suaveplan/crypto/keychain`. The shape is identical to the upstream definition and `generateKeychain` accepts any object satisfying it structurally. If a future crypto release adds a `./derivation` subpath export, this declaration can be replaced with the canonical import.
|
||||
|
||||
The factory functions trust `generateKeychain`'s contract — that every generated keychain contains all 8 default-purpose slots with valid Ed25519 sign-slots — and do not re-validate the freshly-generated output. Validation is concentrated at the boundaries: `signWithSlot` rejects non-Ed25519 slots, and `assertRootSlots` checks every caller-supplied root before use. This keeps the happy path lean and pushes the cost of bad input onto the caller's stack frame.
|
||||
|
||||
`signWithSlot` takes an optional `parent: Uint8Array`; the factory functions pass `rootChain` when extending and omit it for the self-signed root. `ChainedKeySigner` distinguishes a root signature (no parent) from a non-root signature (parent supplied) by the presence of the argument — there's no separate "root" API.
|
||||
Reference in New Issue
Block a user