Skip to content

Commit 3b203bf

Browse files
panvaaduh95
authored andcommitted
crypto: fix RSA-PSS oversized salt handling
Let OpenSSL handle representable salt lengths so verification of an impossible length resolves false. Guard values outside the native int32 parameter range to prevent SignJob from silently ignoring them. Move the digest-size helper next to HKDF, its remaining consumer. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #65550 Reviewed-By: Aviv Keller <me@aviv.sh> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 02e0f3a commit 3b203bf

4 files changed

Lines changed: 47 additions & 50 deletions

File tree

lib/internal/crypto/hkdf.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ const {
2222
const { kMaxLength } = require('buffer');
2323

2424
const {
25-
getDigestSizeInBytes,
2625
jobPromise,
2726
normalizeHashName,
2827
toBuf,
@@ -142,6 +141,22 @@ function hkdfSync(hash, key, salt, info, length) {
142141
return bits;
143142
}
144143

144+
function getDigestSizeInBytes(name) {
145+
switch (name) {
146+
case 'SHA-1':
147+
return 20;
148+
case 'SHA-256': // Fall through
149+
case 'SHA3-256':
150+
return 32;
151+
case 'SHA-384': // Fall through
152+
case 'SHA3-384':
153+
return 48;
154+
case 'SHA-512': // Fall through
155+
case 'SHA3-512':
156+
return 64;
157+
}
158+
}
159+
145160
function validateHkdfDeriveBitsLength(length, hash) {
146161
if (length === null)
147162
throw lazyDOMException('length cannot be null', 'OperationError');

lib/internal/crypto/rsa.js

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22

33
const {
4-
MathCeil,
54
TypedArrayPrototypeGetBuffer,
65
Uint8Array,
76
} = primordials;
@@ -24,12 +23,11 @@ const {
2423
} = internalBinding('crypto');
2524

2625
const {
27-
validateInt32,
26+
isInt32,
2827
} = require('internal/validators');
2928

3029
const {
3130
bigIntArrayToUnsignedInt,
32-
getDigestSizeInBytes,
3331
getUsagesMask,
3432
jobPromise,
3533
normalizeHashName,
@@ -241,19 +239,16 @@ function rsaSignVerify(key, data, { saltLength }, signature) {
241239
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
242240

243241
const algorithm = getCryptoKeyAlgorithm(key);
244-
if (algorithm.name === 'RSA-PSS') {
245-
try {
246-
validateInt32(
247-
saltLength,
248-
'algorithm.saltLength',
249-
0,
250-
MathCeil((algorithm.modulusLength - 1) / 8) -
251-
getDigestSizeInBytes(algorithm.hash.name) - 2);
252-
} catch (err) {
253-
throw lazyDOMException(
254-
'The operation failed for an operation-specific reason',
255-
{ name: 'OperationError', cause: err });
256-
}
242+
// RsaPssParams converts saltLength to an unsigned long, but SignJob only
243+
// accepts int32 values.
244+
if (algorithm.name === 'RSA-PSS' && !isInt32(saltLength)) {
245+
// EMSA-PSS-VERIFY treats an impossible salt length as inconsistent.
246+
if (mode === kSignJobModeVerify)
247+
return false;
248+
249+
throw lazyDOMException(
250+
'The operation failed for an operation-specific reason',
251+
'OperationError');
257252
}
258253

259254
return jobPromise(() => new SignJob(

lib/internal/crypto/util.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,22 +1070,6 @@ function getBlockSize(name) {
10701070
}
10711071
}
10721072

1073-
function getDigestSizeInBytes(name) {
1074-
switch (name) {
1075-
case 'SHA-1':
1076-
return 20;
1077-
case 'SHA-256': // Fall through
1078-
case 'SHA3-256':
1079-
return 32;
1080-
case 'SHA-384': // Fall through
1081-
case 'SHA3-384':
1082-
return 48;
1083-
case 'SHA-512': // Fall through
1084-
case 'SHA3-512':
1085-
return 64;
1086-
}
1087-
}
1088-
10891073
function validateKeyOps(keyOps, usagesSet) {
10901074
if (keyOps === undefined) return;
10911075
validateArray(keyOps, 'keyData.key_ops');
@@ -1159,7 +1143,6 @@ module.exports = {
11591143
bigIntArrayToUnsignedBigInt,
11601144
bigIntArrayToUnsignedInt,
11611145
getBlockSize,
1162-
getDigestSizeInBytes,
11631146
getStringOption,
11641147
getUsagesMask,
11651148
getUsagesFromMask,

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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -227,22 +227,26 @@ async function testSaltLength(keyLength, hash, hLen) {
227227

228228
const signature = await subtle.sign(
229229
{ name: 'RSA-PSS', saltLength: max }, privateKey, data);
230-
await assert.rejects(
231-
subtle.sign({ name: 'RSA-PSS', saltLength: max + 1 }, privateKey, data), (err) => {
232-
assert.strictEqual(err.name, 'OperationError');
233-
assert.strictEqual(err.cause?.code, 'ERR_OUT_OF_RANGE');
234-
assert.strictEqual(err.cause?.message, `The value of "algorithm.saltLength" is out of range. It must be >= 0 && <= ${max}. Received ${max + 1}`);
235-
return true;
236-
});
237-
await subtle.verify(
238-
{ name: 'RSA-PSS', saltLength: max }, publicKey, signature, data);
239-
await assert.rejects(
240-
subtle.verify({ name: 'RSA-PSS', saltLength: max + 1 }, publicKey, signature, data), (err) => {
241-
assert.strictEqual(err.name, 'OperationError');
242-
assert.strictEqual(err.cause?.code, 'ERR_OUT_OF_RANGE');
243-
assert.strictEqual(err.cause?.message, `The value of "algorithm.saltLength" is out of range. It must be >= 0 && <= ${max}. Received ${max + 1}`);
244-
return true;
245-
});
230+
assert.strictEqual(await subtle.verify(
231+
{ name: 'RSA-PSS', saltLength: max }, publicKey, signature, data), true);
232+
233+
for (const saltLength of [max + 1, 0x7fffffff]) {
234+
await assert.rejects(
235+
subtle.sign({ name: 'RSA-PSS', saltLength }, privateKey, data), {
236+
name: 'OperationError',
237+
});
238+
assert.strictEqual(await subtle.verify(
239+
{ name: 'RSA-PSS', saltLength }, publicKey, signature, data), false);
240+
}
241+
242+
for (const saltLength of [0x80000000, 0xffffffff]) {
243+
await assert.rejects(
244+
subtle.sign({ name: 'RSA-PSS', saltLength }, privateKey, data), {
245+
name: 'OperationError',
246+
});
247+
assert.strictEqual(await subtle.verify(
248+
{ name: 'RSA-PSS', saltLength }, publicKey, signature, data), false);
249+
}
246250
}
247251

248252
(async function() {

0 commit comments

Comments
 (0)