mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
0e710aada4
Adds support for the PSS padding scheme. Until now, the sign/verify functions used the old EVP_Sign*/EVP_Verify* OpenSSL API, making it impossible to change the padding scheme. Fixed by first computing the message digest and then signing/verifying with a custom EVP_PKEY_CTX, allowing us to specify options such as the padding scheme and the PSS salt length. Fixes: https://github.com/nodejs/node/issues/1127 PR-URL: https://github.com/nodejs/node/pull/11705 Reviewed-By: Shigeki Ohtsu <ohtsu@ohtsu.org> Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Anna Henningsen <anna@addaleax.net>
277 lines
9.3 KiB
JavaScript
277 lines
9.3 KiB
JavaScript
'use strict';
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const exec = require('child_process').exec;
|
|
|
|
if (!common.hasCrypto) {
|
|
common.skip('missing crypto');
|
|
return;
|
|
}
|
|
const crypto = require('crypto');
|
|
|
|
// Test certificates
|
|
const certPem = fs.readFileSync(common.fixturesDir + '/test_cert.pem', 'ascii');
|
|
const keyPem = fs.readFileSync(common.fixturesDir + '/test_key.pem', 'ascii');
|
|
const modSize = 1024;
|
|
|
|
// Test signing and verifying
|
|
{
|
|
const s1 = crypto.createSign('RSA-SHA1')
|
|
.update('Test123')
|
|
.sign(keyPem, 'base64');
|
|
let s1stream = crypto.createSign('RSA-SHA1');
|
|
s1stream.end('Test123');
|
|
s1stream = s1stream.sign(keyPem, 'base64');
|
|
assert.strictEqual(s1, s1stream, 'Stream produces same output');
|
|
|
|
const verified = crypto.createVerify('RSA-SHA1')
|
|
.update('Test')
|
|
.update('123')
|
|
.verify(certPem, s1, 'base64');
|
|
assert.strictEqual(verified, true, 'sign and verify (base 64)');
|
|
}
|
|
|
|
{
|
|
const s2 = crypto.createSign('RSA-SHA256')
|
|
.update('Test123')
|
|
.sign(keyPem, 'latin1');
|
|
let s2stream = crypto.createSign('RSA-SHA256');
|
|
s2stream.end('Test123');
|
|
s2stream = s2stream.sign(keyPem, 'latin1');
|
|
assert.strictEqual(s2, s2stream, 'Stream produces same output');
|
|
|
|
let verified = crypto.createVerify('RSA-SHA256')
|
|
.update('Test')
|
|
.update('123')
|
|
.verify(certPem, s2, 'latin1');
|
|
assert.strictEqual(verified, true, 'sign and verify (latin1)');
|
|
|
|
const verStream = crypto.createVerify('RSA-SHA256');
|
|
verStream.write('Tes');
|
|
verStream.write('t12');
|
|
verStream.end('3');
|
|
verified = verStream.verify(certPem, s2, 'latin1');
|
|
assert.strictEqual(verified, true, 'sign and verify (stream)');
|
|
}
|
|
|
|
{
|
|
const s3 = crypto.createSign('RSA-SHA1')
|
|
.update('Test123')
|
|
.sign(keyPem, 'buffer');
|
|
let verified = crypto.createVerify('RSA-SHA1')
|
|
.update('Test')
|
|
.update('123')
|
|
.verify(certPem, s3);
|
|
assert.strictEqual(verified, true, 'sign and verify (buffer)');
|
|
|
|
const verStream = crypto.createVerify('RSA-SHA1');
|
|
verStream.write('Tes');
|
|
verStream.write('t12');
|
|
verStream.end('3');
|
|
verified = verStream.verify(certPem, s3);
|
|
assert.strictEqual(verified, true, 'sign and verify (stream)');
|
|
}
|
|
|
|
// Special tests for RSA_PKCS1_PSS_PADDING
|
|
{
|
|
function testPSS(algo, hLen) {
|
|
// Maximum permissible salt length
|
|
const max = modSize / 8 - hLen - 2;
|
|
|
|
function getEffectiveSaltLength(saltLength) {
|
|
switch (saltLength) {
|
|
case crypto.constants.RSA_PSS_SALTLEN_DIGEST:
|
|
return hLen;
|
|
case crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN:
|
|
return max;
|
|
default:
|
|
return saltLength;
|
|
}
|
|
}
|
|
|
|
const signSaltLengths = [
|
|
crypto.constants.RSA_PSS_SALTLEN_DIGEST,
|
|
getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST),
|
|
crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN,
|
|
getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN),
|
|
0, 16, 32, 64, 128
|
|
];
|
|
|
|
const verifySaltLengths = [
|
|
crypto.constants.RSA_PSS_SALTLEN_DIGEST,
|
|
getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST),
|
|
getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN),
|
|
0, 16, 32, 64, 128
|
|
];
|
|
|
|
signSaltLengths.forEach((signSaltLength) => {
|
|
if (signSaltLength > max) {
|
|
// If the salt length is too big, an Error should be thrown
|
|
assert.throws(() => {
|
|
crypto.createSign(algo)
|
|
.update('Test123')
|
|
.sign({
|
|
key: keyPem,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
saltLength: signSaltLength
|
|
});
|
|
}, /^Error:.*data too large for key size$/);
|
|
} else {
|
|
// Otherwise, a valid signature should be generated
|
|
const s4 = crypto.createSign(algo)
|
|
.update('Test123')
|
|
.sign({
|
|
key: keyPem,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
saltLength: signSaltLength
|
|
});
|
|
|
|
let verified;
|
|
verifySaltLengths.forEach((verifySaltLength) => {
|
|
// Verification should succeed if and only if the salt length is
|
|
// correct
|
|
verified = crypto.createVerify(algo)
|
|
.update('Test123')
|
|
.verify({
|
|
key: certPem,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
saltLength: verifySaltLength
|
|
}, s4);
|
|
const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) ==
|
|
getEffectiveSaltLength(verifySaltLength);
|
|
assert.strictEqual(verified, saltLengthCorrect, 'verify (PSS)');
|
|
});
|
|
|
|
// Verification using RSA_PSS_SALTLEN_AUTO should always work
|
|
verified = crypto.createVerify(algo)
|
|
.update('Test123')
|
|
.verify({
|
|
key: certPem,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
|
|
}, s4);
|
|
assert.strictEqual(verified, true, 'verify (PSS with SALTLEN_AUTO)');
|
|
|
|
// Verifying an incorrect message should never work
|
|
verified = crypto.createVerify(algo)
|
|
.update('Test1234')
|
|
.verify({
|
|
key: certPem,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
|
|
}, s4);
|
|
assert.strictEqual(verified, false, 'verify (PSS, incorrect)');
|
|
}
|
|
});
|
|
}
|
|
|
|
testPSS('RSA-SHA1', 20);
|
|
testPSS('RSA-SHA256', 32);
|
|
}
|
|
|
|
// Test vectors for RSA_PKCS1_PSS_PADDING provided by the RSA Laboratories:
|
|
// https://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-rsa-cryptography-standard.htm
|
|
{
|
|
// We only test verification as we cannot specify explicit salts when signing
|
|
function testVerify(cert, vector) {
|
|
const verified = crypto.createVerify('RSA-SHA1')
|
|
.update(Buffer.from(vector.message, 'hex'))
|
|
.verify({
|
|
key: cert,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
saltLength: vector.salt.length / 2
|
|
}, vector.signature, 'hex');
|
|
assert.strictEqual(verified, true, 'verify (PSS)');
|
|
}
|
|
|
|
const vectorfile = path.join(common.fixturesDir, 'pss-vectors.json');
|
|
const examples = JSON.parse(fs.readFileSync(vectorfile, {
|
|
encoding: 'utf8'
|
|
}));
|
|
|
|
for (const key in examples) {
|
|
const example = examples[key];
|
|
const publicKey = example.publicKey.join('\n');
|
|
example.tests.forEach((test) => testVerify(publicKey, test));
|
|
}
|
|
}
|
|
|
|
// Test exceptions for invalid `padding` and `saltLength` values
|
|
{
|
|
[null, undefined, NaN, 'boom', {}, [], true, false]
|
|
.forEach((invalidValue) => {
|
|
assert.throws(() => {
|
|
crypto.createSign('RSA-SHA256')
|
|
.update('Test123')
|
|
.sign({
|
|
key: keyPem,
|
|
padding: invalidValue
|
|
});
|
|
}, /^TypeError: padding must be an integer$/);
|
|
|
|
assert.throws(() => {
|
|
crypto.createSign('RSA-SHA256')
|
|
.update('Test123')
|
|
.sign({
|
|
key: keyPem,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
saltLength: invalidValue
|
|
});
|
|
}, /^TypeError: saltLength must be an integer$/);
|
|
});
|
|
|
|
assert.throws(() => {
|
|
crypto.createSign('RSA-SHA1')
|
|
.update('Test123')
|
|
.sign({
|
|
key: keyPem,
|
|
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
|
|
});
|
|
}, /^Error:.*illegal or unsupported padding mode$/);
|
|
}
|
|
|
|
// Test throws exception when key options is null
|
|
{
|
|
assert.throws(() => {
|
|
crypto.createSign('RSA-SHA1').update('Test123').sign(null, 'base64');
|
|
}, /^Error: No key provided to sign$/);
|
|
}
|
|
|
|
// RSA-PSS Sign test by verifying with 'openssl dgst -verify'
|
|
{
|
|
if (!common.opensslCli) {
|
|
common.skip('node compiled without OpenSSL CLI.');
|
|
return;
|
|
}
|
|
|
|
const pubfile = path.join(common.fixturesDir, 'keys/rsa_public_2048.pem');
|
|
const privfile = path.join(common.fixturesDir, 'keys/rsa_private_2048.pem');
|
|
const privkey = fs.readFileSync(privfile);
|
|
|
|
const msg = 'Test123';
|
|
const s5 = crypto.createSign('RSA-SHA256')
|
|
.update(msg)
|
|
.sign({
|
|
key: privkey,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING
|
|
});
|
|
|
|
common.refreshTmpDir();
|
|
|
|
const sigfile = path.join(common.tmpDir, 's5.sig');
|
|
fs.writeFileSync(sigfile, s5);
|
|
const msgfile = path.join(common.tmpDir, 's5.msg');
|
|
fs.writeFileSync(msgfile, msg);
|
|
|
|
const cmd = '"' + common.opensslCli + '" dgst -sha256 -verify "' + pubfile +
|
|
'" -signature "' + sigfile +
|
|
'" -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-2 "' +
|
|
msgfile + '"';
|
|
|
|
exec(cmd, common.mustCall((err, stdout, stderr) => {
|
|
assert(stdout.includes('Verified OK'));
|
|
}));
|
|
}
|