Commit 618f7934644 for nodejs
commit 618f7934644cb96d0419af4cb61d3505febb3554
Author: Filip Skokan <panva.ip@gmail.com>
Date: Tue Sep 22 22:29:27 2026 +0200
crypto: report actual RSA modulus lengths
Use the generated key's modulus size for both CryptoKey algorithm
objects. Preserve successful backend generation when it rounds the
requested size, keeping metadata consistent across export and cloning.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js
index 767c5e14507..8fa3ecdff1b 100644
--- a/lib/internal/crypto/rsa.js
+++ b/lib/internal/crypto/rsa.js
@@ -32,6 +32,7 @@ const {
bigIntArrayToUnsignedInt,
getUsagesMask,
jobPromise,
+ jobPromiseThen,
normalizeHashName,
validateAlgorithm,
validateMaxBufferLength,
@@ -132,7 +133,7 @@ function rsaKeyGenerate(
const keyUsages = getKeyPairUsages(usagesSet, allowedUsages);
validateUsagesNotEmpty(keyUsages.private);
- return jobPromise(() => new RsaKeyPairGenJob(
+ return jobPromiseThen(jobPromise(() => new RsaKeyPairGenJob(
kCryptoJobWebCrypto,
kKeyVariantRSA_SSA_PKCS1_v1_5,
modulusLength,
@@ -140,7 +141,13 @@ function rsaKeyGenerate(
keyAlgorithm,
getUsagesMask(keyUsages.public),
getUsagesMask(keyUsages.private),
- extractable));
+ extractable)), (result) => {
+ const { modulusLength: actualModulusLength } =
+ getCryptoKeyHandle(result.publicKey).keyDetail({ __proto__: null });
+ getCryptoKeyAlgorithm(result.publicKey).modulusLength = actualModulusLength;
+ getCryptoKeyAlgorithm(result.privateKey).modulusLength = actualModulusLength;
+ return result;
+ });
}
function rsaExportKey(key, format) {
diff --git a/test/parallel/test-webcrypto-rsa-modulus-length.js b/test/parallel/test-webcrypto-rsa-modulus-length.js
new file mode 100644
index 00000000000..72032ed1a44
--- /dev/null
+++ b/test/parallel/test-webcrypto-rsa-modulus-length.js
@@ -0,0 +1,47 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const { generateKeyPairSync, KeyObject } = require('crypto');
+const { subtle } = globalThis.crypto;
+
+(async () => {
+ for (const name of ['RSA-PSS', 'RSASSA-PKCS1-v1_5', 'RSA-OAEP']) {
+ const usages = name === 'RSA-OAEP' ? ['encrypt', 'decrypt'] : ['sign', 'verify'];
+ for (const modulusLength of [1025, 2048, 2049]) {
+ let pair;
+ try {
+ pair = await subtle.generateKey({
+ name, modulusLength, hash: 'SHA-256', publicExponent: new Uint8Array([1, 0, 1]),
+ }, true, usages);
+ } catch (err) {
+ if (modulusLength === 2048 || err.name !== 'OperationError')
+ throw err;
+ // Only reject sizes the backend also rejects. Some backends round
+ // the requested size, which must still produce a usable CryptoKey.
+ assert.throws(() => generateKeyPairSync('rsa', { modulusLength }),
+ { name: 'Error' });
+ continue;
+ }
+ const actualModulusLength =
+ KeyObject.from(pair.publicKey).asymmetricKeyDetails.modulusLength;
+ for (const key of [pair.publicKey, pair.privateKey]) {
+ assert.strictEqual(key.algorithm.modulusLength, actualModulusLength);
+ assert.strictEqual(
+ KeyObject.from(key).asymmetricKeyDetails.modulusLength, actualModulusLength);
+ assert.deepStrictEqual(structuredClone(key).algorithm, key.algorithm);
+
+ const format = key.type === 'public' ? 'spki' : 'pkcs8';
+ const imported = await subtle.importKey(
+ format, await subtle.exportKey(format, key),
+ { name, hash: 'SHA-256' }, true, key.usages);
+ assert.deepStrictEqual(imported.algorithm, key.algorithm);
+ }
+ const publicKey = await subtle.getPublicKey(pair.privateKey, pair.publicKey.usages);
+ assert.deepStrictEqual(publicKey.algorithm, pair.publicKey.algorithm);
+ }
+ }
+})().then(common.mustCall());