Skip to content

Commit 2241e8c

Browse files
authored
crypto: validate RSA-PSS saltLength in subtle.sign and subtle.verify
fixes: #52188 PR-URL: #52262 Fixes: #52188 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
1 parent 24c1a8e commit 2241e8c

File tree

3 files changed

+71
-23
lines changed

3 files changed

+71
-23
lines changed

lib/internal/crypto/rsa.js

+26-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
MathCeil,
45
SafeSet,
56
Uint8Array,
67
} = primordials;
@@ -27,6 +28,7 @@ const {
2728

2829
const {
2930
bigIntArrayToUnsignedInt,
31+
getDigestSizeInBytes,
3032
getUsagesUnion,
3133
hasAnyNotIn,
3234
jobPromise,
@@ -306,35 +308,36 @@ async function rsaImportKey(
306308
}, keyUsages, extractable);
307309
}
308310

309-
function rsaSignVerify(key, data, { saltLength }, signature) {
310-
let padding;
311-
if (key.algorithm.name === 'RSA-PSS') {
312-
padding = RSA_PKCS1_PSS_PADDING;
313-
// TODO(@jasnell): Validate maximum size of saltLength
314-
// based on the key size:
315-
// Math.ceil((keySizeInBits - 1)/8) - digestSizeInBytes - 2
316-
validateInt32(saltLength, 'algorithm.saltLength', -2);
317-
}
318-
311+
async function rsaSignVerify(key, data, { saltLength }, signature) {
319312
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
320313
const type = mode === kSignJobModeSign ? 'private' : 'public';
321314

322315
if (key.type !== type)
323316
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
324317

325-
return jobPromise(() => new SignJob(
326-
kCryptoJobAsync,
327-
signature === undefined ? kSignJobModeSign : kSignJobModeVerify,
328-
key[kKeyObject][kHandle],
329-
undefined,
330-
undefined,
331-
undefined,
332-
data,
333-
normalizeHashName(key.algorithm.hash.name),
334-
saltLength,
335-
padding,
336-
undefined,
337-
signature));
318+
return jobPromise(() => {
319+
if (key.algorithm.name === 'RSA-PSS') {
320+
validateInt32(
321+
saltLength,
322+
'algorithm.saltLength',
323+
0,
324+
MathCeil((key.algorithm.modulusLength - 1) / 8) - getDigestSizeInBytes(key.algorithm.hash.name) - 2);
325+
}
326+
327+
return new SignJob(
328+
kCryptoJobAsync,
329+
signature === undefined ? kSignJobModeSign : kSignJobModeVerify,
330+
key[kKeyObject][kHandle],
331+
undefined,
332+
undefined,
333+
undefined,
334+
data,
335+
normalizeHashName(key.algorithm.hash.name),
336+
saltLength,
337+
key.algorithm.name === 'RSA-PSS' ? RSA_PKCS1_PSS_PADDING : undefined,
338+
undefined,
339+
signature);
340+
});
338341
}
339342

340343

lib/internal/crypto/util.js

+10
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,15 @@ function getBlockSize(name) {
517517
}
518518
}
519519

520+
function getDigestSizeInBytes(name) {
521+
switch (name) {
522+
case 'SHA-1': return 20;
523+
case 'SHA-256': return 32;
524+
case 'SHA-384': return 48;
525+
case 'SHA-512': return 64;
526+
}
527+
}
528+
520529
const kKeyOps = {
521530
sign: 1,
522531
verify: 2,
@@ -596,6 +605,7 @@ module.exports = {
596605
bigIntArrayToUnsignedBigInt,
597606
bigIntArrayToUnsignedInt,
598607
getBlockSize,
608+
getDigestSizeInBytes,
599609
getStringOption,
600610
getUsagesUnion,
601611
secureHeapUsed,

test/parallel/test-webcrypto-sign-verify-rsa.js

+35
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,35 @@ async function testSign({
194194
});
195195
}
196196

197+
async function testSaltLength(keyLength, hash, hLen) {
198+
const { publicKey, privateKey } = await subtle.generateKey({
199+
name: 'RSA-PSS',
200+
modulusLength: keyLength,
201+
publicExponent: new Uint8Array([1, 0, 1]),
202+
hash,
203+
}, false, ['sign', 'verify']);
204+
205+
const data = Buffer.from('Hello, world!');
206+
const max = keyLength / 8 - hLen - 2;
207+
208+
const signature = await subtle.sign({ name: 'RSA-PSS', saltLength: max }, privateKey, data);
209+
await assert.rejects(
210+
subtle.sign({ name: 'RSA-PSS', saltLength: max + 1 }, privateKey, data), (err) => {
211+
assert.strictEqual(err.name, 'OperationError');
212+
assert.strictEqual(err.cause?.code, 'ERR_OUT_OF_RANGE');
213+
assert.strictEqual(err.cause?.message, `The value of "algorithm.saltLength" is out of range. It must be >= 0 && <= ${max}. Received ${max + 1}`);
214+
return true;
215+
});
216+
await subtle.verify({ name: 'RSA-PSS', saltLength: max }, publicKey, signature, data);
217+
await assert.rejects(
218+
subtle.verify({ name: 'RSA-PSS', saltLength: max + 1 }, publicKey, signature, data), (err) => {
219+
assert.strictEqual(err.name, 'OperationError');
220+
assert.strictEqual(err.cause?.code, 'ERR_OUT_OF_RANGE');
221+
assert.strictEqual(err.cause?.message, `The value of "algorithm.saltLength" is out of range. It must be >= 0 && <= ${max}. Received ${max + 1}`);
222+
return true;
223+
});
224+
}
225+
197226
(async function() {
198227
const variations = [];
199228

@@ -206,5 +235,11 @@ async function testSign({
206235
variations.push(testSign(vector));
207236
});
208237

238+
for (const keyLength of [1024, 2048]) {
239+
for (const [hash, hLen] of [['SHA-1', 20], ['SHA-256', 32], ['SHA-384', 48], ['SHA-512', 64]]) {
240+
variations.push(testSaltLength(keyLength, hash, hLen));
241+
}
242+
}
243+
209244
await Promise.all(variations);
210245
})().then(common.mustCall());

0 commit comments

Comments
 (0)