Initial Commit
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: crypto (crypto-trust-anchor-bootstrap)
|
||||
|
||||
**Package**: `@suaveplan/crypto`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time, pointing to a real test in `packages/crypto/crypto/src/trust-anchors/`.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Trust-anchor bootstrap
|
||||
|
||||
`@suaveplan/crypto` SHALL ship a trust-anchor loader at `src/trust-anchors/` that reads a self-verifying root public bundle from a caller-supplied filesystem reader and returns both the raw bytes (for direct use with `verifyChain`) and the parsed `PublicBundle` (for diagnostics). The loader SHALL self-verify the bundle in root-mode (`verifyPublicBundle(bytes, null)`) on every load — bytes that do not self-verify SHALL be rejected with `CRYPTO.TRUST_ANCHOR.SELF_VERIFY_FAILED`.
|
||||
|
||||
The package SHALL expose:
|
||||
|
||||
- `loadTrustAnchor(reader, path): Promise<TrustAnchor>` — explicit-path entry.
|
||||
- `loadTrustAnchorFromEnv(reader, options): Promise<TrustAnchor>` — env-driven entry. `options.getenv` is required (the universal package never reads `process.env` directly); production wires `(name) => process.env[name]`, tests inject deterministic stubs. `options.envVar` defaults to `SUAVEPLAN_TRUST_ANCHOR_PATH`.
|
||||
- `DEFAULT_TRUST_ANCHOR_ENV_VAR` — the canonical env-var name as a `const`.
|
||||
- Types `TrustAnchor`, `TrustAnchorReader`, `TrustAnchorEnvGetter`, `LoadTrustAnchorFromEnvOptions`.
|
||||
|
||||
The universal-tier package MUST NOT import `node:fs` or read `process.env` at the module-import level; all platform access is caller-injected via the `TrustAnchorReader` interface and the optional `getenv` callback. Production wires `(path) => fs.readFile(path)`; tests inject deterministic stubs.
|
||||
|
||||
#### Scenario: round-trip self-verify
|
||||
|
||||
- **WHEN** a self-signed root public bundle is supplied via the reader and `loadTrustAnchor(reader, path)` is called
|
||||
- **THEN** the call returns `{ bytes, bundle }` where `bytes` is byte-identical to the input and `bundle` has a 32-byte `sign` slot public key
|
||||
|
||||
#### Scenario: env-var default name
|
||||
|
||||
- **WHEN** `loadTrustAnchorFromEnv(reader, { getenv })` is called without `options.envVar`
|
||||
- **THEN** `getenv` is invoked with `"SUAVEPLAN_TRUST_ANCHOR_PATH"` and the returned path is read
|
||||
|
||||
#### Scenario: env-var name override
|
||||
|
||||
- **WHEN** `loadTrustAnchorFromEnv(reader, { envVar: "VAULT_ANCHOR", getenv })` is called
|
||||
- **THEN** `getenv` is invoked with `"VAULT_ANCHOR"` and the returned path is read
|
||||
|
||||
#### Scenario: missing env-var rejected
|
||||
|
||||
- **WHEN** `loadTrustAnchorFromEnv(reader, { envVar, getenv: () => undefined })` is called
|
||||
- **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.PATH_MISSING` and `context.envVar` reflects the requested name
|
||||
|
||||
#### Scenario: empty env-var string rejected
|
||||
|
||||
- **WHEN** `loadTrustAnchorFromEnv(reader, { envVar, getenv: () => "" })` is called
|
||||
- **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.PATH_MISSING`
|
||||
|
||||
#### Scenario: reader failure surfaces structured error
|
||||
|
||||
- **WHEN** the reader's `read(path)` rejects (e.g. ENOENT)
|
||||
- **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.READ_FAILED`, with the reader's error attached via `cause` and `context.path` equal to the requested path
|
||||
|
||||
#### Scenario: tampered bytes rejected
|
||||
|
||||
- **WHEN** the loader receives bytes that parse but fail `verifyPublicBundle(bytes, null)`
|
||||
- **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.SELF_VERIFY_FAILED` and `context.byteLength` reflects the actual length
|
||||
|
||||
#### Scenario: garbage bytes rejected
|
||||
|
||||
- **WHEN** the loader receives bytes that fail to parse as a public bundle
|
||||
- **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.SELF_VERIFY_FAILED`
|
||||
|
||||
**Verified by:** `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"loads, self-verifies, and returns byte-identical bytes"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"the returned bundle parses identically to a fresh verify on the bytes"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"uses the default env var name SUAVEPLAN_TRUST_ANCHOR_PATH"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"honors a caller-supplied envVar override"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"getenv is invoked exactly once with the resolved env-var name"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.PATH_MISSING when env var is unset"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.PATH_MISSING when env var is empty string"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.READ_FAILED when reader rejects"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.SELF_VERIFY_FAILED on tampered bundle bytes"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.SELF_VERIFY_FAILED on garbage bytes"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
Reference in New Issue
Block a user