mirror of
https://github.com/nodejs/node.git
synced 2025-05-21 08:57:07 +00:00

This will be a start to generalize all argument validation errors. As currently we throw ARG/OPT, OUT_OF_RANGE, and other more specific errors. The OPT errors didn't bring much to the errors as it's just another variant of ARG error which is sometimes more confusing (some of our code used OPT errors to denote just argument validation errors presumably because of similarity of OPT to 'option' and not 'options-object') and they don't specify the name of the options object where the invalid value is located. Much better approach would be to just specify path to the invalid value in the name of the value as it is done in this PR (i.e. 'options.format', 'options.publicKey.type' etc) Also since this decreases a variety of errors we have it'd be easier to reuse validation code across the codebase. Refs: https://github.com/nodejs/node/pull/31251 Refs: https://github.com/nodejs/node/pull/34070#discussion_r467251009 Signed-off-by: Denys Otrishko <shishugi@gmail.com> PR-URL: https://github.com/nodejs/node/pull/34682 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
322 lines
10 KiB
JavaScript
322 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ObjectDefineProperty,
|
|
} = primordials;
|
|
|
|
const { AsyncWrap, Providers } = internalBinding('async_wrap');
|
|
const {
|
|
generateKeyPairRSA,
|
|
generateKeyPairRSAPSS,
|
|
generateKeyPairDSA,
|
|
generateKeyPairEC,
|
|
generateKeyPairNid,
|
|
generateKeyPairDH,
|
|
EVP_PKEY_ED25519,
|
|
EVP_PKEY_ED448,
|
|
EVP_PKEY_X25519,
|
|
EVP_PKEY_X448,
|
|
OPENSSL_EC_NAMED_CURVE,
|
|
OPENSSL_EC_EXPLICIT_CURVE
|
|
} = internalBinding('crypto');
|
|
const {
|
|
parsePublicKeyEncoding,
|
|
parsePrivateKeyEncoding,
|
|
|
|
PublicKeyObject,
|
|
PrivateKeyObject
|
|
} = require('internal/crypto/keys');
|
|
const { customPromisifyArgs } = require('internal/util');
|
|
const {
|
|
isUint32,
|
|
validateString,
|
|
validateObject,
|
|
} = require('internal/validators');
|
|
const {
|
|
ERR_INCOMPATIBLE_OPTION_PAIR,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_INVALID_CALLBACK,
|
|
ERR_MISSING_OPTION
|
|
} = require('internal/errors').codes;
|
|
|
|
const { isArrayBufferView } = require('internal/util/types');
|
|
|
|
function wrapKey(key, ctor) {
|
|
if (typeof key === 'string' || isArrayBufferView(key))
|
|
return key;
|
|
return new ctor(key);
|
|
}
|
|
|
|
function generateKeyPair(type, options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = undefined;
|
|
}
|
|
|
|
const impl = check(type, options);
|
|
|
|
if (typeof callback !== 'function')
|
|
throw new ERR_INVALID_CALLBACK(callback);
|
|
|
|
const wrap = new AsyncWrap(Providers.KEYPAIRGENREQUEST);
|
|
wrap.ondone = (ex, pubkey, privkey) => {
|
|
if (ex) return callback.call(wrap, ex);
|
|
// If no encoding was chosen, return key objects instead.
|
|
pubkey = wrapKey(pubkey, PublicKeyObject);
|
|
privkey = wrapKey(privkey, PrivateKeyObject);
|
|
callback.call(wrap, null, pubkey, privkey);
|
|
};
|
|
|
|
handleError(impl(wrap));
|
|
}
|
|
|
|
ObjectDefineProperty(generateKeyPair, customPromisifyArgs, {
|
|
value: ['publicKey', 'privateKey'],
|
|
enumerable: false
|
|
});
|
|
|
|
function generateKeyPairSync(type, options) {
|
|
const impl = check(type, options);
|
|
return handleError(impl());
|
|
}
|
|
|
|
function handleError(ret) {
|
|
if (ret === undefined)
|
|
return; // async
|
|
|
|
const [err, publicKey, privateKey] = ret;
|
|
if (err !== undefined)
|
|
throw err;
|
|
|
|
// If no encoding was chosen, return key objects instead.
|
|
return {
|
|
publicKey: wrapKey(publicKey, PublicKeyObject),
|
|
privateKey: wrapKey(privateKey, PrivateKeyObject)
|
|
};
|
|
}
|
|
|
|
function parseKeyEncoding(keyType, options) {
|
|
const { publicKeyEncoding, privateKeyEncoding } = options;
|
|
|
|
let publicFormat, publicType;
|
|
if (publicKeyEncoding == null) {
|
|
publicFormat = publicType = undefined;
|
|
} else if (typeof publicKeyEncoding === 'object') {
|
|
({
|
|
format: publicFormat,
|
|
type: publicType
|
|
} = parsePublicKeyEncoding(publicKeyEncoding, keyType,
|
|
'publicKeyEncoding'));
|
|
} else {
|
|
throw new ERR_INVALID_ARG_VALUE('options.publicKeyEncoding',
|
|
publicKeyEncoding);
|
|
}
|
|
|
|
let privateFormat, privateType, cipher, passphrase;
|
|
if (privateKeyEncoding == null) {
|
|
privateFormat = privateType = undefined;
|
|
} else if (typeof privateKeyEncoding === 'object') {
|
|
({
|
|
format: privateFormat,
|
|
type: privateType,
|
|
cipher,
|
|
passphrase
|
|
} = parsePrivateKeyEncoding(privateKeyEncoding, keyType,
|
|
'privateKeyEncoding'));
|
|
} else {
|
|
throw new ERR_INVALID_ARG_VALUE('options.privateKeyEncoding',
|
|
privateKeyEncoding);
|
|
}
|
|
|
|
return {
|
|
cipher, passphrase, publicType, publicFormat, privateType, privateFormat
|
|
};
|
|
}
|
|
|
|
function check(type, options, callback) {
|
|
validateString(type, 'type');
|
|
|
|
// These will be set after parsing the type and type-specific options to make
|
|
// the order a bit more intuitive.
|
|
let cipher, passphrase, publicType, publicFormat, privateType, privateFormat;
|
|
|
|
if (options !== undefined)
|
|
validateObject(options, 'options');
|
|
|
|
let impl;
|
|
switch (type) {
|
|
case 'rsa':
|
|
case 'rsa-pss':
|
|
{
|
|
validateObject(options, 'options');
|
|
const { modulusLength } = options;
|
|
if (!isUint32(modulusLength))
|
|
throw new ERR_INVALID_ARG_VALUE('options.modulusLength',
|
|
modulusLength);
|
|
|
|
let { publicExponent } = options;
|
|
if (publicExponent == null) {
|
|
publicExponent = 0x10001;
|
|
} else if (!isUint32(publicExponent)) {
|
|
throw new ERR_INVALID_ARG_VALUE('options.publicExponent',
|
|
publicExponent);
|
|
}
|
|
|
|
if (type === 'rsa') {
|
|
impl = (wrap) => generateKeyPairRSA(modulusLength, publicExponent,
|
|
publicFormat, publicType,
|
|
privateFormat, privateType,
|
|
cipher, passphrase, wrap);
|
|
break;
|
|
}
|
|
|
|
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);
|
|
if (saltLength !== undefined && !isUint32(saltLength))
|
|
throw new ERR_INVALID_ARG_VALUE('options.saltLength', saltLength);
|
|
|
|
impl = (wrap) => generateKeyPairRSAPSS(modulusLength, publicExponent,
|
|
hash, mgf1Hash, saltLength,
|
|
publicFormat, publicType,
|
|
privateFormat, privateType,
|
|
cipher, passphrase, wrap);
|
|
}
|
|
break;
|
|
case 'dsa':
|
|
{
|
|
validateObject(options, 'options');
|
|
const { modulusLength } = options;
|
|
if (!isUint32(modulusLength))
|
|
throw new ERR_INVALID_ARG_VALUE('options.modulusLength',
|
|
modulusLength);
|
|
|
|
let { divisorLength } = options;
|
|
if (divisorLength == null) {
|
|
divisorLength = -1;
|
|
} else if (!isUint32(divisorLength)) {
|
|
throw new ERR_INVALID_ARG_VALUE('options.divisorLength',
|
|
divisorLength);
|
|
}
|
|
|
|
impl = (wrap) => generateKeyPairDSA(modulusLength, divisorLength,
|
|
publicFormat, publicType,
|
|
privateFormat, privateType,
|
|
cipher, passphrase, wrap);
|
|
}
|
|
break;
|
|
case 'ec':
|
|
{
|
|
validateObject(options, 'options');
|
|
const { namedCurve } = options;
|
|
if (typeof namedCurve !== 'string')
|
|
throw new ERR_INVALID_ARG_VALUE('options.namedCurve', namedCurve);
|
|
let { paramEncoding } = options;
|
|
if (paramEncoding == null || paramEncoding === 'named')
|
|
paramEncoding = OPENSSL_EC_NAMED_CURVE;
|
|
else if (paramEncoding === 'explicit')
|
|
paramEncoding = OPENSSL_EC_EXPLICIT_CURVE;
|
|
else
|
|
throw new ERR_INVALID_ARG_VALUE('options.paramEncoding',
|
|
paramEncoding);
|
|
|
|
impl = (wrap) => generateKeyPairEC(namedCurve, paramEncoding,
|
|
publicFormat, publicType,
|
|
privateFormat, privateType,
|
|
cipher, passphrase, wrap);
|
|
}
|
|
break;
|
|
case 'ed25519':
|
|
case 'ed448':
|
|
case 'x25519':
|
|
case 'x448':
|
|
{
|
|
let id;
|
|
switch (type) {
|
|
case 'ed25519':
|
|
id = EVP_PKEY_ED25519;
|
|
break;
|
|
case 'ed448':
|
|
id = EVP_PKEY_ED448;
|
|
break;
|
|
case 'x25519':
|
|
id = EVP_PKEY_X25519;
|
|
break;
|
|
case 'x448':
|
|
id = EVP_PKEY_X448;
|
|
break;
|
|
}
|
|
impl = (wrap) => generateKeyPairNid(id,
|
|
publicFormat, publicType,
|
|
privateFormat, privateType,
|
|
cipher, passphrase, wrap);
|
|
}
|
|
break;
|
|
case 'dh':
|
|
{
|
|
validateObject(options, 'options');
|
|
const { group, primeLength, prime, generator } = options;
|
|
let args;
|
|
if (group != null) {
|
|
if (prime != null)
|
|
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
|
|
if (primeLength != null)
|
|
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');
|
|
if (generator != null)
|
|
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');
|
|
if (typeof group !== 'string')
|
|
throw new ERR_INVALID_ARG_VALUE('options.group', group);
|
|
args = [group];
|
|
} else {
|
|
if (prime != null) {
|
|
if (primeLength != null)
|
|
throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');
|
|
if (!isArrayBufferView(prime))
|
|
throw new ERR_INVALID_ARG_VALUE('options.prime', prime);
|
|
} else if (primeLength != null) {
|
|
if (!isUint32(primeLength))
|
|
throw new ERR_INVALID_ARG_VALUE('options.primeLength',
|
|
primeLength);
|
|
} else {
|
|
throw new ERR_MISSING_OPTION(
|
|
'At least one of the group, prime, or primeLength options');
|
|
}
|
|
|
|
if (generator != null) {
|
|
if (!isUint32(generator))
|
|
throw new ERR_INVALID_ARG_VALUE('options.generator', generator);
|
|
}
|
|
|
|
args = [prime != null ? prime : primeLength,
|
|
generator == null ? 2 : generator];
|
|
}
|
|
|
|
impl = (wrap) => generateKeyPairDH(...args,
|
|
publicFormat, publicType,
|
|
privateFormat, privateType,
|
|
cipher, passphrase, wrap);
|
|
}
|
|
break;
|
|
default:
|
|
throw new ERR_INVALID_ARG_VALUE('type', type,
|
|
'must be a supported key type');
|
|
}
|
|
|
|
if (options) {
|
|
({
|
|
cipher,
|
|
passphrase,
|
|
publicType,
|
|
publicFormat,
|
|
privateType,
|
|
privateFormat
|
|
} = parseKeyEncoding(type, options));
|
|
}
|
|
|
|
return impl;
|
|
}
|
|
|
|
module.exports = { generateKeyPair, generateKeyPairSync };
|