Add plan.
This commit is contained in:
parent
9803553b61
commit
6e2cd91af5
58
AGENTS.md
Normal file
58
AGENTS.md
Normal file
@ -0,0 +1,58 @@
|
||||
# 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.
|
||||
- **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 opening the PR):**
|
||||
- `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)?
|
||||
244
plan-jsrsasign.md
Normal file
244
plan-jsrsasign.md
Normal file
@ -0,0 +1,244 @@
|
||||
# Replace jsrsasign in CyberChef
|
||||
|
||||
> See [AGENTS.md](AGENTS.md) for cross-PR conventions, workflow, and library decisions agents must follow.
|
||||
|
||||
## Status
|
||||
|
||||
- [ ] PR 1 — Setup + ASN.1 utilities
|
||||
- [ ] PR 2 — SM2 rewrite
|
||||
- [ ] PR 3 — ECDSA primitives
|
||||
- [ ] PR 4 — PEM/JWK conversion + key extraction
|
||||
- [ ] PR 5 — X.509 / CSR / CRL parsing
|
||||
- [ ] PR 6 — Removal
|
||||
|
||||
_Notes for next session:_ (none yet)
|
||||
|
||||
## Context
|
||||
|
||||
[jsrsasign](https://github.com/kjur/jsrsasign) (currently pinned at `^11.1.3` in [package.json:148](package.json#L148)) has [announced end-of-support](https://github.com/kjur/jsrsasign/blob/master/README.md#end-of-support-announcement-for-jsrsasign). It is used in 14 operations and one library file in CyberChef and covers X.509/CSR/CRL parsing, ECDSA signing/verification, key format conversion, ASN.1/OID utilities, and SM2 elliptic-curve encryption.
|
||||
|
||||
Continuing to ship an unmaintained crypto library is a security risk. This plan replaces every jsrsasign call with proven, actively-maintained alternatives, then removes the dependency entirely.
|
||||
|
||||
## Library choices
|
||||
|
||||
Add these production dependencies, all MIT/BSD-3 (compatible with CyberChef's Apache-2.0 license), all pure-JS, all browser + Node:
|
||||
|
||||
| Library | Used for | Why this one |
|
||||
|---|---|---|
|
||||
| **[@peculiar/x509](https://github.com/PeculiarVentures/x509)** (^1.14) | X.509 certs, PKCS#10 CSRs, X.509 CRLs, SPKI/PKCS#8 key parsing | Peculiar Ventures specialise in browser PKI. Built on Web Crypto + asn1js. Used by Microsoft, Cloudflare. MIT. |
|
||||
| **[asn1js](https://github.com/PeculiarVentures/ASN1.js)** (^3) | Generic ASN.1 parse + tree dump for `ParseASN1HexString` | Same author. BSD-3. Pulled in transitively by @peculiar/x509 anyway. |
|
||||
| **[@noble/curves](https://github.com/paulmillr/noble-curves)** (^2) | ECDSA sign/verify, signature format conversion, key generation, SM2 curve | Audited by Cure53, Trail of Bits, Kudelski. Zero dependencies. Used by MetaMask, Coinbase, Ethereum Foundation. Built-in `@noble/curves/sm2`. Same author as the already-installed `@noble/hashes`. |
|
||||
| **node-forge** (already a dep) | DSA path inside `PubKeyFromPrivKey` only | Avoids adding another lib just for DSA. |
|
||||
|
||||
**Not adopted, with reasons:** `pkijs` (covered by @peculiar/x509); `jose`/panva (overkill for two JWK round-trip ops); `sm-crypto`/`sm-crypto-v2` (@noble/curves/sm2 handles it); native Web Crypto for ECDSA (refuses MD5/SHA-1 digests that the UI exposes, and JWK/format gymnastics outweigh the dependency saving).
|
||||
|
||||
**End state:** `npm uninstall jsrsasign`; remove from [package.json](package.json).
|
||||
|
||||
## Decisions confirmed with user
|
||||
|
||||
- Phased delivery: **6 PRs**, one per phase below.
|
||||
- Output fidelity: **cosmetic drift accepted** for golden text-output tests (`ParseX509Certificate`, `ParseCSR`, `ParseX509CRL`, `ParseASN1HexString`). Update fixtures, document in CHANGELOG.
|
||||
- DSA in `PubKeyFromPrivKey`: **keep via node-forge** (no user-visible regression).
|
||||
- ECDSA signature fixtures: **update to @noble values** if RFC 6979 outputs diverge (signatures remain valid).
|
||||
|
||||
## Files to be modified
|
||||
|
||||
**14 operations** (all under [src/core/operations/](src/core/operations/)):
|
||||
- ASN.1 / encoding: `HexToObjectIdentifier.mjs`, `ObjectIdentifierToHex.mjs`, `HexToPEM.mjs`, `ParseASN1HexString.mjs`
|
||||
- X.509 / CSR / CRL: `ParseX509Certificate.mjs`, `PubKeyFromCert.mjs`, `ParseCSR.mjs`, `ParseX509CRL.mjs`
|
||||
- Key conversion: `PEMToJWK.mjs`, `JWKToPem.mjs`, `PubKeyFromPrivKey.mjs`
|
||||
- ECDSA: `ECDSASign.mjs`, `ECDSAVerify.mjs`, `ECDSASignatureConversion.mjs`, `GenerateECDSAKeyPair.mjs`
|
||||
|
||||
**Library files:**
|
||||
- [src/core/lib/SM2.mjs](src/core/lib/SM2.mjs) — full rewrite, drop jsrsasign
|
||||
- [src/core/lib/PublicKey.mjs](src/core/lib/PublicKey.mjs) — extend `formatDnObj` to accept @peculiar/x509's `Name.toJSON()` shape
|
||||
- **NEW** [src/core/lib/Asn1.mjs](src/core/lib/Asn1.mjs) — `oidHexToInt`, `oidIntToHex`, `derToPem`, `dumpAsn1Hex`
|
||||
- **NEW** [src/core/lib/Ecdsa.mjs](src/core/lib/Ecdsa.mjs) — shared ECDSA helpers (key load, sign/verify, sig format conversions)
|
||||
|
||||
**Build/manifest:**
|
||||
- [package.json](package.json) — add 3 deps in PR 1, remove jsrsasign in PR 6
|
||||
- `CHANGELOG.md` — note in PR 6
|
||||
|
||||
## Phased plan
|
||||
|
||||
### PR 1 — Setup + ASN.1 utilities (PublicKey bundle)
|
||||
|
||||
**Goal:** Add the three new deps, ship the `Asn1.mjs` helper module, migrate the four utility operations.
|
||||
|
||||
1. `npm install --save @peculiar/x509 asn1js @noble/curves`. Confirm webpack builds; smoke-test `npm test`.
|
||||
2. Create [src/core/lib/Asn1.mjs](src/core/lib/Asn1.mjs):
|
||||
- `oidHexToInt(hex)` — inline BER OID decode (~25 lines, handles arc0/arc1 combined byte and base-128 multibyte arcs).
|
||||
- `oidIntToHex(oid)` — inverse.
|
||||
- `derToPem(hex, label)` — base64 + 64-col wrap with `\n` line endings.
|
||||
- `dumpAsn1Hex(hex, { truncate, startIndex })` — walks `asn1js.fromBER` output and emits an indented tree.
|
||||
3. Migrate:
|
||||
- [HexToObjectIdentifier.mjs](src/core/operations/HexToObjectIdentifier.mjs): `r.KJUR.asn1.ASN1Util.oidHexToInt` → `oidHexToInt`.
|
||||
- [ObjectIdentifierToHex.mjs](src/core/operations/ObjectIdentifierToHex.mjs): `r.KJUR.asn1.ASN1Util.oidIntToHex` → `oidIntToHex`.
|
||||
- [HexToPEM.mjs](src/core/operations/HexToPEM.mjs): `r.KJUR.asn1.ASN1Util.getPEMStringFromHex` → `derToPem`.
|
||||
- [ParseASN1HexString.mjs](src/core/operations/ParseASN1HexString.mjs): `r.ASN1HEX.dump` → `dumpAsn1Hex`.
|
||||
4. Tests:
|
||||
- Add round-trip OID tests (sample OIDs incl. `2.5.4.3`, `1.3.6.1.4.1.311.2.1.4`, edge case with arc > 127).
|
||||
- Update `ParseASN1HexString` golden output to new tree format (drift allowed).
|
||||
|
||||
**Risks:** OID encoding edge cases — verify the `40·a + b` first byte and base-128 arcs.
|
||||
|
||||
### PR 2 — SM2 (Ciphers bundle)
|
||||
|
||||
**Goal:** Rewrite [src/core/lib/SM2.mjs](src/core/lib/SM2.mjs) on top of `@noble/curves/sm2`.
|
||||
|
||||
API mapping:
|
||||
| jsrsasign | @noble/curves/sm2 |
|
||||
|---|---|
|
||||
| `r.crypto.ECParameterDB.regist("sm2p256v1", …)` + `getByName` | `import { sm2 } from "@noble/curves/sm2"` (curve built-in) |
|
||||
| `r.SecureRandom` + `new r.BigInteger(bits, rng).mod(n-1)+1` | `sm2.utils.randomPrivateKey()` |
|
||||
| `ecParams.G.multiply(k)` | `sm2.Point.BASE.multiply(k)` (k is `bigint`) |
|
||||
| `ecParams.curve.decodePointHex("04"+x+y)` | `sm2.Point.fromHex("04"+x+y)` |
|
||||
| `point.getX().toBigInteger()` / `getY()` | `point.toAffine().x` / `.y` (both `bigint`) |
|
||||
| `point.isInfinity()` | `point.is0()` (or `equals(sm2.Point.ZERO)`) |
|
||||
| `new r.BigInteger(hex, 16)` | `BigInt("0x" + hex)` |
|
||||
|
||||
**Gotchas:**
|
||||
- Pad point coords with `.padStart(64, "0")` after `bigint.toString(16)` — existing tests depend on fixed-width hex.
|
||||
- KDF logic (SM3-based) is already pure-JS; do **not** change it.
|
||||
- Preserve both ciphertext layouts (GMT 0009 BBB vs GMT 0010 C1C2C3/C1C3C2) — keep current format-string handling.
|
||||
|
||||
**Tests:** [tests/operations/tests/SM2.mjs](tests/operations/tests/SM2.mjs) has hard-coded ciphertext→plaintext fixtures. They MUST pass unchanged — they pin cryptographic correctness.
|
||||
|
||||
### PR 3 — ECDSA primitives (Ciphers bundle)
|
||||
|
||||
**Goal:** Migrate the four ECDSA operations to `@noble/curves` + `@noble/hashes`.
|
||||
|
||||
1. Create [src/core/lib/Ecdsa.mjs](src/core/lib/Ecdsa.mjs) with shared helpers:
|
||||
- `loadEcKey(pem)` — parse SEC1 / PKCS#8 / SPKI via `@peculiar/asn1-ecc` + `@peculiar/asn1-pkcs8`; return `{ curve, isPrivate, d?, x, y, jwk }`.
|
||||
- `signEcdsa(curve, dBytes, digest)`, `verifyEcdsa(curve, qBytes, digest, sigDer)`.
|
||||
- `asn1SigToConcatHex`, `concatHexToAsn1Sig`, `parseAsn1SigToHexRS`, `hexRSToAsn1Sig` — wrap `Signature.fromBytes(.., 'der'|'compact')` and `.toBytes(format)`.
|
||||
- `isAsn1Hex(hex)` — try `asn1js.fromBER`.
|
||||
- `generateEcKeyPair(curveName)` — `curve.utils.randomPrivateKey()`, build SPKI + PKCS#8 via `@peculiar/asn1-*` schemas, derive JWK.
|
||||
|
||||
2. Migrate operations:
|
||||
- [ECDSASign.mjs](src/core/operations/ECDSASign.mjs): use `loadEcKey` + hash (md5/sha1/sha256/sha384/sha512 from `@noble/hashes`; **md5/sha1 come from `@noble/hashes/legacy`**) + `signEcdsa`. Format conversion via the helpers.
|
||||
- [ECDSAVerify.mjs](src/core/operations/ECDSAVerify.mjs): `isAsn1Hex` for input-format auto-detect; `verifyEcdsa`.
|
||||
- [ECDSASignatureConversion.mjs](src/core/operations/ECDSASignatureConversion.mjs): format conversions only — no key handling.
|
||||
- [GenerateECDSAKeyPair.mjs](src/core/operations/GenerateECDSAKeyPair.mjs): `generateEcKeyPair`; drop the `\r` stripping (was a jsrsasign artefact).
|
||||
|
||||
**Gotchas:**
|
||||
- **MD5/SHA-1 digests** — `@noble/hashes/legacy` (the v2 split). Web Crypto cannot do this, which is why we chose `@noble/curves`.
|
||||
- **P-521 byte length is 66**, not 65 — important for P1363 padding and JWK `x`/`y` encoding.
|
||||
- **`parseSigHexInHexRS`** historically returns r/s with a leading `00` when the MSB is set (DER 2's-complement artefact). Replicate this — tests depend on it.
|
||||
- **RFC 6979 determinism** — outputs should match jsrsasign byte-for-byte. If they don't, update fixtures (signatures still valid; document in CHANGELOG).
|
||||
- **JWK field ordering** for the JWK output of `GenerateECDSAKeyPair`: build the literal as `{kty, crv, x, y, d?}` — `JSON.stringify` preserves insertion order.
|
||||
|
||||
**Tests:** [tests/operations/tests/ECDSA.mjs](tests/operations/tests/ECDSA.mjs) has 83 tests covering all curves × digests × formats. Run; update fixtures where needed.
|
||||
|
||||
### PR 4 — PEM/JWK conversion + key extraction (PublicKey bundle)
|
||||
|
||||
**Goal:** Migrate [PEMToJWK.mjs](src/core/operations/PEMToJWK.mjs), [JWKToPem.mjs](src/core/operations/JWKToPem.mjs), [PubKeyFromPrivKey.mjs](src/core/operations/PubKeyFromPrivKey.mjs).
|
||||
|
||||
API mapping:
|
||||
| jsrsasign | Replacement |
|
||||
|---|---|
|
||||
| `r.KEYUTIL.getKey(pem)` RSA | `node-forge` `pki.privateKeyFromPem` / `publicKeyFromPem` |
|
||||
| `r.KEYUTIL.getKey(pem)` EC | `loadEcKey` from `Ecdsa.mjs` |
|
||||
| `r.KEYUTIL.getKey(pem)` DSA | `node-forge` DSA parsing |
|
||||
| `r.KEYUTIL.getKey(jwk)` | Inline: detect `kty`, dispatch to RSA / EC builders |
|
||||
| `r.KEYUTIL.getJWKFromKey(key)` | Inline literal `{ kty, …, [d] }` |
|
||||
| `r.KEYUTIL.getPEM(key)` SPKI | `@peculiar/x509` `PublicKey(spki).toString("pem")` |
|
||||
| `r.KEYUTIL.getPEM(key, "PKCS8PRV")` | Build PKCS#8 via `@peculiar/asn1-pkcs8`'s `PrivateKeyInfo` |
|
||||
| `new r.RSAKey().setPublic(n, e)` + getPEM | `node-forge` `pki.setRsaPublicKey(n, e)` + `publicKeyToPem` |
|
||||
| `new r.KJUR.crypto.ECDSA({curve}).setPublicKeyHex/generatePublicKeyHex` | Derive Q with `curve.getPublicKey(d, false)`; build SPKI |
|
||||
| `new r.KJUR.crypto.DSA().setPublic(p, q, g, y)` | `node-forge` DSA public key + `publicKeyToPem` |
|
||||
|
||||
**Gotchas:**
|
||||
- JWK field order and PEM line endings (`\n` not `\r\n`) — see PR 3.
|
||||
- `JWKToPem`'s `PKCS8PRV` output: build via `@peculiar/asn1-pkcs8` + the algorithm-specific key schema (RSA or EC).
|
||||
- DSA PKCS#8 with no `y`: keep the current "unsupported" throw — test already accepts it.
|
||||
|
||||
**Tests:** [tests/operations/tests/JWK.mjs](tests/operations/tests/JWK.mjs), [tests/operations/tests/PubKeyFromPrivKey.mjs](tests/operations/tests/PubKeyFromPrivKey.mjs) — should pass with minor whitespace adjustments.
|
||||
|
||||
### PR 5 — X.509 / CSR / CRL parsing (PublicKey bundle)
|
||||
|
||||
**Goal:** Migrate the four cert-family operations to `@peculiar/x509`.
|
||||
|
||||
1. **Before any code changes:** add a regression test for [ParseX509Certificate.mjs](src/core/operations/ParseX509Certificate.mjs) (currently uncovered — confirm with `ls tests/operations/tests/ | grep -i x509certificate`). Use the existing test certs from [PubKeyFromCert.mjs](tests/operations/tests/PubKeyFromCert.mjs).
|
||||
2. Extend [src/core/lib/PublicKey.mjs](src/core/lib/PublicKey.mjs): adapt `formatDnObj` to accept either the legacy `{array: [[{type, value}]]}` shape OR @peculiar/x509's `JsonName` array shape.
|
||||
3. Migrate:
|
||||
|
||||
**`ParseX509Certificate`:**
|
||||
| jsrsasign | @peculiar/x509 |
|
||||
|---|---|
|
||||
| `new r.X509(); readCertHex/readCertPEM` | `new X509Certificate(input)` (accepts PEM string or BufferSource) |
|
||||
| `cert.hex` | `bytesToHex(cert.rawData)` |
|
||||
| `cert.getSerialNumberHex()` | `cert.serialNumber` |
|
||||
| `cert.getIssuer()` / `getSubject()` | `cert.issuerName.toJSON()` / `cert.subjectName.toJSON()` → `formatDnObj` |
|
||||
| `cert.getPublicKey()` | `cert.publicKey`; parse `rawData` with `@peculiar/asn1-rsa` `RSAPublicKey` or `@peculiar/asn1-ecc` `ECPoint` to extract `n`/`e` or `(x, y)` |
|
||||
| `cert.getSignatureValueHex()` | `bytesToHex(cert.signature)` |
|
||||
| `cert.version` | `cert.version` |
|
||||
| `cert.getSignatureAlgorithm*` | `cert.signatureAlgorithm.name` |
|
||||
| `cert.getNotBefore()` / `getNotAfter()` | `cert.notBefore` / `cert.notAfter` (`Date`) — reformat to the existing yymmddHHMMSSZ string |
|
||||
| `cert.getInfo()` extensions text | Iterate `cert.extensions`, format known types (BasicConstraints, KeyUsage, EKU, SAN, AKI, SKI, CRLDistributionPoints, AIA) — reuse formatters in current [ParseCSR.mjs](src/core/operations/ParseCSR.mjs) |
|
||||
| `r.BigInteger` keylen | native `BigInt("0x"+hex)` |
|
||||
|
||||
**`PubKeyFromCert`:** `new X509Certificate(pem).publicKey.toString("pem")`.
|
||||
|
||||
**`ParseCSR`:**
|
||||
| jsrsasign | @peculiar/x509 |
|
||||
|---|---|
|
||||
| `r.KJUR.asn1.csr.CSRUtil.getParam(input)` | `new Pkcs10CertificateRequest(input)` |
|
||||
| `csrParam.sbjpubkey` | `csr.publicKey.toString("pem")` |
|
||||
| `csrParam.sigalg` | `csr.signatureAlgorithm.name` |
|
||||
| `csrParam.sighex` | `bytesToHex(csr.signature)` |
|
||||
| `csrParam.extreq` | walk `csr.attributes`, find OID `1.2.840.113549.1.9.14`, decode with `@peculiar/asn1-pkcs9` |
|
||||
|
||||
**`ParseX509CRL`:**
|
||||
| jsrsasign | @peculiar/x509 |
|
||||
|---|---|
|
||||
| `new r.X509CRL(input)` | `new X509Crl(input)` (convert hex input → `Uint8Array` first) |
|
||||
| `crl.getVersion / SignatureAlgorithm* / Issuer / ThisUpdate / NextUpdate` | `crl.version / signatureAlgorithm.name / issuer / thisUpdate / nextUpdate` |
|
||||
| `crl.getParam().ext` | `crl.extensions` |
|
||||
| `crl.getRevCertArray()` | `crl.entries` (each has `serialNumber`, `revocationDate`, `extensions`) |
|
||||
| `crl.getSignatureValueHex()` | `bytesToHex(crl.signature)` |
|
||||
|
||||
**Gotchas:**
|
||||
- `cert.getInfo()` is the trickiest dependency — the current op extracts extensions text by string-splitting on `"X509v3 Extensions:\n"` and `"signature"`. Replace with a real extension walker (reuse `formatGeneralNames`, KeyUsage bit mapping, etc. from existing `ParseCSR.mjs`).
|
||||
- CRL hex input: convert hex → `Uint8Array` before passing to `X509Crl`.
|
||||
- DSA-in-cert: rare; use `@peculiar/asn1-x509` DSA params or fall back to a placeholder text block.
|
||||
- Date formatting: produce both raw `yymmddHHMMSSZ` and a human-readable form, as currently displayed.
|
||||
|
||||
**Tests:** Update goldens for [ParseCSR.mjs](tests/operations/tests/ParseCSR.mjs), [ParseX509CRL.mjs](tests/operations/tests/ParseX509CRL.mjs), [PubKeyFromCert.mjs](tests/operations/tests/PubKeyFromCert.mjs) as needed. Add fixtures for the new `ParseX509Certificate` test from step 1.
|
||||
|
||||
### PR 6 — Removal
|
||||
|
||||
1. `grep -rn jsrsasign src/` — must return zero matches.
|
||||
2. `npm uninstall jsrsasign`; remove from [package.json](package.json) dependencies.
|
||||
3. `npm run lint && npm test` — full suite green.
|
||||
4. `npm run build` — confirm bundle builds; check PublicKey and Ciphers chunk size deltas.
|
||||
5. Update `CHANGELOG.md`: removed jsrsasign; added @peculiar/x509, asn1js, @noble/curves; note any cosmetic output-format changes in the affected ops.
|
||||
|
||||
## Verification
|
||||
|
||||
After **each PR:**
|
||||
- `npm run lint`
|
||||
- `npm test` (Node + browser test suites)
|
||||
- `npm run build` succeeds
|
||||
- `grep -rn "from \"jsrsasign\"" src/core/` shrinks monotonically toward zero
|
||||
|
||||
After **PR 6:**
|
||||
- `grep -rn jsrsasign .` returns zero results outside `package-lock.json` and `CHANGELOG.md`
|
||||
- Manual UI smoke test: launch `npm start`, try each migrated operation in the browser with representative input
|
||||
- Check bundle size: `npm run build` and compare `web/assets/*.js` sizes against the pre-migration baseline
|
||||
|
||||
## Open follow-ups (not in scope)
|
||||
|
||||
- Audit other crypto deps (`crypto-api`, `crypto-js`, `node-forge`) for similar end-of-life risk.
|
||||
- Consider whether `crypto-api` (used for SM3 in [SM2.mjs](src/core/lib/SM2.mjs)) could also be retired in favour of `@noble/hashes/sm3` in a future cleanup.
|
||||
|
||||
## Changelog
|
||||
|
||||
Record deviations from the original plan here, newest at the top. One bullet per change: what changed, why, and which PR.
|
||||
|
||||
<!-- e.g.
|
||||
### PR 2 — 2026-05-20
|
||||
- Added `@noble/hashes` peer-dep bump (was on ^1.x, needed ^2 for the `legacy` subpath). No code impact.
|
||||
- SM2 ciphertext fixture #3 was already wrong (decoded to the wrong plaintext under jsrsasign too) — confirmed via independent SM2 implementation, regenerated fixture.
|
||||
-->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user