node/test/parallel/test-webcrypto-sign-verify-rsa.js
Filip Skokan 2241e8c5b3
crypto: validate RSA-PSS saltLength in subtle.sign and subtle.verify
fixes: https://github.com/nodejs/node/issues/52188
PR-URL: https://github.com/nodejs/node/pull/52262
Fixes: https://github.com/nodejs/node/issues/52188
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
2024-04-03 05:16:10 +00:00

246 lines
6.5 KiB
JavaScript

'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { subtle } = globalThis.crypto;
const rsa_pkcs = require('../fixtures/crypto/rsa_pkcs');
const rsa_pss = require('../fixtures/crypto/rsa_pss');
async function testVerify({
algorithm,
hash,
publicKeyBuffer,
privateKeyBuffer,
signature,
plaintext,
}) {
const [
publicKey,
noVerifyPublicKey,
privateKey,
hmacKey,
ecdsaKeys,
] = await Promise.all([
subtle.importKey(
'spki',
publicKeyBuffer,
{ name: algorithm.name, hash },
false,
['verify']),
subtle.importKey(
'spki',
publicKeyBuffer,
{ name: algorithm.name, hash },
false,
[ /* No usages */ ]),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name: algorithm.name, hash },
false,
['sign']),
subtle.generateKey(
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']),
subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-521',
hash: 'SHA-256',
},
false,
['sign']),
]);
assert(await subtle.verify(algorithm, publicKey, signature, plaintext));
// Test verification with altered buffers
const copy = Buffer.from(plaintext);
const sigcopy = Buffer.from(signature);
const p = subtle.verify(algorithm, publicKey, sigcopy, copy);
copy[0] = 255 - copy[0];
sigcopy[0] = 255 - sigcopy[0];
assert(await p);
// Test failure when using wrong key
await assert.rejects(
subtle.verify(algorithm, privateKey, signature, plaintext), {
message: /Unable to use this key to verify/
});
await assert.rejects(
subtle.verify(algorithm, noVerifyPublicKey, signature, plaintext), {
message: /Unable to use this key to verify/
});
// Test failure when using the wrong algorithms
await assert.rejects(
subtle.verify(algorithm, hmacKey, signature, plaintext), {
message: /Unable to use this key to verify/
});
await assert.rejects(
subtle.verify(algorithm, ecdsaKeys.publicKey, signature, plaintext), {
message: /Unable to use this key to verify/
});
// Test failure when signature is altered
{
const copy = Buffer.from(signature);
copy[0] = 255 - copy[0];
assert(!(await subtle.verify(algorithm, publicKey, copy, plaintext)));
assert(!(await subtle.verify(
algorithm,
publicKey,
copy.slice(1),
plaintext)));
}
// Test failure when data is altered
{
const copy = Buffer.from(plaintext);
copy[0] = 255 - copy[0];
assert(!(await subtle.verify(algorithm, publicKey, signature, copy)));
}
// Test failure when wrong hash is used
{
const otherhash = hash === 'SHA-1' ? 'SHA-256' : 'SHA-1';
const keyWithOtherHash = await subtle.importKey(
'spki',
publicKeyBuffer,
{ name: algorithm.name, hash: otherhash },
false,
['verify']);
assert(!(await subtle.verify(algorithm, keyWithOtherHash, signature, plaintext)));
}
}
async function testSign({
algorithm,
hash,
publicKeyBuffer,
privateKeyBuffer,
signature,
plaintext,
}) {
const [
publicKey,
privateKey,
hmacKey,
ecdsaKeys,
] = await Promise.all([
subtle.importKey(
'spki',
publicKeyBuffer,
{ name: algorithm.name, hash },
false,
['verify']),
subtle.importKey(
'pkcs8',
privateKeyBuffer,
{ name: algorithm.name, hash },
false,
['sign']),
subtle.generateKey(
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']),
subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-521',
hash: 'SHA-256',
},
false,
['sign']),
]);
{
const sig = await subtle.sign(algorithm, privateKey, plaintext);
assert.strictEqual(sig.byteLength, signature.byteLength);
assert(await subtle.verify(algorithm, publicKey, sig, plaintext));
}
{
const copy = Buffer.from(plaintext);
const p = subtle.sign(algorithm, privateKey, copy);
copy[0] = 255 - copy[0];
const sig = await p;
assert(await subtle.verify(algorithm, publicKey, sig, plaintext));
}
// Test failure when using wrong key
await assert.rejects(
subtle.sign(algorithm, publicKey, plaintext), {
message: /Unable to use this key to sign/
});
// Test failure when using the wrong algorithms
await assert.rejects(
subtle.sign(algorithm, hmacKey, plaintext), {
message: /Unable to use this key to sign/
});
await assert.rejects(
subtle.sign(algorithm, ecdsaKeys.privateKey, plaintext), {
message: /Unable to use this key to sign/
});
}
async function testSaltLength(keyLength, hash, hLen) {
const { publicKey, privateKey } = await subtle.generateKey({
name: 'RSA-PSS',
modulusLength: keyLength,
publicExponent: new Uint8Array([1, 0, 1]),
hash,
}, false, ['sign', 'verify']);
const data = Buffer.from('Hello, world!');
const max = keyLength / 8 - hLen - 2;
const signature = await subtle.sign({ name: 'RSA-PSS', saltLength: max }, privateKey, data);
await assert.rejects(
subtle.sign({ name: 'RSA-PSS', saltLength: max + 1 }, privateKey, data), (err) => {
assert.strictEqual(err.name, 'OperationError');
assert.strictEqual(err.cause?.code, 'ERR_OUT_OF_RANGE');
assert.strictEqual(err.cause?.message, `The value of "algorithm.saltLength" is out of range. It must be >= 0 && <= ${max}. Received ${max + 1}`);
return true;
});
await subtle.verify({ name: 'RSA-PSS', saltLength: max }, publicKey, signature, data);
await assert.rejects(
subtle.verify({ name: 'RSA-PSS', saltLength: max + 1 }, publicKey, signature, data), (err) => {
assert.strictEqual(err.name, 'OperationError');
assert.strictEqual(err.cause?.code, 'ERR_OUT_OF_RANGE');
assert.strictEqual(err.cause?.message, `The value of "algorithm.saltLength" is out of range. It must be >= 0 && <= ${max}. Received ${max + 1}`);
return true;
});
}
(async function() {
const variations = [];
rsa_pkcs().forEach((vector) => {
variations.push(testVerify(vector));
variations.push(testSign(vector));
});
rsa_pss().forEach((vector) => {
variations.push(testVerify(vector));
variations.push(testSign(vector));
});
for (const keyLength of [1024, 2048]) {
for (const [hash, hLen] of [['SHA-1', 20], ['SHA-256', 32], ['SHA-384', 48], ['SHA-512', 64]]) {
variations.push(testSaltLength(keyLength, hash, hLen));
}
}
await Promise.all(variations);
})().then(common.mustCall());