From fb226ff2eeb55680e86d03d625507f45ace6d328 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 29 Aug 2021 10:09:48 +0200 Subject: [PATCH] crypto: add rsa-pss keygen parameters PR-URL: https://github.com/nodejs/node/pull/39927 Reviewed-By: James M Snell --- doc/api/crypto.md | 16 ++++ doc/api/deprecations.md | 13 +++ lib/internal/crypto/keygen.js | 43 ++++++++-- .../test-crypto-keygen-deprecation.js | 51 ++++++++++++ test/parallel/test-crypto-keygen.js | 83 +++++++++++++++---- 5 files changed, 184 insertions(+), 22 deletions(-) create mode 100644 test/parallel/test-crypto-keygen-deprecation.js diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 7c69a638e09a5c..db917d7eb81113 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -3375,6 +3375,10 @@ generateKey('hmac', { length: 64 }, (err, key) => { + +Type: Documentation-only (supports [`--pending-deprecation`][]) + +The `'hash'` and `'mgf1Hash'` options are replaced with `'hashAlgorithm'` +and `'mgf1HashAlgorithm'`. + [Legacy URL API]: url.md#url_legacy_url_api [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf [RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3 diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js index 49a7f044cb66cd..1469a01682edda 100644 --- a/lib/internal/crypto/keygen.js +++ b/lib/internal/crypto/keygen.js @@ -60,6 +60,9 @@ const { const { isArrayBufferView } = require('internal/util/types'); +const { getOptionValue } = require('internal/options'); +const pendingDeprecation = getOptionValue('--pending-deprecation'); + function wrapKey(key, ctor) { if (typeof key === 'string' || isArrayBufferView(key) || @@ -193,21 +196,47 @@ function createJob(mode, type, options) { ...encoding); } - const { hash, mgf1Hash, saltLength } = options; - if (hash !== undefined && typeof hash !== 'string') - throw new ERR_INVALID_ARG_VALUE('options.hash', hash); - if (mgf1Hash !== undefined && typeof mgf1Hash !== 'string') - throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash); + const { + hash, mgf1Hash, hashAlgorithm, mgf1HashAlgorithm, saltLength + } = options; if (saltLength !== undefined && (!isInt32(saltLength) || saltLength < 0)) throw new ERR_INVALID_ARG_VALUE('options.saltLength', saltLength); + if (hashAlgorithm !== undefined && typeof hashAlgorithm !== 'string') + throw new ERR_INVALID_ARG_VALUE('options.hashAlgorithm', hashAlgorithm); + if (mgf1HashAlgorithm !== undefined && + typeof mgf1HashAlgorithm !== 'string') + throw new ERR_INVALID_ARG_VALUE('options.mgf1HashAlgorithm', + mgf1HashAlgorithm); + if (hash !== undefined) { + pendingDeprecation && process.emitWarning( + '"options.hash" is deprecated, ' + + 'use "options.hashAlgorithm" instead.', + 'DeprecationWarning', + 'DEP0154'); + if (typeof hash !== 'string' || + (hashAlgorithm && hash !== hashAlgorithm)) { + throw new ERR_INVALID_ARG_VALUE('options.hash', hash); + } + } + if (mgf1Hash !== undefined) { + pendingDeprecation && process.emitWarning( + '"options.mgf1Hash" is deprecated, ' + + 'use "options.mgf1HashAlgorithm" instead.', + 'DeprecationWarning', + 'DEP0154'); + if (typeof mgf1Hash !== 'string' || + (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm)) { + throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash); + } + } return new RsaKeyPairGenJob( mode, kKeyVariantRSA_PSS, modulusLength, publicExponent, - hash, - mgf1Hash, + hashAlgorithm || hash, + mgf1HashAlgorithm || mgf1Hash, saltLength, ...encoding); } diff --git a/test/parallel/test-crypto-keygen-deprecation.js b/test/parallel/test-crypto-keygen-deprecation.js new file mode 100644 index 00000000000000..318377e840b0fc --- /dev/null +++ b/test/parallel/test-crypto-keygen-deprecation.js @@ -0,0 +1,51 @@ +// Flags: --pending-deprecation + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const DeprecationWarning = []; +DeprecationWarning.push([ + '"options.hash" is deprecated, use "options.hashAlgorithm" instead.', + 'DEP0154']); +DeprecationWarning.push([ + '"options.mgf1Hash" is deprecated, use "options.mgf1HashAlgorithm" instead.', + 'DEP0154']); + +common.expectWarning({ DeprecationWarning }); + +const assert = require('assert'); +const { generateKeyPair } = require('crypto'); + +{ + // This test makes sure deprecated options still work as intended + + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hash: 'sha256', + mgf1Hash: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + })); +} diff --git a/test/parallel/test-crypto-keygen.js b/test/parallel/test-crypto-keygen.js index 09d43317426e71..d35eeae5b98ed5 100644 --- a/test/parallel/test-crypto-keygen.js +++ b/test/parallel/test-crypto-keygen.js @@ -302,8 +302,8 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); generateKeyPair('rsa-pss', { modulusLength: 512, saltLength: 16, - hash: 'sha256', - mgf1Hash: 'sha256' + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256' }, common.mustSucceed((publicKey, privateKey) => { assert.strictEqual(publicKey.type, 'public'); assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); @@ -1324,12 +1324,12 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); assert.throws(() => { generateKeyPairSync('rsa-pss', { modulusLength: 4096, - hash: hashValue + hashAlgorithm: hashValue }); }, { name: 'TypeError', code: 'ERR_INVALID_ARG_VALUE', - message: "The property 'options.hash' is invalid. " + + message: "The property 'options.hashAlgorithm' is invalid. " + `Received ${inspect(hashValue)}` }); } @@ -1339,8 +1339,8 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); generateKeyPair('rsa-pss', { modulusLength: 512, saltLength: 2147483648, - hash: 'sha256', - mgf1Hash: 'sha256' + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256' }, common.mustNotCall()); }, { name: 'TypeError', @@ -1353,8 +1353,8 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); generateKeyPair('rsa-pss', { modulusLength: 512, saltLength: -1, - hash: 'sha256', - mgf1Hash: 'sha256' + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256' }, common.mustNotCall()); }, { name: 'TypeError', @@ -1451,8 +1451,8 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); generateKeyPair('rsa-pss', { modulusLength: 512, saltLength: 16, - hash: 'sha256', - mgf1Hash: undefined + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: undefined }); }, { @@ -1462,21 +1462,21 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); } ); - for (const mgf1Hash of [null, 0, false, {}, []]) { + for (const mgf1HashAlgorithm of [null, 0, false, {}, []]) { assert.throws( () => { generateKeyPair('rsa-pss', { modulusLength: 512, saltLength: 16, - hash: 'sha256', - mgf1Hash + hashAlgorithm: 'sha256', + mgf1HashAlgorithm }, common.mustNotCall()); }, { name: 'TypeError', code: 'ERR_INVALID_ARG_VALUE', - message: "The property 'options.mgf1Hash' is invalid. " + - `Received ${inspect(mgf1Hash)}` + message: "The property 'options.mgf1HashAlgorithm' is invalid. " + + `Received ${inspect(mgf1HashAlgorithm)}` } ); @@ -1568,3 +1568,56 @@ if (!common.hasOpenSSL3) { } } } + +{ + // This test makes sure deprecated and new options may be used + // simultaneously so long as they're identical values. + + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hash: 'sha256', + hashAlgorithm: 'sha256', + mgf1Hash: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + })); +} + +{ + // This test makes sure deprecated and new options must + // be the same value. + + assert.throws(() => generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + mgf1Hash: 'sha256', + mgf1HashAlgorithm: 'sha1' + }, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' }); + + assert.throws(() => generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hash: 'sha256', + hashAlgorithm: 'sha1' + }, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' }); +}