15 KiB
15 KiB
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
- 1.1 Read
../suaveplan/enterprise/jws.mdend-to-end - 1.2 Read RFC 7515 (JWS), RFC 7517 (JWK), RFC 7518 (JWA), RFC 8037 (CFRG curves in JOSE), RFC 7797 (b64) end-to-end
- 1.3 Read
openspec/AGENTS.mdSections 13, 14, 14a - 1.4 Confirm
proposal.mdcovers every public-surface item, every subpath, every dependency - 1.5 Confirm
specs/jws/spec.mdcarries one requirement per logical capability with ≥2 scenarios each - 1.6 Confirm every requirement has a
**Verified by:**line naming concreteit(...)titles in concrete test file paths - 1.7 Source-mapping table cites every RFC section consumed
2. Package scaffold
- 2.1 Create
packages/security/jws/withsrc/{base64url,serialization,header,algorithms,keys,detached,verify,sign,errors,observability,schemas,__benchmarks__}/ - 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,tslibvia catalog refs only (no bare versions) - 2.3
package.jsonexportsmap declares.,./serialization/compact,./serialization/flat,./serialization/general,./header,./algorithms,./keys,./detached,./base64url,./errors(10 subpaths) — each mapped to its builtdist/<feature>/<feature>.js+.d.tspair - 2.4
tsconfig.lib.jsonextends@suaveplan/typescript-config/library.json - 2.5
vite.config.tsmulti-entry build (one per subpath) +vitest.config.tscallingcreateTestConfig()from@suaveplan/testing/config - 2.6
README.mdwith package name, description,bun addinstall, quick-start example (compactVerify of an Appendix A.1 token), Modules table linking every co-located.md
3. Implementation — base64url
- 3.1
src/base64url/base64url.ts—base64urlEncode(bytes): stringandbase64urlDecode(str): Uint8Arrayper RFC 7515 Appendix C (no padding, no+//) - 3.2 Malformed input throws
JWS.BASE64URL_INVALID - 3.3
src/base64url/base64url.test.ts— Appendix C round-trip + every malformed-alphabet case
4. Implementation — serializations
- 4.1
src/serialization/compact.ts—compactDecode,compactVerify,compactSignper RFC 7515 §3.1 + §7.1 - 4.2
src/serialization/flat.ts—flatDecode,flatVerify,flatSignper RFC 7515 §3.2 + §7.2.2 - 4.3
src/serialization/general.ts—generalDecode,generalVerify,generalSignper RFC 7515 §3.2 + §7.2.1 - 4.4
src/serialization/serialization.types.ts—CompactJws,FlatJws,GeneralJws,JwsSignature,JwsSerializationdiscriminator - 4.5 Signing input formation per RFC 7515 §5.1 step 5 (with RFC 7797
b64: falsebranch)
5. Implementation — headers
- 5.1
src/header/header.ts—parseHeader,serializeHeader; every RFC 7515 §4.1 registered parameter - 5.2
src/header/crit.ts—enforceCrit(header, acceptableCriticalHeaders); empty / non-array / unknown entry rejections - 5.3
src/header/b64.ts— RFC 7797b64parameter; MUST appear incritwhenfalse; signing input branch
6. Implementation — algorithms
- 6.1
src/algorithms/registry.ts—AlgorithmRegistry.register/lookup/lookupOrThrow; built-ins registered at module load - 6.2
src/algorithms/hmac.ts—HS256/HS384/HS512per RFC 7518 §3.2;constantTimeEqualover tag bytes - 6.3
src/algorithms/rsa.ts—RS256/RS384/RS512per RFC 7518 §3.3 - 6.4
src/algorithms/rsa-pss.ts—PS256/PS384/PS512per RFC 7518 §3.5; salt length = hash output length - 6.5
src/algorithms/ecdsa.ts—ES256/ES384/ES512per RFC 7518 §3.4; fixed-lengthR||S; ASN.1 DER input rejected - 6.6
src/algorithms/eddsa.ts—EdDSA(Ed25519) per RFC 8037 §3.1 - 6.7
src/algorithms/none.ts—"none"per RFC 7518 §3.6; rejected unlessalgorithms: ["none"]ANDacceptNone: true
7. Implementation — keys
- 7.1
src/keys/jwk.ts—jwkToCryptoKey(jwk, alg)per RFC 7517 §4 + RFC 7518 §6;kty/algconsistency check - 7.2
src/keys/x5c.ts—extractX5cLeafKey; chain delegated to@suaveplan/x509.validateChain; leaf SPKI extraction + alg match - 7.3
src/keys/resolver.ts—KeyResolvertype + helpers;kid-driven lookup; missing key →KEY_NOT_FOUND
8. Implementation — detached payload + RFC 7797 b64
- 8.1
src/detached/detached.ts— RFC 7515 §A.5 / Appendix F detached helpers; empty middle segment on sign;detachedPayloadinjection on verify - 8.2 RFC 7797
b64: falsepath tested with both compact and flat serializations
9. Implementation — verify + sign pipelines
- 9.1
src/verify/verify-pipeline.ts— parse header → enforcecrit→ enforcealgallowlist → enforcealg !== "none"unless opted in → resolve key (explicit →jwk→x5c→kid) → recompute signing input → dispatch to algorithmverify→ returnVerifyResult - 9.2
src/sign/sign-pipeline.ts— validate options → resolve algorithm → assemble protected header → compute signing input → dispatch to algorithmsign→ assemble serialization - 9.3
AbortSignalhonored in both pipelines; pre-aborted signal throwsJWS.ABORTED; abort between phases throwsJWS.ABORTED
10. Implementation — errors, schemas, observability
- 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> - 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;outcomebounded to 4 values - 10.3
src/schemas/schemas.ts— Zod schemas forJoseHeader,VerifyOptions,SignOptions,FlatJws,GeneralJws,JsonWebKey; validated at every public entry point - 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
- 11.1
src/index.ts— explicit named re-exports (noexport *in feature barrels); namespace re-exports allowed for sub-namespaces - 11.2 Every
<feature>/index.tsuses explicit named re-exports per AGENTS.md §6a - 11.3
package.jsonexportsmap matches the 10 subpaths declared in §2.3
12. Tests (runner-neutral imports — @suaveplan/testing/runner only)
- 12.1
src/base64url/base64url.test.ts— Appendix C round-trip; malformed alphabet rejection - 12.2
src/serialization/compact.test.ts— three-segment parse + assembly; malformed-segment rejection - 12.3
src/serialization/compact.spec-conformance.test.ts— RFC 7515 Appendix A.1–A.4 vectors round-trip byte-identically - 12.4
src/serialization/flat.test.ts— JSON-flat decode / verify / sign; Appendix A.7 vector - 12.5
src/serialization/general.test.ts— multi-signer encode / decode / verify; Appendix A.6 vector - 12.6
src/header/header.test.ts— every RFC 7515 §4.1 registered parameter parses; unknown parameter survives round-trip - 12.7
src/header/crit.test.ts— empty / non-array / unknown / allowlisted entry behaviours - 12.8
src/header/b64.test.ts—b64: falsewithoutcritrejected; withcritround-trips; signing input uses raw payload - 12.9
src/algorithms/registry.test.ts— built-ins registered at load;registeradds; double-register throws - 12.10
src/algorithms/hmac.test.ts—HS256/HS384/HS512IANA vectors; tampered tag rejected - 12.11
src/algorithms/rsa.test.ts—RS256/RS384/RS512Appendix A.2 vector + IANA vectors - 12.12
src/algorithms/rsa-pss.test.ts—PS256/PS384/PS512round-trip; salt-length mismatch rejected - 12.13
src/algorithms/ecdsa.test.ts—ES256/ES384/ES512Appendix A.3 + IANA P-384/P-521 vectors; DER input rejected withSIG_FORMAT_INVALID - 12.14
src/algorithms/eddsa.test.ts— RFC 8037 Appendix A.4 vector verifies; round-trip with generated keypair - 12.15
src/algorithms/none.test.ts— default rejection; explicit two-flag opt-in accepts; missing either flag throwsALG_NONE_DISABLED - 12.16
src/keys/jwk.test.ts— every supportedktyparses;alg/ktymismatch throwsJWK_INVALID - 12.17
src/keys/x5c.test.ts— leaf SPKI extracted; chain delegated to@suaveplan/x509.validateChainwith real fixture chain; invalid chain throwsX5C_CHAIN_INVALID; leaf-alg mismatch throwsKEY_ALG_MISMATCH - 12.18
src/keys/resolver.test.ts— resolver awaited whenkidset; resolver returningundefinedthrowsKEY_NOT_FOUND - 12.19
src/detached/detached.test.ts— Appendix F vectors round-trip; missingdetachedPayloadat verify throws - 12.20
src/verify/verify-pipeline.test.ts— pipeline ordering; allowlist enforced before any crypto runs;critenforced before alg dispatch - 12.21
src/sign/sign-pipeline.test.ts— sign pipeline round-trip per algorithm - 12.22
src/errors/error-codes.test.ts— every code in the registry thrown by ≥1 production path;expectTypeOfpins literal union - 12.23
src/observability/metrics.test.ts— histograms + counters recorded under in-memoryMeterProvider; outcome attribute bounded - 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. - 12.26
src/verify/abort.test.ts— pre-aborted signal throwsJWS.ABORTED; abort between phases throws - 12.27
src/index.test.ts— every documented subpath export resolves - 12.28 Every
it(...)carries{ timeout: 5000 }(or{ timeout: 30000 }for the property tests) - 12.29 Runner-neutral imports —
grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/security/jws/src/returns zero output
13. Documentation
- 13.1 Sibling
.mdfor 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 - 13.2 README.md links every co-located
.mdunder## Modules - 13.3 Update
CLAUDE.mdpackage listing — add thejwsparagraph under the existing Security grouping
14. Benchmarks
- 14.1
src/__benchmarks__/verify.bench.ts—HS256,RS256,ES256,EdDSAverify on 1 KiB payload - 14.2
src/__benchmarks__/sign.bench.ts— same four algorithms sign on 1 KiB payload - 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)
- 15.1
bun run check— Biome 0 errors / 0 warnings / 0 info across the monorepo - 15.2
bunx turbo lint typecheck test --filter=@suaveplan/jws— green - 15.3
bunx turbo test:coverage --filter=@suaveplan/jws— 100/100/100/100 - 15.4
bun scripts/openspec-conformance.ts @suaveplan/jws— exit 0, noerrorfindings - 15.5
bunx turbo build --filter=@suaveplan/jws— green
16. Pre-archive 7-row checklist (AGENTS.md §13)
- 16.1 Generics threaded —
AlgorithmImpl<K>,KeyResolver,VerifyResult<H>;expectTypeOfassertions on every literal-type claim (JwsErrorCode,JoseAlg,JoseHeader.critentries) - 16.2 Zod schemas wired at every declared boundary (
compactVerify,compactSign,flatVerify,flatSign,generalVerify,generalSign,parseHeader,jwkToCryptoKey) - 16.3 Every error code in
error-codes.tsthrown by ≥1 production path - 16.4 Strict-mode behaviors tested both on (
acceptNone: falsedefault) and off (acceptNone: truewithalgorithms: ["none"]) - 16.5 OTel instruments emitted from production paths and asserted with a real in-memory
MeterProvider - 16.6 Lifecycle dedup — N/A (verify / sign are stateless;
AlgorithmRegistrydouble-register throws and is tested) - 16.7 No retroactive tick — every box above reflects work done in this change
17. Banned-pattern + barrel-shape + runner-import scans
- 17.1
grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/security/jws/src/ --include="*.ts"— zero output - 17.2 Barrel-shape scan per AGENTS.md §13 step 4a — zero output for any
<feature>/index.ts - 17.3 Runner-import scan per AGENTS.md §13 step 4b — zero output
- 17.4 Missing-
.mdscan — zero output - 17.5 Bare-version scan — zero output
- 17.6 Subpath count matches feature module count (10 subpaths:
.,./serialization/compact,./serialization/flat,./serialization/general,./header,./algorithms,./keys,./detached,./base64url,./errors) - 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 --fixthen a secondbunx markdownlint-cli2pass with exit 0 — DEFERRED to follow-up - 18.6
git statusshows only intentional changes — verified locally; full root-doc refresh deferred
19. Archive
- 19.1 Fold spec delta into
openspec/specs/jws/spec.md(canonical, status ACTIVE) - 19.2 Move
openspec/changes/enterprise-jws/→openspec/archive/<yyyy-mm-dd>-enterprise-jws/ - 19.3 Confirm
MANIFEST.mdlists@suaveplan/jwsafter refresh