'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')); })); }