Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4c3aefa
feat(crypto): add RSA-PSS codemod for DEP0154
nievasdev Aug 5, 2025
90bb8ba
chore: update author from Claude Code to Mauro Nievas
nievasdev Aug 5, 2025
2998555
chore: update package-lock.json to register crypto-rsa-pss-update wor…
nievasdev Aug 5, 2025
3ba20fe
Merge remote-tracking branch 'upstream/main' into feat/crypto-rsa-pss…
nievasdev Aug 5, 2025
1492821
feat(crypto): enhance RSA-PSS codemod to handle all import patterns
nievasdev Aug 8, 2025
c89dc4b
Merge remote-tracking branch 'upstream/main' into feat/crypto-rsa-pss…
nievasdev Aug 8, 2025
df65682
refactor(crypto): simplify namespace import handling using resolveBin…
nievasdev Aug 13, 2025
ab88911
fix(crypto): handle variable values in property transformations
nievasdev Aug 13, 2025
4ee7879
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Aug 13, 2025
50086cd
refactor(crypto): simplify getCryptoBindings using resolveBindingPath…
nievasdev Aug 13, 2025
6c0a199
feat(crypto-rsa-pss): add template literal support and optimize test …
nievasdev Aug 14, 2025
c6b7d75
refactor(crypto-rsa-pss):
nievasdev Aug 14, 2025
2993107
test(crypto-rsa-pss): consolidate import pattern tests
nievasdev Aug 14, 2025
3fa7277
test(crypto-rsa-pss): remove redundant destructured-renamed test case
nievasdev Aug 14, 2025
2e93c36
test(crypto-rsa-pss): add comprehensive edge case test coverage
nievasdev Aug 14, 2025
f101d13
docs(crypto-rsa-pss): update documentation and metadata
nievasdev Aug 14, 2025
098c35b
feat(crypto-rsa-pss): support variable type detection for 'rsa-pss'
nievasdev Aug 14, 2025
29a500d
feat(crypto-rsa-pss): add promisified wrapper support
nievasdev Aug 14, 2025
a7c6048
feat: improve promisify detection using resolveBindingPath
nievasdev Aug 14, 2025
76bd8f7
fix: resolve yamllint errors in crypto-rsa-pss YAML files
nievasdev Aug 14, 2025
05dcd05
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Aug 16, 2025
77da176
feat(crypto-rsa-pss): add this.property support and refactor codebase
nievasdev Aug 17, 2025
ae2b431
refactor(crypto-rsa-pss): consolidate modular architecture into singl…
nievasdev Aug 17, 2025
6831416
deps: bump @biomejs/biome from 2.1.2 to 2.1.3 (#163)
dependabot[bot] Aug 5, 2025
73143b4
deps: bump @types/node from 24.1.0 to 24.2.0 (#160)
dependabot[bot] Aug 5, 2025
3551f66
deps: bump @types/node from 24.2.0 to 24.2.1 (#170)
dependabot[bot] Aug 12, 2025
a39aa02
chore: remove `@next` and clean (#176)
AugustinMauroy Aug 12, 2025
129294d
refactor(crypto-rsa-pss): remove type wrappers, consolidate tests, ad…
nievasdev Aug 19, 2025
ae8161b
fix: regenerate package-lock.json to resolve npm ci compatibility
nievasdev Aug 20, 2025
ba5b05e
fix(crypto-rsa-pss): resolve TypeScript type compatibility issues
nievasdev Aug 21, 2025
9e6cdf5
fix: remove unnecessary type assertions and update test snapshot
nievasdev Aug 21, 2025
f101067
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Aug 23, 2025
1e9e147
fix: resolve TypeScript type compatibility issues
nievasdev Aug 24, 2025
366a99c
fix(deps): restore Darwin binaries in package-lock for macOS CI compa…
nievasdev Aug 24, 2025
493b355
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Sep 4, 2025
61904af
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Sep 7, 2025
259d497
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Sep 16, 2025
6ce24b4
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Sep 17, 2025
f1fe1ac
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Sep 23, 2025
b48cb42
Merge branch 'main' into feat/crypto-rsa-pss-update
nievasdev Sep 27, 2025
92bd02c
fix(crypto-rsa-pss-update): correct TypeScript types for utility func…
nievasdev Sep 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 75 additions & 0 deletions recipes/crypto-rsa-pss-update/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# crypto-rsa-pss-update

Codemod to handle Node.js crypto deprecation DEP0154 by transforming deprecated RSA-PSS key generation options.

## What it does

This codemod transforms deprecated RSA-PSS crypto options in `crypto.generateKeyPair()` and `crypto.generateKeyPairSync()` calls:

- `hash` → `hashAlgorithm`
- `mgf1Hash` → `mgf1HashAlgorithm`

The transformation only applies to calls with `'rsa-pss'` as the key type.

## Examples

**Before**

```js
const crypto = require("node:crypto");

crypto.generateKeyPair(
"rsa-pss",
{
modulusLength: 2048,
hash: "sha256",
mgf1Hash: "sha1",
saltLength: 32,
},
(err, publicKey, privateKey) => {
// callback
},
);

crypto.generateKeyPairSync("node:rsa-pss", {
modulusLength: 2048,
hash: "sha256",
});
```

**After**

```js
const crypto = require("crypto");

crypto.generateKeyPair(
"rsa-pss",
{
modulusLength: 2048,
hashAlgorithm: "sha256",
mgf1HashAlgorithm: "sha1",
saltLength: 32,
},
(err, publicKey, privateKey) => {
// callback
},
);

crypto.generateKeyPairSync("rsa-pss", {
modulusLength: 2048,
hashAlgorithm: "sha256",
});
```

## Usage

```bash
npx codemod@next @nodejs/crypto-rsa-pss-update
```

## Supports

- Both `crypto.generateKeyPair()` and `crypto.generateKeyPairSync()`
- Destructured imports: `const { generateKeyPair } = require('crypto')`
- Only transforms `'rsa-pss'` key type calls
- Preserves all other options and call structure
23 changes: 23 additions & 0 deletions recipes/crypto-rsa-pss-update/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
schema_version: "1.0"
name: "@nodejs/crypto-rsa-pss-update"
version: 1.0.0
description: Handle DEP0154 via transforming deprecated RSA-PSS crypto options `hash` to `hashAlgorithm` and `mgf1Hash` to `mgf1HashAlgorithm`
author: Mauro Nievas
license: MIT
workflow: workflow.yaml
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration
- crypto
- rsa-pss

registry:
access: public
visibility: public
25 changes: 25 additions & 0 deletions recipes/crypto-rsa-pss-update/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@nodejs/crypto-rsa-pss-update",
"version": "1.0.0",
"description": "Handle DEP0154 via transforming deprecated RSA-PSS crypto options `hash` to `hashAlgorithm` and `mgf1Hash` to `mgf1HashAlgorithm`.",
"type": "module",
"scripts": {
"test": "npx codemod@next jssg test -l typescript ./src/workflow.ts ./"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nodejs/userland-migrations.git",
"directory": "recipes/crypto-rsa-pss-update",
"bugs": "https://github.com/nodejs/userland-migrations/issues"
},
"author": "Mauro Nievas",
"license": "MIT",
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/crypto-rsa-pss-update/README.md",
"devDependencies": {
"@types/node": "^24.0.3",
"@codemod.com/jssg-types": "^1.0.3"
},
"dependencies": {
"@nodejs/codemod-utils": "*"
}
}
160 changes: 160 additions & 0 deletions recipes/crypto-rsa-pss-update/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import type { SgRoot, SgNode, Edit } from "@codemod.com/jssg-types/main";
import { getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement";
import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call";
import { resolveBindingPath } from "@nodejs/codemod-utils/ast-grep/resolve-binding-path";
import type JS from "@codemod.com/jssg-types/langs/javascript";

/**
* Transform function that updates deprecated RSA-PSS crypto options.
*
* Transforms:
* - hash → hashAlgorithm
* - mgf1Hash → mgf1HashAlgorithm
*
* Only applies to crypto.generateKeyPair() and crypto.generateKeyPairSync()
* calls with 'rsa-pss' as the first argument.
*/
export default function transform(root: SgRoot<JS>): string | null {
const rootNode = root.root();
let hasChanges = false;
const edits: Edit[] = [];

// Collect all possible function names that could be generateKeyPair or generateKeyPairSync
const cryptoBindings = getCryptoBindings(root);

// Find all potential calls using any of the identified bindings
const allCalls = findCryptoCalls(rootNode, cryptoBindings);

for (const call of allCalls) {
const typeMatch = call.getMatch("TYPE");
const optionsMatch = call.getMatch("OPTIONS");

if (!typeMatch || !optionsMatch) continue;

// Only process 'rsa-pss' key type
const typeText = typeMatch.text();
if (!typeText.includes("'rsa-pss'") && !typeText.includes('"rsa-pss"')) {
continue;
}

// Find the object node that contains the options
const objectNode = optionsMatch.find({
rule: {
kind: "object"
}
});

if (!objectNode) continue;

// Find all property pairs in the object
const pairs = objectNode.findAll({
rule: {
kind: "pair"
}
});

// Transform hash and mgf1Hash properties
for (const pair of pairs) {
const keyNode = pair.find({
rule: {
any: [
{
regex: "hash",
kind: "property_identifier",
},
{
regex: "mgf1Hash",
kind: "property_identifier",
},
],
},
});
if (!keyNode) continue;
const key = keyNode.text();

// Get the value node to preserve it in the transformation
const valueNode = pair.find({
rule: {
kind: "string"
}
}) || pair.find({
rule: {
kind: "identifier"
}
}) || pair.find({
rule: {
kind: "template_string"
}
});

if (!valueNode) continue;

hasChanges = true;
const value = valueNode.text();

if (key === "hash") {
edits.push(pair.replace(`hashAlgorithm: ${value}`));
}
if (key === "mgf1Hash") {
edits.push(pair.replace(`mgf1HashAlgorithm: ${value}`));
}
}
}

if (!hasChanges) return null;

return rootNode.commitEdits(edits);
}

/**
* Analyzes imports and requires to determine all possible identifiers
* that could refer to generateKeyPair or generateKeyPairSync functions
*/
function getCryptoBindings(root: SgRoot<JS>): Map<string, string[]> {
const bindings = new Map<string, string[]>();

// @ts-ignore - ast-grep types compatibility
const importStatements = getNodeImportStatements(root, "crypto");
// @ts-ignore - ast-grep types compatibility
const requireCalls = getNodeRequireCalls(root, "crypto");

// Handle both ES6 imports and CommonJS requires
for (const stmt of [...importStatements, ...requireCalls]) {
const generateKeyPairBinding = resolveBindingPath(stmt, "$.generateKeyPair");
const generateKeyPairSyncBinding = resolveBindingPath(stmt, "$.generateKeyPairSync");

if (generateKeyPairBinding) {
bindings.set(generateKeyPairBinding, ['generateKeyPair']);
}
if (generateKeyPairSyncBinding) {
bindings.set(generateKeyPairSyncBinding, ['generateKeyPairSync']);
}
}

return bindings;
}

/**
* Find all function calls that match the crypto bindings
*/
function findCryptoCalls(rootNode: SgNode<JS>, bindings: Map<string, string[]>) {
const allCalls = [];

for (const [bindingName] of bindings) {
// Generate patterns for each binding
const patterns = [
{ pattern: `${bindingName}($TYPE, $OPTIONS, $CALLBACK)` },
{ pattern: `${bindingName}($TYPE, $OPTIONS)` }
];

const calls = rootNode.findAll({
rule: {
any: patterns
}
});

allCalls.push(...calls);
}

return allCalls;
}
24 changes: 24 additions & 0 deletions recipes/crypto-rsa-pss-update/tests/expected/basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const crypto = require('crypto');

// Basic case with hash option
crypto.generateKeyPair('rsa-pss', {
modulusLength: 2048,
hashAlgorithm: 'sha256',
saltLength: 32
}, (err, publicKey, privateKey) => {
console.log('Generated keys');
});

// Basic case with mgf1Hash option
crypto.generateKeyPairSync('rsa-pss', {
modulusLength: 2048,
mgf1HashAlgorithm: 'sha256'
});

// Both options together
crypto.generateKeyPair('rsa-pss', {
modulusLength: 2048,
hashAlgorithm: 'sha256',
mgf1HashAlgorithm: 'sha1',
saltLength: 32
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { generateKeyPair: foo, generateKeyPairSync: bar } = require('node:crypto');

// This should be transformed but currently fails
foo('rsa-pss', {
modulusLength: 2048,
hashAlgorithm: 'sha256',
saltLength: 32
}, (err, publicKey, privateKey) => {
console.log('Generated keys');
});

// This should also be transformed
bar('rsa-pss', {
modulusLength: 2048,
mgf1HashAlgorithm: 'sha256'
});
25 changes: 25 additions & 0 deletions recipes/crypto-rsa-pss-update/tests/expected/destructured.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Comprehensive import patterns test
import { generateKeyPair } from 'crypto';
const { generateKeyPairSync } = require('node:crypto');
const { generateKeyPair: aliasedGenerateKeyPair } = require('crypto');

// ES6 import destructuring
generateKeyPair('rsa-pss', {
modulusLength: 2048,
hashAlgorithm: 'sha256'
}, (err, publicKey, privateKey) => {
console.log('ES6 import');
});

// CommonJS destructuring
generateKeyPairSync('rsa-pss', {
modulusLength: 2048,
mgf1HashAlgorithm: 'sha1'
});

// Aliased destructuring
aliasedGenerateKeyPair('rsa-pss', {
modulusLength: 2048,
hashAlgorithm: 'sha512',
mgf1HashAlgorithm: 'sha256'
});
Loading
Loading