current/lib/internal/crypto/keygen.js
'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 } = require('internal/validators');
const {
ERR_INCOMPATIBLE_OPTION_PAIR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CALLBACK,
ERR_INVALID_OPT_VALUE,
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_OPT_VALUE('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_OPT_VALUE('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 && typeof options !== 'object')
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
function needOptions() {
if (options == null)
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
return options;
}
let impl;
switch (type) {
case 'rsa':
case 'rsa-pss':
{
const { modulusLength } = needOptions();
if (!isUint32(modulusLength))
throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength);
let { publicExponent } = options;
if (publicExponent == null) {
publicExponent = 0x10001;
} else if (!isUint32(publicExponent)) {
throw new ERR_INVALID_OPT_VALUE('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_OPT_VALUE('hash', hash);
if (mgf1Hash !== undefined && typeof mgf1Hash !== 'string')
throw new ERR_INVALID_OPT_VALUE('mgf1Hash', mgf1Hash);
if (saltLength !== undefined && !isUint32(saltLength))
throw new ERR_INVALID_OPT_VALUE('saltLength', saltLength);
impl = (wrap) => generateKeyPairRSAPSS(modulusLength, publicExponent,
hash, mgf1Hash, saltLength,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
case 'dsa':
{
const { modulusLength } = needOptions();
if (!isUint32(modulusLength))
throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength);
let { divisorLength } = options;
if (divisorLength == null) {
divisorLength = -1;
} else if (!isUint32(divisorLength)) {
throw new ERR_INVALID_OPT_VALUE('divisorLength', divisorLength);
}
impl = (wrap) => generateKeyPairDSA(modulusLength, divisorLength,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
case 'ec':
{
const { namedCurve } = needOptions();
if (typeof namedCurve !== 'string')
throw new ERR_INVALID_OPT_VALUE('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_OPT_VALUE('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':
{
const { group, primeLength, prime, generator } = needOptions();
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_OPT_VALUE('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_OPT_VALUE('prime', prime);
} else if (primeLength != null) {
if (!isUint32(primeLength))
throw new ERR_INVALID_OPT_VALUE('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_OPT_VALUE('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 };