fix(deps): replace node-rsa to node crypto#1380
Conversation
There was a problem hiding this comment.
Pull request overview
This PR replaces the unmaintained node-rsa package with Node.js's built-in crypto module for all RSA key operations (generation, import/export, signing, and verification), reducing external dependencies and unblocking a future ESM migration.
Changes:
- Crypto operations in
private-key.ts,public-key.ts, andsign.tsmigrated fromnode-rsaRSAclass tocrypto.KeyObject/crypto.sign/crypto.verify node-rsaand@types/node-rsaremoved frompackage.jsonpnpm-lock.yamlupdated to removenode-rsa,@types/node-rsa, and the transitiveasn1dependency
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/plugin/core/crypto/sign.ts |
Replaced node-rsa signing with crypto.sign() using sha1 and RSA_PKCS1_PADDING |
src/plugin/core/crypto/public-key.ts |
Replaced node-rsa RSA class with crypto.KeyObject; import via createPublicKey, verify via crypto.verify() |
src/plugin/core/crypto/private-key.ts |
Replaced node-rsa RSA class with crypto.KeyObject; key gen via generateKeyPairSync, import via createPrivateKey, export via KeyObject.export() |
package.json |
Removed node-rsa and @types/node-rsa dependencies |
pnpm-lock.yaml |
Lockfile updated to reflect removed packages including transitive asn1 |
Two issues were found:
1. Dead guard in PublicKey.importKey() (moderate/bug)
crypto.createPublicKey() always returns a KeyObject with type === "public" — it never returns a key with any other type when given a valid DER/SPKI input (it throws on malformed or private-only input instead). The check if (publicKey.type !== "public") on line 44 of public-key.ts is dead code that can never be true, making it a misleading no-op rather than a real guard.
2. Unnecessary key serialization round-trip in PrivateKey.sign() (moderate/performance)
PrivateKey.sign() calls this.exportPrivateKey(), which serializes the internally stored crypto.KeyObject to a PEM string, and then passes that string to sign(), which passes it back to crypto.sign() where it is reparsed. Since crypto.sign() accepts a crypto.KeyObject directly, the sign() helper function's second parameter should be updated to accept crypto.KeyObject (or the broader crypto.KeyLike) and PrivateKey.sign() should pass this.key directly to avoid the unnecessary serialization overhead.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import RSA from "node-rsa"; | ||
| import crypto from "crypto"; | ||
|
|
||
| export const sign = (contents: Buffer, privateKey: string): Buffer => { |
There was a problem hiding this comment.
The sign function in sign.ts accepts a string (PEM) for the private key, and PrivateKey.sign() calls this.exportPrivateKey() to convert the stored KeyObject to a PEM string before passing it to sign(). This introduces an unnecessary round-trip: the KeyObject is serialized to PEM only to be re-parsed internally by crypto.sign().
Since both PrivateKey and sign.ts now use the crypto module, the sign function's second parameter should be updated to accept crypto.KeyObject (or the broader crypto.KeyLike) directly, and PrivateKey.sign() should pass this.key instead of this.exportPrivateKey().
| export const sign = (contents: Buffer, privateKey: string): Buffer => { | |
| export const sign = (contents: Buffer, privateKey: crypto.KeyLike): Buffer => { |
| if (publicKey.type !== "public") { | ||
| throw new Error("key must contain public key"); | ||
| } |
There was a problem hiding this comment.
crypto.createPublicKey() always returns a KeyObject with type === "public" — it will never return a key of any other type (it throws if the input is a private key instead of being silently allowed). Therefore, the check if (publicKey.type !== "public") on line 44 is dead code that can never be true, making it misleading rather than protective.
The old code used rsa.isPrivate() to detect if a private key was accidentally passed. With the crypto module, crypto.createPublicKey() will throw a TypeError if a private-key-only PEM is passed where a public key is expected, so no explicit check is needed. The dead check should be removed to avoid confusion.
| if (publicKey.type !== "public") { | |
| throw new Error("key must contain public key"); | |
| } |
de4f35d to
22b324f
Compare
22b324f to
ddd6874
Compare
Why
node-rsais no longer maintained and could be a blocker for ESM migration, so replace it with the Node.js built-incryptomodule.What
Replace the
node-rsapackage with Node.js built-incryptomodule.Removed dependencies
node-rsa(dependencies)@types/node-rsa(devDependencies)asn1(transitive dependency ofnode-rsa)Changed files
src/plugin/core/crypto/private-key.tscrypto.KeyObjectinstead ofRSAclass. Key generation viacrypto.generateKeyPairSync(), import viacrypto.createPrivateKey(), export viaKeyObject.export()src/plugin/core/crypto/public-key.tscrypto.createPublicKey(). Signature verification viacrypto.verify()src/plugin/core/crypto/sign.tscrypto.sign()package.jsonnode-rsaand@types/node-rsapnpm-lock.yamlAPI mapping
node-rsa)crypto)new RSA({ b: 1024 })crypto.generateKeyPairSync("rsa", { modulusLength: 1024 })new RSA(key)crypto.createPrivateKey({ key, format: "pem" })new RSA(key, "pkcs8-public-der")crypto.createPublicKey({ key, format: "der", type: "spki" })key.exportKey("pkcs1-private")key.export({ type: "pkcs1", format: "pem" })key.exportKey("pkcs8-public-der")publicKey.export({ type: "spki", format: "der" })key.sign(contents)crypto.sign("sha1", contents, { key, padding: RSA_PKCS1_PADDING })crypto.createVerify("RSA-SHA1")crypto.verify("sha1", data, { key, padding: RSA_PKCS1_PADDING }, signature)How to test
Affected commands
plugin keygen— Private key generationplugin pack— Plugin packagingplugin info— Plugin info displayplugin upload— Plugin upload (Plugin ID verification)Checklist
pnpm lintandpnpm teston the root directory.