Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -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 -->