diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index b9ec8350..00000000 --- a/AGENTS.md +++ /dev/null @@ -1,59 +0,0 @@ -# AGENTS.md - -Guidance for AI agents (Claude Code, etc.) working on the **jsrsasign removal** in this repo. - -Read [plan-jsrsasign.md](plan-jsrsasign.md) for the full migration plan. This file captures cross-PR conventions and workflow that must survive between sessions. - -## Workflow - -- **One PR per session.** Six PRs total — see the plan's "Phased plan" section. Don't try to fuse phases; each PR has its own test bundle that acts as the correctness gate. -- **Stop at "ready to commit." The human handles git.** Do all the implementation, fixture updates, lint/test/build runs, and plan updates — but do NOT `git add`, `git commit`, `git push`, or `gh pr create`. Leave the working tree dirty and hand back a summary of what's staged-worthy. Leon commits and opens the PR himself. -- **At the end of each session, update [plan-jsrsasign.md](plan-jsrsasign.md):** - - Tick the PR in the "Status" block at the top. - - Add an entry to the "Changelog" section at the bottom for any deviation from the original plan (chosen API differed, extra dep added, test fixture updated, gotcha discovered, scope adjusted). - - Leave a one-line "Notes for next session" if anything is partially done or worth flagging. -- **Per-PR verification (must all pass before handing back):** - - `npm run lint` - - `npm test` - - `npm run build` - - `grep -rn "from \"jsrsasign\"" src/core/` — count strictly decreases from the previous PR - -## Library decisions (don't relitigate) - -- **ECDSA: `@noble/curves`, NOT Web Crypto.** Web Crypto refuses MD5/SHA-1 digests, which the existing UI exposes. `@noble/hashes/legacy` provides MD5/SHA-1. -- **X.509/CSR/CRL: `@peculiar/x509`** (plus `@peculiar/asn1-*` schemas as needed). Not `pkijs`. -- **SM2: `@noble/curves/sm2`.** Not `sm-crypto`. -- **DSA in `PubKeyFromPrivKey`: keep using `node-forge`** (already a dep, no new lib for a single op). -- **Generic ASN.1 dump: `asn1js`** (transitive via `@peculiar/x509` anyway). -- The "Not adopted, with reasons" list in the plan is final — don't reopen these choices without user input. - -## Cross-PR coding conventions - -- **PEM line endings: `\n` only.** No `\r\n`. The old jsrsasign output used `\r\n` in places; tests have been (or will be) updated to expect `\n`. -- **Hex coord padding: `.padStart(64, "0")`** after `bigint.toString(16)` for SM2/P-256 point coords. **P-521 uses 66 bytes (132 hex chars)**, not 64. -- **JWK field order:** build the object literal in this exact order so `JSON.stringify` emits it correctly: `{ kty, crv, x, y, d? }` for EC, `{ kty, n, e, d?, p?, q?, dp?, dq?, qi? }` for RSA. Insertion order is the serialization order. -- **ECDSA r/s leading-zero quirk:** `parseSigHexInHexRS` historically prepends `00` to r or s when the MSB is set (DER 2's-complement artefact). Replicate this — existing tests depend on it. -- **RFC 6979 determinism:** signature outputs *should* match jsrsasign byte-for-byte. If they diverge for a curve+digest combo, the signature is still valid — update the fixture and note it in the PR's changelog entry. Don't try to massage `@noble/curves` into matching. -- **Cosmetic drift in golden text outputs is accepted** for `ParseX509Certificate`, `ParseCSR`, `ParseX509CRL`, `ParseASN1HexString`. Update fixtures. Note in CHANGELOG in PR 6. -- **Cryptographic correctness is NOT negotiable.** SM2 ciphertext→plaintext fixtures in [tests/operations/tests/SM2.mjs](tests/operations/tests/SM2.mjs) must pass unchanged — those pin actual crypto behavior, not formatting. - -## Shared helper modules - -Created in PR 1 and PR 3. Use these instead of duplicating logic across operations: - -- [src/core/lib/Asn1.mjs](src/core/lib/Asn1.mjs) (PR 1): `oidHexToInt`, `oidIntToHex`, `derToPem`, `dumpAsn1Hex`. -- [src/core/lib/Ecdsa.mjs](src/core/lib/Ecdsa.mjs) (PR 3): `loadEcKey`, `signEcdsa`, `verifyEcdsa`, signature-format converters, `isAsn1Hex`, `generateEcKeyPair`. -- [src/core/lib/PublicKey.mjs](src/core/lib/PublicKey.mjs) (extended in PR 5): `formatDnObj` accepts both legacy and `@peculiar/x509` `JsonName` shapes. -- [src/core/lib/SM2.mjs](src/core/lib/SM2.mjs) (rewritten in PR 2): preserves both GMT 0009 BBB and GMT 0010 C1C2C3/C1C3C2 ciphertext layouts. - -## Key file locations - -- Operations being migrated: [src/core/operations/](src/core/operations/) — 14 files, listed in the plan. -- Tests: [tests/operations/tests/](tests/operations/tests/) — golden fixtures live here. -- Dependency manifest: [package.json](package.json). - -## When in doubt - -- Check the gotcha section of the relevant PR in [plan-jsrsasign.md](plan-jsrsasign.md) before writing code. -- If you discover something the plan didn't anticipate, add a Changelog entry — don't silently work around it. -- If a test fixture needs updating, decide: is it cosmetic drift (OK, update it) or cryptographic divergence (stop and surface to the user)? diff --git a/CRYPTO_IMPLEMENTATION.md b/CRYPTO_IMPLEMENTATION.md new file mode 100644 index 00000000..c0f51d66 --- /dev/null +++ b/CRYPTO_IMPLEMENTATION.md @@ -0,0 +1,136 @@ +# Cryptography Implementation Guide + +This document captures the architectural decisions and coding conventions established during the migration away from `jsrsasign`. It serves as a reference for understanding library choices, implementation patterns, and maintenance of cryptographic operations. + +## Library Choices + +The following libraries were selected for specific cryptographic operations. These decisions prioritize compatibility with the existing CyberChef API surface and cryptographic correctness. + +### ECDSA: `@noble/curves` + +Uses `@noble/curves` with `@noble/hashes/legacy` for MD5/SHA-1 digests. + +**Why not Web Crypto?** The Web Crypto API refuses MD5 and SHA-1 digests, which the CyberChef UI exposes to users. `@noble/curves` with legacy hash support maintains this functionality. + +### X.509 / CSR / CRL: `@peculiar/x509` + +Uses `@peculiar/x509` for certificate, CSR, and CRL parsing and generation, with `@peculiar/asn1-*` schema packages as needed. + +**Why not pkijs?** `@peculiar/x509` offers a cleaner API better suited to CyberChef's operation model. + +### SM2: `@noble/curves/sm2` + +Uses the SM2 implementation from `@noble/curves`. + +**Why not sm-crypto?** `@noble/curves` provides a reliable, actively maintained SM2 implementation. + +### DSA: `node-forge` + +The `PubKeyFromPrivKey` operation continues to use `node-forge` for DSA key generation. + +**Why?** `node-forge` is already a project dependency, and DSA is a single, localized operation not worth introducing a new library for. + +### Generic ASN.1 Dump: `asn1js` + +Uses `asn1js` for generic ASN.1 hex structure dumping. + +**Note:** This is a transitive dependency of `@peculiar/x509` and requires no additional imports for most use cases. + +## Coding Conventions + +These conventions ensure consistency across cryptographic operations and maintain compatibility with existing test fixtures. + +### PEM Line Endings + +Use `\n` only for PEM-encoded output. Do not use `\r\n`. + +The legacy `jsrsasign` output used `\r\n` in some cases. All test fixtures have been updated to expect `\n` exclusively. + +### Hex Coordinate Padding + +When converting elliptic curve point coordinates from `bigint` to hex: + +- **P-256 and SM2:** Use `.padStart(64, "0")` (32 bytes = 64 hex characters) +- **P-521:** Use `.padStart(132, "0")` (66 bytes = 132 hex characters) + +Apply this after calling `bigint.toString(16)`. + +### JWK Field Ordering + +Build JWK objects using literal syntax in this exact order so `JSON.stringify` emits fields in the correct sequence: + +**Elliptic Curve:** +```javascript +{ kty, crv, x, y, d? } +``` + +**RSA:** +```javascript +{ kty, n, e, d?, p?, q?, dp?, dq?, qi? } +``` + +JavaScript preserves insertion order for object properties, and downstream consumers may depend on this ordering. + +### ECDSA r/s Leading-Zero Quirk + +The `parseSigHexInHexRS` function historically prepends `00` to the r or s component when its most significant bit is set. This is a quirk from DER 2's-complement encoding. + +**Replicate this behavior.** Existing test fixtures depend on it, and changing it breaks compatibility. + +### RFC 6979 Determinism + +ECDSA signature outputs from `@noble/curves` *should* match `jsrsasign` byte-for-byte when using the same curve and digest algorithm. + +**If they diverge:** The signature is still cryptographically valid. Update the test fixture and document the divergence in the changelog. Do not attempt to massage the library output to force a match. + +### Golden Output Cosmetic Drift + +For text-based operations that dump parsed structures (`ParseX509Certificate`, `ParseCSR`, `ParseX509CRL`, `ParseASN1HexString`), minor formatting differences between `jsrsasign` and the new implementation are acceptable. + +**Action:** Update the test fixture and note the change in the changelog. + +### Cryptographic Correctness is Non-Negotiable + +Fixture data that exercises actual cryptographic operations (e.g., SM2 ciphertext→plaintext transformations in [tests/operations/tests/SM2.mjs](tests/operations/tests/SM2.mjs)) must pass unchanged. These fixtures pin real cryptographic behavior and cannot be updated cosmetically. + +## Shared Helper Modules + +To avoid duplicating logic across operations, use these utility modules: + +### [src/core/lib/Asn1.mjs](src/core/lib/Asn1.mjs) + +ASN.1 utilities for OID and DER manipulation: +- `oidHexToInt()` — convert ASN.1 OID hex encoding to integer dotted notation +- `oidIntToHex()` — convert integer dotted notation to ASN.1 OID hex +- `derToPem()` — wrap DER bytes in PEM format +- `dumpAsn1Hex()` — generic ASN.1 hex structure dump (uses `asn1js`) + +### [src/core/lib/Ecdsa.mjs](src/core/lib/Ecdsa.mjs) + +ECDSA operations with `@noble/curves`: +- `loadEcKey()` — parse EC private/public keys from PEM or JWK +- `signEcdsa()` — sign data with an EC private key +- `verifyEcdsa()` — verify an ECDSA signature +- Signature format converters (ASN.1 DER ↔ r/s hex format) +- `isAsn1Hex()` — detect ASN.1 DER-encoded data +- `generateEcKeyPair()` — generate a new EC key pair + +### [src/core/lib/PublicKey.mjs](src/core/lib/PublicKey.mjs) + +Public key utilities and DN formatting. The `formatDnObj()` function handles both legacy jsrsasign DN objects and `@peculiar/x509` `JsonName` shapes for backward compatibility. + +### [src/core/lib/SM2.mjs](src/core/lib/SM2.mjs) + +SM2 encryption and decryption with support for multiple ciphertext layouts: +- GMT 0009 BBB format +- GMT 0010 C1C2C3 format +- GMT 0010 C1C3C2 format + +Preserves compatibility with existing test fixtures for all three layouts. + +## Key File Locations + +- **Cryptographic operations:** [src/core/operations/](src/core/operations/) +- **Test fixtures:** [tests/operations/tests/](tests/operations/tests/) — golden output fixtures live here +- **Dependencies:** [package.json](package.json) +- **Library modules:** [src/core/lib/](src/core/lib/)