Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: add rsa-pss keygen parameters #39927

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -3397,6 +3401,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 @@ -3475,6 +3483,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 @@ -3494,6 +3506,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 @@ -2801,6 +2801,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.

### DEP0XXX: 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#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',
'DEP0XXX');
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',
'DEP0XXX');
if (typeof mgf1Hash !== 'string' ||
(mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm)) {
throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash);
}
}
panva marked this conversation as resolved.
Show resolved Hide resolved

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.',
'DEP0XXX']);
DeprecationWarning.push([
'"options.mgf1Hash" is deprecated, use "options.mgf1HashAlgorithm" instead.',
'DEP0XXX']);

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 @@ -1301,12 +1301,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 @@ -1316,8 +1316,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 @@ -1330,8 +1330,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 @@ -1428,8 +1428,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 @@ -1439,21 +1439,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 @@ -1545,3 +1545,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' });
}