Skip to content

Commit

Permalink
crypto: add rsa-pss keygen parameters
Browse files Browse the repository at this point in the history
PR-URL: #39927
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
panva authored and richardlau committed Sep 10, 2021
1 parent 22a78a7 commit fb226ff
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 22 deletions.
16 changes: 16 additions & 0 deletions doc/api/crypto.md
Expand Up @@ -3375,6 +3375,10 @@ generateKey('hmac', { length: 64 }, (err, key) => {
<!-- YAML
added: v10.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39927
description: Add ability to define `RSASSA-PSS-params` sequence parameters
for RSA-PSS keys pairs.
- version:
- v13.9.0
- v12.17.0
Expand All @@ -3400,6 +3404,10 @@ changes:
* `options`: {Object}
* `modulusLength`: {number} Key size in bits (RSA, DSA).
* `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
* `hashAlgorithm`: {string} Name of the message digest (RSA-PSS).
* `mgf1HashAlgorithm`: {string} Name of the message digest used by
MGF1 (RSA-PSS).
* `saltLength`: {number} Minimal salt length in bytes (RSA-PSS).
* `divisorLength`: {number} Size of `q` in bits (DSA).
* `namedCurve`: {string} Name of the curve to use (EC).
* `prime`: {Buffer} The prime parameter (DH).
Expand Down Expand Up @@ -3478,6 +3486,10 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
<!-- YAML
added: v10.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39927
description: Add ability to define `RSASSA-PSS-params` sequence parameters
for RSA-PSS keys pairs.
- version:
- v13.9.0
- v12.17.0
Expand All @@ -3503,6 +3515,10 @@ changes:
* `options`: {Object}
* `modulusLength`: {number} Key size in bits (RSA, DSA).
* `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
* `hashAlgorithm`: {string} Name of the message digest (RSA-PSS).
* `mgf1HashAlgorithm`: {string} Name of the message digest used by
MGF1 (RSA-PSS).
* `saltLength`: {number} Minimal salt length in bytes (RSA-PSS).
* `divisorLength`: {number} Size of `q` in bits (DSA).
* `namedCurve`: {string} Name of the curve to use (EC).
* `prime`: {Buffer} The prime parameter (DH).
Expand Down
13 changes: 13 additions & 0 deletions doc/api/deprecations.md
Expand Up @@ -2799,6 +2799,19 @@ non-number value for `hints` option, a non-nullish non-boolean value for `all`
option, or a non-nullish non-boolean value for `verbatim` option in
[`dns.lookup()`][] and [`dnsPromises.lookup()`][] is deprecated.

### DEP0154: RSA-PSS generate key pair options
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39927
description: Documentation-only deprecation.
-->

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
Expand Down
43 changes: 36 additions & 7 deletions lib/internal/crypto/keygen.js
Expand Up @@ -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) ||
Expand Down Expand Up @@ -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);
}
Expand Down
51 changes: 51 additions & 0 deletions 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
});
}));
}
83 changes: 68 additions & 15 deletions test/parallel/test-crypto-keygen.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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)}`
});
}
Expand All @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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
});
},
{
Expand All @@ -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)}`

}
);
Expand Down Expand Up @@ -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' });
}

0 comments on commit fb226ff

Please sign in to comment.