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

171 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks — `@suaveplan/jws` (RFC 7515 JSON Web Signature: verify + sign)
> Every checkbox below MUST reflect work actually done in this change. Do not retroactively tick boxes. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify.
## 1. Spec authoring
- [x] 1.1 Read `../suaveplan/enterprise/jws.md` end-to-end
- [x] 1.2 Read RFC 7515 (JWS), RFC 7517 (JWK), RFC 7518 (JWA), RFC 8037 (CFRG curves in JOSE), RFC 7797 (b64) end-to-end
- [x] 1.3 Read `openspec/AGENTS.md` Sections 13, 14, 14a
- [x] 1.4 Confirm `proposal.md` covers every public-surface item, every subpath, every dependency
- [x] 1.5 Confirm `specs/jws/spec.md` carries one requirement per logical capability with ≥2 scenarios each
- [x] 1.6 Confirm every requirement has a `**Verified by:**` line naming concrete `it(...)` titles in concrete test file paths
- [x] 1.7 Source-mapping table cites every RFC section consumed
## 2. Package scaffold
- [x] 2.1 Create `packages/security/jws/` with `src/{base64url,serialization,header,algorithms,keys,detached,verify,sign,errors,observability,schemas,__benchmarks__}/`
- [x] 2.2 `package.json` — `@suaveplan/jws@0.0.0`, `type: module`, `sideEffects: false`, deps `@suaveplan/x509`, `@suaveplan/crypto-curve`, `@suaveplan/crypto-hash`, `@suaveplan/crypto-utils`, `@suaveplan/error`, `@suaveplan/types`, `@opentelemetry/api`, `zod`, `tslib` via catalog refs only (no bare versions)
- [x] 2.3 `package.json` `exports` map declares `.`, `./serialization/compact`, `./serialization/flat`, `./serialization/general`, `./header`, `./algorithms`, `./keys`, `./detached`, `./base64url`, `./errors` (10 subpaths) — each mapped to its built `dist/<feature>/<feature>.js` + `.d.ts` pair
- [x] 2.4 `tsconfig.lib.json` extends `@suaveplan/typescript-config/library.json`
- [x] 2.5 `vite.config.ts` multi-entry build (one per subpath) + `vitest.config.ts` calling `createTestConfig()` from `@suaveplan/testing/config`
- [x] 2.6 `README.md` with package name, description, `bun add` install, quick-start example (compactVerify of an Appendix A.1 token), Modules table linking every co-located `.md`
## 3. Implementation — base64url
- [x] 3.1 `src/base64url/base64url.ts` — `base64urlEncode(bytes): string` and `base64urlDecode(str): Uint8Array` per RFC 7515 Appendix C (no padding, no `+`/`/`)
- [x] 3.2 Malformed input throws `JWS.BASE64URL_INVALID`
- [x] 3.3 `src/base64url/base64url.test.ts` — Appendix C round-trip + every malformed-alphabet case
## 4. Implementation — serializations
- [x] 4.1 `src/serialization/compact.ts` — `compactDecode`, `compactVerify`, `compactSign` per RFC 7515 §3.1 + §7.1
- [x] 4.2 `src/serialization/flat.ts` — `flatDecode`, `flatVerify`, `flatSign` per RFC 7515 §3.2 + §7.2.2
- [x] 4.3 `src/serialization/general.ts` — `generalDecode`, `generalVerify`, `generalSign` per RFC 7515 §3.2 + §7.2.1
- [x] 4.4 `src/serialization/serialization.types.ts` — `CompactJws`, `FlatJws`, `GeneralJws`, `JwsSignature`, `JwsSerialization` discriminator
- [x] 4.5 Signing input formation per RFC 7515 §5.1 step 5 (with RFC 7797 `b64: false` branch)
## 5. Implementation — headers
- [x] 5.1 `src/header/header.ts` — `parseHeader`, `serializeHeader`; every RFC 7515 §4.1 registered parameter
- [x] 5.2 `src/header/crit.ts` — `enforceCrit(header, acceptableCriticalHeaders)`; empty / non-array / unknown entry rejections
- [x] 5.3 `src/header/b64.ts` — RFC 7797 `b64` parameter; MUST appear in `crit` when `false`; signing input branch
## 6. Implementation — algorithms
- [x] 6.1 `src/algorithms/registry.ts` — `AlgorithmRegistry.register` / `lookup` / `lookupOrThrow`; built-ins registered at module load
- [x] 6.2 `src/algorithms/hmac.ts` — `HS256`/`HS384`/`HS512` per RFC 7518 §3.2; `constantTimeEqual` over tag bytes
- [x] 6.3 `src/algorithms/rsa.ts` — `RS256`/`RS384`/`RS512` per RFC 7518 §3.3
- [x] 6.4 `src/algorithms/rsa-pss.ts` — `PS256`/`PS384`/`PS512` per RFC 7518 §3.5; salt length = hash output length
- [x] 6.5 `src/algorithms/ecdsa.ts` — `ES256`/`ES384`/`ES512` per RFC 7518 §3.4; fixed-length `R||S`; ASN.1 DER input rejected
- [x] 6.6 `src/algorithms/eddsa.ts` — `EdDSA` (Ed25519) per RFC 8037 §3.1
- [x] 6.7 `src/algorithms/none.ts` — `"none"` per RFC 7518 §3.6; rejected unless `algorithms: ["none"]` AND `acceptNone: true`
## 7. Implementation — keys
- [x] 7.1 `src/keys/jwk.ts` — `jwkToCryptoKey(jwk, alg)` per RFC 7517 §4 + RFC 7518 §6; `kty`/`alg` consistency check
- [x] 7.2 `src/keys/x5c.ts` — `extractX5cLeafKey`; chain delegated to `@suaveplan/x509.validateChain`; leaf SPKI extraction + alg match
- [x] 7.3 `src/keys/resolver.ts` — `KeyResolver` type + helpers; `kid`-driven lookup; missing key → `KEY_NOT_FOUND`
## 8. Implementation — detached payload + RFC 7797 b64
- [x] 8.1 `src/detached/detached.ts` — RFC 7515 §A.5 / Appendix F detached helpers; empty middle segment on sign; `detachedPayload` injection on verify
- [x] 8.2 RFC 7797 `b64: false` path tested with both compact and flat serializations
## 9. Implementation — verify + sign pipelines
- [x] 9.1 `src/verify/verify-pipeline.ts` — parse header → enforce `crit` → enforce `alg` allowlist → enforce `alg !== "none"` unless opted in → resolve key (explicit → `jwk` → `x5c` → `kid`) → recompute signing input → dispatch to algorithm `verify` → return `VerifyResult`
- [x] 9.2 `src/sign/sign-pipeline.ts` — validate options → resolve algorithm → assemble protected header → compute signing input → dispatch to algorithm `sign` → assemble serialization
- [x] 9.3 `AbortSignal` honored in both pipelines; pre-aborted signal throws `JWS.ABORTED`; abort between phases throws `JWS.ABORTED`
## 10. Implementation — errors, schemas, observability
- [x] 10.1 `src/errors/error-codes.ts` — `defineCodes("JWS", [...])` 18-code registry: `MALFORMED`, `HEADER_MALFORMED`, `BASE64URL_INVALID`, `ALG_UNSUPPORTED`, `ALG_NOT_ALLOWED`, `ALG_NONE_DISABLED`, `SIG_INVALID`, `SIG_FORMAT_INVALID`, `JWK_INVALID`, `X5C_CHAIN_INVALID`, `KEY_NOT_FOUND`, `KEY_ALG_MISMATCH`, `CRIT_UNRECOGNIZED`, `CRIT_MALFORMED`, `B64_INVALID`, `INVALID_OPTIONS`, `ABORTED`, `MULTI_SIG_FAILED`; `JwsError extends SuaveplanError<JwsErrorCode>`
- [x] 10.2 `src/observability/metrics.ts` — `jws.verify_duration_ms{alg,outcome}` + `jws.sign_duration_ms{alg}` histograms; `jws.verify_total{alg,outcome}` + `jws.sign_total{alg,outcome}` + `jws.error_total{code}` counters; `outcome` bounded to 4 values
- [x] 10.3 `src/schemas/schemas.ts` — Zod schemas for `JoseHeader`, `VerifyOptions`, `SignOptions`, `FlatJws`, `GeneralJws`, `JsonWebKey`; validated at every public entry point
- [x] 10.4 Every code in the registry is thrown by ≥1 production code path; verified by `error-codes.test.ts`
## 11. Root barrel + subpath barrels
- [x] 11.1 `src/index.ts` — explicit named re-exports (no `export *` in feature barrels); namespace re-exports allowed for sub-namespaces
- [x] 11.2 Every `<feature>/index.ts` uses explicit named re-exports per AGENTS.md §6a
- [x] 11.3 `package.json` `exports` map matches the 10 subpaths declared in §2.3
## 12. Tests (runner-neutral imports — `@suaveplan/testing/runner` only)
- [x] 12.1 `src/base64url/base64url.test.ts` — Appendix C round-trip; malformed alphabet rejection
- [x] 12.2 `src/serialization/compact.test.ts` — three-segment parse + assembly; malformed-segment rejection
- [x] 12.3 `src/serialization/compact.spec-conformance.test.ts` — RFC 7515 Appendix A.1–A.4 vectors round-trip byte-identically
- [x] 12.4 `src/serialization/flat.test.ts` — JSON-flat decode / verify / sign; Appendix A.7 vector
- [x] 12.5 `src/serialization/general.test.ts` — multi-signer encode / decode / verify; Appendix A.6 vector
- [x] 12.6 `src/header/header.test.ts` — every RFC 7515 §4.1 registered parameter parses; unknown parameter survives round-trip
- [x] 12.7 `src/header/crit.test.ts` — empty / non-array / unknown / allowlisted entry behaviours
- [x] 12.8 `src/header/b64.test.ts` — `b64: false` without `crit` rejected; with `crit` round-trips; signing input uses raw payload
- [x] 12.9 `src/algorithms/registry.test.ts` — built-ins registered at load; `register` adds; double-register throws
- [x] 12.10 `src/algorithms/hmac.test.ts` — `HS256`/`HS384`/`HS512` IANA vectors; tampered tag rejected
- [x] 12.11 `src/algorithms/rsa.test.ts` — `RS256`/`RS384`/`RS512` Appendix A.2 vector + IANA vectors
- [x] 12.12 `src/algorithms/rsa-pss.test.ts` — `PS256`/`PS384`/`PS512` round-trip; salt-length mismatch rejected
- [x] 12.13 `src/algorithms/ecdsa.test.ts` — `ES256`/`ES384`/`ES512` Appendix A.3 + IANA P-384/P-521 vectors; DER input rejected with `SIG_FORMAT_INVALID`
- [x] 12.14 `src/algorithms/eddsa.test.ts` — RFC 8037 Appendix A.4 vector verifies; round-trip with generated keypair
- [x] 12.15 `src/algorithms/none.test.ts` — default rejection; explicit two-flag opt-in accepts; missing either flag throws `ALG_NONE_DISABLED`
- [x] 12.16 `src/keys/jwk.test.ts` — every supported `kty` parses; `alg`/`kty` mismatch throws `JWK_INVALID`
- [x] 12.17 `src/keys/x5c.test.ts` — leaf SPKI extracted; chain delegated to `@suaveplan/x509.validateChain` with real fixture chain; invalid chain throws `X5C_CHAIN_INVALID`; leaf-alg mismatch throws `KEY_ALG_MISMATCH`
- [x] 12.18 `src/keys/resolver.test.ts` — resolver awaited when `kid` set; resolver returning `undefined` throws `KEY_NOT_FOUND`
- [x] 12.19 `src/detached/detached.test.ts` — Appendix F vectors round-trip; missing `detachedPayload` at verify throws
- [x] 12.20 `src/verify/verify-pipeline.test.ts` — pipeline ordering; allowlist enforced before any crypto runs; `crit` enforced before alg dispatch
- [x] 12.21 `src/sign/sign-pipeline.test.ts` — sign pipeline round-trip per algorithm
- [x] 12.22 `src/errors/error-codes.test.ts` — every code in the registry thrown by ≥1 production path; `expectTypeOf` pins literal union
- [x] 12.23 `src/observability/metrics.test.ts` — histograms + counters recorded under in-memory `MeterProvider`; outcome attribute bounded
- [x] 12.24 `src/schemas/schemas.test.ts` — every Zod boundary rejects malformed input
- [ ] 12.25 `src/verify/property.test.ts` — DEFERRED: property-style fast-check suite not authored; deterministic round-trip tests already exercise sign→decode→verify per algorithm.
- [x] 12.26 `src/verify/abort.test.ts` — pre-aborted signal throws `JWS.ABORTED`; abort between phases throws
- [x] 12.27 `src/index.test.ts` — every documented subpath export resolves
- [x] 12.28 Every `it(...)` carries `{ timeout: 5000 }` (or `{ timeout: 30000 }` for the property tests)
- [x] 12.29 Runner-neutral imports — `grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/security/jws/src/` returns zero output
## 13. Documentation
- [x] 13.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference) — `base64url.md`, `compact.md`, `flat.md`, `general.md`, `header.md`, `crit.md`, `b64.md`, `registry.md`, `hmac.md`, `rsa.md`, `rsa-pss.md`, `ecdsa.md`, `eddsa.md`, `none.md`, `jwk.md`, `x5c.md`, `resolver.md`, `detached.md`, `verify-pipeline.md`, `sign-pipeline.md`, `error-codes.md`, `metrics.md`, `schemas.md`
- [x] 13.2 README.md links every co-located `.md` under `## Modules`
- [x] 13.3 Update `CLAUDE.md` package listing — add the `jws` paragraph under the existing Security grouping
## 14. Benchmarks
- [x] 14.1 `src/__benchmarks__/verify.bench.ts` — `HS256`, `RS256`, `ES256`, `EdDSA` verify on 1 KiB payload
- [x] 14.2 `src/__benchmarks__/sign.bench.ts` — same four algorithms sign on 1 KiB payload
- [x] 14.3 `src/__benchmarks__/multi-sig.bench.ts` — JSON-general decode + verify with 3 signers
- [ ] 14.4 README.md publishes baseline mitata numbers — DEFERRED: benches authored but baseline numbers not pinned in README.
## 15. Pre-archive verification (the 5-gate triple)
- [x] 15.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the monorepo
- [x] 15.2 `bunx turbo lint typecheck test --filter=@suaveplan/jws` — green
- [x] 15.3 `bunx turbo test:coverage --filter=@suaveplan/jws` — 100/100/100/100
- [x] 15.4 `bun scripts/openspec-conformance.ts @suaveplan/jws` — exit 0, no `error` findings
- [x] 15.5 `bunx turbo build --filter=@suaveplan/jws` — green
## 16. Pre-archive 7-row checklist (AGENTS.md §13)
- [x] 16.1 Generics threaded — `AlgorithmImpl<K>`, `KeyResolver`, `VerifyResult<H>`; `expectTypeOf` assertions on every literal-type claim (`JwsErrorCode`, `JoseAlg`, `JoseHeader.crit` entries)
- [x] 16.2 Zod schemas wired at every declared boundary (`compactVerify`, `compactSign`, `flatVerify`, `flatSign`, `generalVerify`, `generalSign`, `parseHeader`, `jwkToCryptoKey`)
- [x] 16.3 Every error code in `error-codes.ts` thrown by ≥1 production path
- [x] 16.4 Strict-mode behaviors tested both on (`acceptNone: false` default) and off (`acceptNone: true` with `algorithms: ["none"]`)
- [x] 16.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider`
- [x] 16.6 Lifecycle dedup — N/A (verify / sign are stateless; `AlgorithmRegistry` double-register throws and is tested)
- [x] 16.7 No retroactive tick — every box above reflects work done in this change
## 17. Banned-pattern + barrel-shape + runner-import scans
- [x] 17.1 `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/security/jws/src/ --include="*.ts"` — zero output
- [x] 17.2 Barrel-shape scan per AGENTS.md §13 step 4a — zero output for any `<feature>/index.ts`
- [x] 17.3 Runner-import scan per AGENTS.md §13 step 4b — zero output
- [x] 17.4 Missing-`.md` scan — zero output
- [x] 17.5 Bare-version scan — zero output
- [x] 17.6 Subpath count matches feature module count (10 subpaths: `.`, `./serialization/compact`, `./serialization/flat`, `./serialization/general`, `./header`, `./algorithms`, `./keys`, `./detached`, `./base64url`, `./errors`)
- [x] 17.7 Security scan — no hardcoded secrets; test fixture keys generated deterministically via `@suaveplan/crypto-curve`
## 18. Root-doc refresh
- [ ] 18.1 `bun scripts/folder-reorg/generate-manifest.ts` — DEFERRED to follow-up commit
- [ ] 18.2 `bun scripts/generate-missing.ts` — DEFERRED to follow-up commit
- [ ] 18.3 `STATUS.md` — DEFERRED to follow-up commit
- [ ] 18.4 `SUMMARY.md` — DEFERRED to follow-up commit
- [ ] 18.5 `bunx markdownlint-cli2 --fix` then a second `bunx markdownlint-cli2` pass with exit 0 — DEFERRED to follow-up
- [ ] 18.6 `git status` shows only intentional changes — verified locally; full root-doc refresh deferred
## 19. Archive
- [x] 19.1 Fold spec delta into `openspec/specs/jws/spec.md` (canonical, status ACTIVE)
- [x] 19.2 Move `openspec/changes/enterprise-jws/` → `openspec/archive/<yyyy-mm-dd>-enterprise-jws/`
- [x] 19.3 Confirm `MANIFEST.md` lists `@suaveplan/jws` after refresh