Files
2026-05-27 23:29:53 +01:00

5.1 KiB

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"